Add the new command API + converted commands

Converted commands mostly use some backwards-compatibility hack. Please don't mind it, I'll eventually get to it.
This commit is contained in:
Vgr E. Barry 2016-11-28 20:19:48 -05:00
parent d590e7b727
commit e8338d1ef6
14 changed files with 497 additions and 345 deletions

View File

@ -596,7 +596,6 @@
"admin_forced_game": "A game mode has already been forced by an admin.",
"vote_game_fail": "You can't vote for that game mode.",
"fsend_usage": "Usage: {0}{1} <target> <message>",
"wrong_channel": "You have to be in {0} to use this command.",
"invalid_fsend_permissions": "You do not have permission to message this user or channel.",
"temp_invalid_perms": "You are not allowed to use that command right now.",
"fgame_success": "\u0002{0}\u0002 has changed the game settings successfully.",

View File

@ -27,7 +27,13 @@ class _States(Enum):
def predicate(name):
return not name.startswith(tuple(Features["CHANTYPES"]))
get = _channels.__getitem__
def get(name, *, allow_none=False):
try:
return _channels[name]
except KeyError:
if allow_none:
return None
raise
def add(name, cli, key=""):
"""Add and return a new channel, or an existing one if it exists."""

View File

@ -14,9 +14,10 @@ from oyoyo.parse import parse_nick
import botconfig
import src.settings as var
from src.dispatcher import MessageDispatcher
from src.utilities import *
from src import channels, users, logger, errlog, events
from src.messages import messages
from src import channels, users, logger, errlog, events
adminlog = logger.logger("audit.log")
@ -181,6 +182,121 @@ class handle_error:
with print_traceback():
return self.func(*args, **kwargs)
class command:
def __init__(self, *commands, flag=None, owner_only=False, chan=True, pm=False,
playing=False, silenced=False, phases=(), roles=(), users=None):
self.commands = frozenset(commands)
self.flag = flag
self.owner_only = owner_only
self.chan = chan
self.pm = pm
self.playing = playing
self.silenced = silenced
self.phases = phases
self.roles = roles
self.users = users # iterable of users that can use the command at any time (should be a mutable object)
self.func = None
self.aftergame = False
self.name = commands[0]
self.alt_allowed = bool(flag or owner_only)
alias = False
self.aliases = []
for name in commands:
for func in COMMANDS[name]:
if func.owner_only != owner_only or func.flag != flag:
raise ValueError("unmatching access levels for {0}".format(func.name))
COMMANDS[name].append(self)
if name in botconfig.ALLOWED_ALT_CHANNELS_COMMANDS:
self.alt_allowed = True
if name in getattr(botconfig, "OWNERS_ONLY_COMMANDS", ()):
self.owner_only = True
if alias:
self.aliases.append(name)
alias = True
def __call__(self, func):
if isinstance(func, command):
func = func.func
self.func = func
self.__doc__ = func.__doc__
return self
@handle_error
def caller(self, cli, rawnick, chan, rest):
user = users._add(cli, nick=rawnick) # FIXME
if users.equals(chan, users.Bot.nick): # PM
target = users.Bot
else:
target = channels.add(chan, cli)
dispatcher = MessageDispatcher(user, target)
if (not self.pm and dispatcher.private) or (not self.chan and dispatcher.public):
return # channel or PM command that we don't allow
if dispatcher.public and target is not channels.Main and not (self.flag or self.owner_only):
if "" in self.commands or not self.alt_allowed:
return # commands not allowed in alt channels
if "" in self.commands:
return self.func(var, dispatcher, rest)
if self.phases and var.PHASE not in self.phases:
return
if self.playing and (user.nick not in list_players() or user.nick in var.DISCONNECTED): # FIXME: Need to change this once list_players() / var.DISCONNECTED use User instances
return
for role in self.roles:
if user.nick in var.ROLES[role]: # FIXME: Need to change this once var.ROLES[role] holds User instances
break
else:
if (self.users is not None and user not in self.users) or self.roles:
return
if self.silenced and user.nick in var.SILENCED: # FIXME: Need to change this once var.SILENCED holds User instances
dispatcher.pm(messages["silenced"])
return
if self.roles or (self.users is not None and user in self.users):
return self.func(var, dispatcher, rest) # don't check restrictions for role commands
if self.owner_only:
if user.is_owner():
adminlog(chan, rawnick, self.name, rest)
return self.func(var, dispatcher, rest)
dispatcher.pm(messages["not_owner"])
return
temp = user.lower()
flags = var.FLAGS[temp.rawnick] + var.FLAGS_ACCS[temp.account] # TODO: add flags handling to User
if self.flag and (user.is_admin() or user.is_owner()):
adminlog(chan, rawnick, self.name, rest)
return self.func(var, dispatcher, rest)
denied_commands = var.DENY[temp.rawnick] | var.DENY_ACCS[temp.account] # TODO: add denied commands handling to User
if self.commands & denied_commands:
dispatcher.pm(messages["invalid_permissions"])
return
if self.flag:
if self.flag in flags:
adminlog(chan, rawnick, self.name, rest)
return self.func(var, dispatcher, rest)
dispatcher.pm(messages["not_an_admin"])
return
return self.func(var, dispatcher, rest)
class cmd:
def __init__(self, *cmds, raw_nick=False, flag=None, owner_only=False,
chan=True, pm=False, playing=False, silenced=False,
@ -222,8 +338,11 @@ class cmd:
return self
@handle_error
def caller(self, *args):
largs = list(args)
def caller(self, cli, rawnick, chan, rest):
if users.equals(chan, users.Bot.nick):
chan = users.parse_rawnick_as_dict(rawnick)["nick"]
largs = [cli, rawnick, chan, rest]
cli, rawnick, chan, rest = largs
nick, mode, ident, host = parse_nick(rawnick)

47
src/dispatcher.py Normal file
View File

@ -0,0 +1,47 @@
from src import channels, users
from src import settings as var
from src.utilities import list_players
class MessageDispatcher:
"""Dispatcher class for raw IRC messages."""
def __init__(self, source, target):
self.source = source
self.target = target
@property
def private(self):
return self.target is users.Bot
@property
def public(self):
return self.target is not users.Bot
def pm(self, *messages, **kwargs):
"""Send a private message or notice to the sender."""
kwargs.setdefault("notice", self.public)
self.source.send(*messages, **kwargs)
def send(self, *messages, **kwargs):
"""Send a message to the channel or a private message."""
if self.private:
self.pm(*messages, **kwargs)
else:
self.target.send(*messages, **kwargs)
def reply(self, *messages, prefix_nick=False, **kwargs):
"""Reply to the user, either in channel or privately."""
first = ""
if prefix_nick:
first = "{0}: ".format(self.source.nick)
if self.private:
self.source.send(*messages, **kwargs)
elif (self.target is channels.Main and
((self.source not in list_players() and var.PHASE in var.GAME_PHASES) or
(var.DEVOICE_DURING_NIGHT and var.PHASE == "night"))): # FIXME
kwargs.setdefault("notice", True)
self.source.send(*messages, **kwargs)
else:
kwargs.setdefault("first", first)
self.target.send(*messages, **kwargs)

View File

@ -10,7 +10,7 @@ import src.settings as var
from src.utilities import *
from src.messages import messages
from src.decorators import handle_error
from src import events
from src import events, channels, users
def game_mode(name, minp, maxp, likelihood = 0):
def decor(c):
@ -1186,23 +1186,24 @@ class MaelstromMode(GameMode):
if not var.ACCOUNTS_ONLY:
self.DEAD_HOSTS.add(var.USERS[nick]["host"].lower())
def on_join(self, evt, cli, var, nick, chan, rest, forced=False):
if var.PHASE != "day" or (nick != chan and chan != botconfig.CHANNEL):
def on_join(self, evt, var, wrapper, message, forced=False):
if var.PHASE != "day" or (wrapper.public and wrapper.target is not channels.Main):
return
if (irc_lower(nick) in (irc_lower(x) for x in var.ALL_PLAYERS) or
irc_lower(var.USERS[nick]["account"]) in self.DEAD_ACCOUNTS or
var.USERS[nick]["host"].lower() in self.DEAD_HOSTS):
cli.notice(nick, messages["maelstrom_dead"])
temp = wrapper.source.lower()
if (temp.nick in (irc_lower(x) for x in var.ALL_PLAYERS) or # FIXME
temp.account in self.DEAD_ACCOUNTS or
temp.host in self.DEAD_HOSTS):
wrapper.pm(messages["maelstrom_dead"])
return
if not forced and evt.data["join_player"](cli, nick, botconfig.CHANNEL, sanity=False):
self._on_join(cli, var, nick, chan)
if not forced and evt.data["join_player"](var, type(wrapper)(wrapper.source, channels.Main), sanity=False):
self._on_join(var, wrapper)
evt.prevent_default = True
elif forced:
# in fjoin, handle this differently
jp = evt.data["join_player"]
evt.data["join_player"] = lambda cli, nick, chan, who=None, forced=False: jp(cli, nick, chan, who=who, forced=forced, sanity=False) and self._on_join(cli, var, nick, chan)
evt.data["join_player"] = lambda var, wrapper, who=None, forced=False: jp(var, wrapper, who=who, forced=forced, sanity=False) and self._on_join(var, wrapper)
def _on_join(self, cli, var, nick, chan):
def _on_join(self, var, wrapper):
role = random.choice(self.roles)
lpl = len(list_players()) + 1
@ -1232,31 +1233,30 @@ class MaelstromMode(GameMode):
elif role == "succubus":
lsuccubi += 1
if self.chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, cli, end_game=False):
return self._on_join(cli, var, nick, chan)
if self.chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, wrapper.client, end_game=False):
return self._on_join(var, wrapper)
var.ROLES[role].add(nick)
var.ORIGINAL_ROLES[role].add(nick)
var.FINAL_ROLES[nick] = role
var.LAST_SAID_TIME[nick] = datetime.now()
if nick in var.USERS:
var.PLAYERS[nick] = var.USERS[nick]
var.ROLES[role].add(wrapper.source.nick)
var.ORIGINAL_ROLES[role].add(wrapper.source.nick)
var.FINAL_ROLES[wrapper.source.nick] = role
var.LAST_SAID_TIME[wrapper.source.nick] = datetime.now()
if wrapper.source.nick in var.USERS:
var.PLAYERS[wrapper.source.nick] = var.USERS[wrapper.source.nick]
if role == "doctor":
lpl = len(list_players())
var.DOCTORS[nick] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * lpl)
var.DOCTORS[wrapper.source.nick] = math.ceil(var.DOCTOR_IMMUNIZATION_MULTIPLIER * lpl)
# let them know their role
# FIXME: this is fugly
from src.decorators import COMMANDS
COMMANDS["myrole"][0].caller(cli, nick, chan, "")
COMMANDS["myrole"][0].caller(wrapper.source.client, wrapper.source.nick, wrapper.target.name, "") # FIXME: New/old API
# if they're a wolfchat role, alert the other wolves
if role in var.WOLFCHAT_ROLES:
relay_wolfchat_command(cli, nick, messages["wolfchat_new_member"].format(nick, role), var.WOLFCHAT_ROLES, is_wolf_command=True, is_kill_command=True)
relay_wolfchat_command(wrapper.source.client, wrapper.source.nick, messages["wolfchat_new_member"].format(wrapper.source.nick, role), var.WOLFCHAT_ROLES, is_wolf_command=True, is_kill_command=True)
# TODO: make this part of !myrole instead, no reason we can't give out wofllist in that
wolves = list_players(var.WOLFCHAT_ROLES)
pl = list_players()
random.shuffle(pl)
pl.remove(nick)
pl.remove(wrapper.source.nick)
for i, player in enumerate(pl):
prole = get_role(player)
if prole in var.WOLFCHAT_ROLES:
@ -1266,7 +1266,7 @@ class MaelstromMode(GameMode):
pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole)
elif player in var.ROLES["cursed villager"]:
pl[i] = player + " (cursed)"
pm(cli, nick, "Players: " + ", ".join(pl))
wrapper.pm("Players: " + ", ".join(pl))
def role_attribution(self, evt, cli, var, chk_win_conditions, villagers):
self.chk_win_conditions = chk_win_conditions

View File

@ -15,22 +15,19 @@ def on_privmsg(cli, rawnick, chan, msg, *, notice=False):
if notice and "!" not in rawnick or not rawnick: # server notice; we don't care about those
return
if not users.equals(chan, botconfig.NICK) and botconfig.IGNORE_HIDDEN_COMMANDS and not chan.startswith(tuple(hooks.Features["CHANTYPES"])):
if not users.equals(chan, users.Bot.nick) and botconfig.IGNORE_HIDDEN_COMMANDS and not chan.startswith(tuple(hooks.Features["CHANTYPES"])):
return
if (notice and ((not users.equals(chan, botconfig.NICK) and not botconfig.ALLOW_NOTICE_COMMANDS) or
(users.equals(chan, botconfig.NICK) and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
if (notice and ((not users.equals(chan, users.Bot.nick) and not botconfig.ALLOW_NOTICE_COMMANDS) or
(users.equals(chan, users.Bot.nick) and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
return # not allowed in settings
if users.equals(chan, botconfig.NICK):
chan = users.parse_rawnick_as_dict(rawnick)["nick"]
for fn in decorators.COMMANDS[""]:
fn.caller(cli, rawnick, chan, msg)
phase = var.PHASE
for x in list(decorators.COMMANDS.keys()):
if chan != users.parse_rawnick_as_dict(rawnick)["nick"] and not msg.lower().startswith(botconfig.CMD_CHAR):
if not users.equals(chan, users.Bot.nick) and not msg.lower().startswith(botconfig.CMD_CHAR):
break # channel message but no prefix; ignore
if msg.lower().startswith(botconfig.CMD_CHAR+x):
h = msg[len(x)+len(botconfig.CMD_CHAR):]

View File

@ -55,13 +55,13 @@ def dullahan_retract(cli, nick, chan, rest):
pm(cli, nick, messages["retracted_kill"])
@event_listener("player_win")
def on_player_win(evt, cli, var, nick, role, winner, survived):
def on_player_win(evt, var, user, role, winner, survived):
if role != "dullahan":
return
alive = set(list_players())
if nick in var.ENTRANCED:
if user.nick in var.ENTRANCED:
alive -= var.ROLES["succubus"]
if not TARGETS[nick] & alive:
if not TARGETS[user.nick] & alive:
evt.data["iwon"] = True
@event_listener("del_player")
@ -222,7 +222,7 @@ def on_myrole(evt, cli, var, nick):
evt.data["messages"].append(messages["dullahan_targets_dead"])
@event_listener("revealroles_role")
def on_revealroles_role(evt, cli, var, nickname, role):
def on_revealroles_role(evt, var, wrapper, nickname, role):
if role == "dullahan" and nickname in TARGETS:
targets = TARGETS[nickname] - var.DEAD
if targets:

View File

@ -53,7 +53,7 @@ brokentotem = set() # type: Set[str]
@cmd("give", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
@cmd("totem", chan=False, pm=True, playing=True, silenced=True, phases=("night",), roles=var.TOTEM_ORDER)
def totem(cli, nick, chan, rest, prefix="You"):
def totem(cli, nick, chan, rest, prefix="You"): # XXX: The transition_day_begin event needs updating alongside this
"""Give a totem to a player."""
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True)
if not victim:
@ -299,7 +299,7 @@ def on_chk_decision_lynch5(evt, cli, var, voters):
evt.params.del_player(cli, target, True, end_game=False, killer_role="shaman", deadlist=evt.data["deadlist"], original=target, ismain=False)
@event_listener("player_win")
def on_player_win(evt, cli, var, splr, rol, winner, survived):
def on_player_win(evt, var, user, rol, winner, survived):
if rol == "crazed shaman" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
evt.data["iwon"] = True
@ -320,7 +320,7 @@ def on_transition_day_begin(evt, cli, var):
ps.remove(succubus)
if ps:
target = random.choice(ps)
totem.func(cli, shaman, shaman, target, messages["random_totem_prefix"])
totem.func(cli, shaman, shaman, target, messages["random_totem_prefix"]) # XXX: Old API
else:
LASTGIVEN[shaman] = None
elif shaman not in SHAMANS:
@ -594,7 +594,7 @@ def on_myrole(evt, cli, var, nick):
evt.data["messages"].append(messages["totem_simple"].format(TOTEMS[nick]))
@event_listener("revealroles_role")
def on_revealroles(evt, cli, var, nickname, role):
def on_revealroles(evt, var, wrapper, nickname, role):
if role in var.TOTEM_ORDER and nickname in TOTEMS:
if nickname in SHAMANS:
evt.data["special_case"].append("giving {0} totem to {1}".format(TOTEMS[nickname], SHAMANS[nickname][0]))

View File

@ -7,8 +7,8 @@ from collections import defaultdict
import botconfig
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src import channels, users, debuglog, errlog, plog
from src.decorators import command, event_listener
from src.messages import messages
from src.events import Event

View File

@ -4,79 +4,79 @@ from collections import defaultdict
import src.settings as var
from src.utilities import *
from src import debuglog, errlog, plog
from src.decorators import cmd, event_listener
from src import channels, users, debuglog, errlog, plog
from src.decorators import command, event_listener
from src.messages import messages
from src.events import Event
KILLS = {} # type: Dict[str, str]
GHOSTS = {} # type: Dict[str, str]
GHOSTS = {} # type: Dict[users.User, str]
# temporary holding variable, only non-empty during transition_day
# as such, no need to track nick changes, etc. with it
drivenoff = {} # type: Dict[str, str]
@cmd("kill", chan=False, pm=True, playing=False, silenced=True, phases=("night",), nicks=GHOSTS)
def vg_kill(cli, nick, chan, rest):
@command("kill", chan=False, pm=True, playing=False, silenced=True, phases=("night",), users=GHOSTS)
def vg_kill(var, wrapper, message):
"""Take revenge on someone each night after you die."""
if GHOSTS[nick][0] == "!":
if GHOSTS[wrapper.source][0] == "!":
return
victim = get_victim(cli, nick, re.split(" +",rest)[0], False)
victim = get_victim(wrapper.source.client, wrapper.source.nick, re.split(" +", message)[0], False)
if not victim:
return
if victim == nick:
pm(cli, nick, messages["player_dead"])
if victim == wrapper.source.nick:
wrapper.pm(messages["player_dead"])
return
wolves = list_players(var.WOLFTEAM_ROLES)
if GHOSTS[nick] == "wolves" and victim not in wolves:
pm(cli, nick, messages["vengeful_ghost_wolf"])
if GHOSTS[wrapper.source] == "wolves" and victim not in wolves:
wrapper.pm(messages["vengeful_ghost_wolf"])
return
elif GHOSTS[nick] == "villagers" and victim in wolves:
pm(cli, nick, messages["vengeful_ghost_villager"])
elif GHOSTS[wrapper.source] == "villagers" and victim in wolves:
wrapper.pm(messages["vengeful_ghost_villager"])
return
orig = victim
evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": False})
evt.dispatch(cli, var, "kill", nick, victim, frozenset({"detrimental"}))
evt.dispatch(wrapper.source.client, var, "kill", wrapper.source.nick, victim, frozenset({"detrimental"}))
if evt.prevent_default:
return
victim = evt.data["target"]
KILLS[nick] = victim
KILLS[wrapper.source.nick] = victim
msg = messages["wolf_target"].format(orig)
pm(cli, nick, messages["player"].format(msg))
wrapper.pm(messages["player"].format(msg))
debuglog("{0} ({1}) KILL: {2} ({3})".format(nick, get_role(nick), victim, get_role(victim)))
chk_nightdone(cli)
debuglog("{0} ({1}) KILL: {2} ({3})".format(wrapper.source.nick, get_role(wrapper.source.nick), victim, get_role(victim)))
chk_nightdone(wrapper.source.client)
@cmd("retract", "r", chan=False, pm=True, playing=False, phases=("night",))
def vg_retract(cli, nick, chan, rest):
@command("retract", "r", chan=False, pm=True, playing=False, phases=("night",))
def vg_retract(var, wrapper, message):
"""Removes a vengeful ghost's kill selection."""
if nick not in GHOSTS:
if wrapper.source not in GHOSTS:
return
if nick in KILLS:
del KILLS[nick]
pm(cli, nick, messages["retracted_kill"])
if wrapper.source.nick in KILLS:
del KILLS[wrapper.source.nick]
wrapper.pm(messages["retracted_kill"])
@event_listener("list_participants")
def on_list_participants(evt, var):
evt.data["pl"].extend([p for p in GHOSTS if GHOSTS[p][0] != "!"])
evt.data["pl"].extend([p.nick for p in GHOSTS if GHOSTS[p][0] != "!"])
evt.data["pl"].extend([p for p in drivenoff])
@event_listener("player_win", priority=1)
def on_player_win(evt, cli, var, nick, role, winner, survived):
def on_player_win(evt, var, user, role, winner, survived):
# alive VG winning is handled in villager.py
# extending VG to work with new teams can be done by registering
# a listener at priority > 1, importing src.roles.vengefulghost,
# and checking if the nick is in GHOSTS.
if nick in GHOSTS:
# and checking if the user is in GHOSTS.
if user in GHOSTS:
evt.data["special"].append("vg activated")
against = GHOSTS[nick]
if GHOSTS[nick][0] == "!":
against = GHOSTS[user]
if against[0] == "!":
evt.data["special"].append("vg driven off")
against = against[1:]
if against == "villagers" and winner == "wolves":
@ -97,14 +97,15 @@ def on_del_player(evt, cli, var, nick, nickrole, nicktpls, death_triggers):
del KILLS[h]
# extending VG to work with new teams can be done by registering a listener
# at priority < 6, importing src.roles.vengefulghost, and setting
# GHOSTS[nick] to something; if that is done then this logic is not run.
if death_triggers and nickrole == "vengeful ghost" and nick not in GHOSTS:
# GHOSTS[user] to something; if that is done then this logic is not run.
user = users._get(nick) # FIXME
if death_triggers and nickrole == "vengeful ghost" and user not in GHOSTS:
if evt.params.killer_role in var.WOLFTEAM_ROLES:
GHOSTS[nick] = "wolves"
GHOSTS[user] = "wolves"
else:
GHOSTS[nick] = "villagers"
pm(cli, nick, messages["vengeful_turn"].format(GHOSTS[nick]))
debuglog(nick, "(vengeful ghost) TRIGGER", GHOSTS[nick])
GHOSTS[user] = "villagers"
user.send(messages["vengeful_turn"].format(GHOSTS[user]))
debuglog(nick, "(vengeful ghost) TRIGGER", GHOSTS[user])
@event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick):
@ -118,8 +119,6 @@ def on_rename(evt, cli, var, prefix, nick):
KILLS.update(kvp)
if prefix in KILLS:
del KILLS[prefix]
if prefix in GHOSTS:
GHOSTS[nick] = GHOSTS.pop(prefix)
@event_listener("night_acted")
def on_acted(evt, cli, var, nick, sender):
@ -132,22 +131,22 @@ def on_transition_day_begin(evt, cli, var):
wolves = set(list_players(var.WOLFTEAM_ROLES))
villagers = set(list_players()) - wolves
for ghost, target in GHOSTS.items():
if target[0] == "!" or ghost in var.SILENCED:
if target[0] == "!" or ghost.nick in var.SILENCED:
continue
if ghost not in KILLS:
if ghost.nick not in KILLS:
choice = set()
if target == "wolves":
choice = wolves.copy()
elif target == "villagers":
choice = villagers.copy()
evt = Event("vg_kill", {"pl": choice})
evt.dispatch(cli, var, ghost, target)
evt.dispatch(var, ghost, target)
choice = evt.data["pl"]
# roll this into the above event once succubus is split off
if ghost in var.ENTRANCED:
if ghost.nick in var.ENTRANCED:
choice -= var.ROLES["succubus"]
if choice:
KILLS[ghost] = random.choice(list(choice))
KILLS[ghost.nick] = random.choice(list(choice))
@event_listener("transition_day", priority=2)
def on_transition_day(evt, cli, var):
@ -159,35 +158,35 @@ def on_transition_day(evt, cli, var):
@event_listener("transition_day", priority=3.01)
def on_transition_day3(evt, cli, var):
for k, d in list(KILLS.items()):
if GHOSTS[k] == "villagers":
if GHOSTS[users._get(k)] == "villagers":
evt.data["killers"][d].remove(k)
evt.data["killers"][d].insert(0, k)
@event_listener("transition_day", priority=6.01)
def on_transition_day6(evt, cli, var):
for k, d in list(KILLS.items()):
if GHOSTS[k] == "villagers" and k in evt.data["killers"][d]:
if GHOSTS[users._get(k)] == "villagers" and k in evt.data["killers"][d]:
evt.data["killers"][d].remove(k)
evt.data["killers"][d].insert(0, k)
# important, otherwise our del_player listener messages the vg
del KILLS[k]
@event_listener("retribution_kill")
@event_listener("retribution_kill") # FIXME: This function, and all of the event
def on_retribution_kill(evt, cli, var, victim, orig_target):
t = evt.data["target"]
if t in GHOSTS:
drivenoff[t] = GHOSTS[t]
GHOSTS[t] = "!" + GHOSTS[t]
if users._get(t) in GHOSTS:
drivenoff[t] = GHOSTS[users._get(t)]
GHOSTS[users._get(t)] = "!" + GHOSTS[users._get(t)]
evt.data["message"].append(messages["totem_banish"].format(victim, t))
evt.data["target"] = None
@event_listener("get_participant_role")
def on_get_participant_role(evt, var, nick):
if nick in GHOSTS:
if users._get(nick) in GHOSTS: # FIXME
if nick in drivenoff:
against = drivenoff[nick]
else:
against = GHOSTS[nick]
against = GHOSTS[users._get(nick)]
if against == "villagers":
evt.data["role"] = "wolf"
elif against == "wolves":
@ -196,7 +195,7 @@ def on_get_participant_role(evt, var, nick):
@event_listener("chk_nightdone")
def on_chk_nightdone(evt, cli, var):
evt.data["actedcount"] += len(KILLS)
evt.data["nightroles"].extend([p for p in GHOSTS if GHOSTS[p][0] != "!"])
evt.data["nightroles"].extend([p.nick for p in GHOSTS if GHOSTS[p][0] != "!"])
@event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, cli, var):
@ -215,27 +214,28 @@ def on_transition_night_end(evt, cli, var):
random.shuffle(pl)
if v_ghost in var.PLAYERS and not is_user_simple(v_ghost):
pm(cli, v_ghost, messages["vengeful_ghost_notify"].format(who))
if not v_ghost.prefers_simple():
v_ghost.send(messages["vengeful_ghost_notify"].format(who))
else:
pm(cli, v_ghost, messages["vengeful_ghost_simple"])
pm(cli, v_ghost, who.capitalize() + ": " + ", ".join(pl))
debuglog("GHOST: {0} (target: {1}) - players: {2}".format(v_ghost, who, ", ".join(pl)))
v_ghost.send(messages["vengeful_ghost_simple"])
v_ghost.send(who.capitalize() + ": " + ", ".join(pl))
debuglog("GHOST: {0} (target: {1}) - players: {2}".format(v_ghost.nick, who, ", ".join(pl)))
@event_listener("myrole")
def on_myrole(evt, cli, var, nick):
if nick in GHOSTS:
user = users._get(nick)
if user in GHOSTS:
evt.prevent_default = True
if GHOSTS[nick][0] != "!":
pm(cli, nick, messages["vengeful_role"].format(GHOSTS[nick]))
if GHOSTS[user][0] != "!":
user.send(messages["vengeful_role"].format(GHOSTS[user]))
@event_listener("revealroles")
def on_revealroles(evt, cli, var):
def on_revealroles(evt, var, wrapper):
if GHOSTS:
glist = []
for ghost, team in GHOSTS.items():
dead = "driven away, " if team[0] == "!" else ""
glist.append("{0} ({1}against {2})".format(ghost, dead, team.lstrip("!")))
glist.append("{0} ({1}against {2})".format(ghost.nick, dead, team.lstrip("!")))
evt.data["output"].append("\u0002dead vengeful ghost\u0002: {0}".format(", ".join(glist)))
@event_listener("begin_day")

View File

@ -33,7 +33,7 @@ def on_transition_night_end(evt, cli, var):
# No listeners should register before this one
# This sets up the initial state, based on village/wolfteam/neutral affiliation
@event_listener("player_win", priority=0)
def on_player_win(evt, cli, var, nick, role, winner, survived):
def on_player_win(evt, var, user, role, winner, survived):
# init won/iwon to False
evt.data["won"] = False
evt.data["iwon"] = False

View File

@ -145,7 +145,7 @@ def on_transition_night_end(evt, cli, var):
pm(cli, child, messages["child_simple"])
@event_listener("revealroles_role")
def on_revealroles_role(evt, cli, var, nick, role):
def on_revealroles_role(evt, var, wrapper, nick, role):
if role == "wild child":
if nick in IDOLS:
evt.data["special_case"].append("picked {0} as idol".format(IDOLS[nick]))

View File

@ -104,8 +104,14 @@ def _add(cli, *, nick, ident=None, host=None, realname=None, account=None):
new = cls(cli, nick, ident, host, realname, account)
if new is not Bot and new.ident is not None and new.host is not None:
_users.add(new)
if new is not Bot:
try:
hash(new)
except ValueError:
pass
else:
_users.add(new)
return new
def add(nick, **blah): # backwards-compatible API
@ -246,7 +252,7 @@ class User(IRCContext):
return True
for hostmask in hosts:
if match_hostmask(hostmask, self.nick, self.ident, self.host):
if self.match_hostmask(hostmask):
return True
return False
@ -268,7 +274,7 @@ class User(IRCContext):
return True
for hostmask in hosts:
if match_hostmask(hostmask, self.nick, self.ident, self.host):
if self.match_hostmask(hostmask):
return True
except AttributeError:
pass

View File

@ -46,10 +46,11 @@ import botconfig
import src
import src.settings as var
from src.utilities import *
from src import db, decorators, events, channels, users, hooks, logger, proxy, debuglog, errlog, plog
from src.decorators import cmd, hook, handle_error, event_listener, COMMANDS
from src import db, events, channels, users, hooks, logger, proxy, debuglog, errlog, plog
from src.decorators import command, cmd, hook, handle_error, event_listener, COMMANDS
from src.messages import messages
from src.warnings import *
from src.context import IRCContext
# done this way so that events is accessible in !eval (useful for debugging)
Event = events.Event
@ -135,12 +136,12 @@ def connect_callback():
signal.signal(signal.SIGINT, signal.SIG_DFL)
if signum in (signal.SIGINT, signal.SIGTERM):
forced_exit.func(cli, "<console>", botconfig.CHANNEL, "")
forced_exit.func(cli, "<console>", botconfig.CHANNEL, "") # XXX: Old API
elif signum == SIGUSR1:
restart_program.func(cli, "<console>", botconfig.CHANNEL, "")
restart_program.func(cli, "<console>", botconfig.CHANNEL, "") # XXX: Old API
elif signum == SIGUSR2:
plog("Scheduling aftergame restart")
aftergame.func(cli, "<console>", botconfig.CHANNEL, "frestart")
aftergame.func(cli, "<console>", botconfig.CHANNEL, "frestart") # XXX: Old API
signal.signal(signal.SIGINT, sighandler)
signal.signal(signal.SIGTERM, sighandler)
@ -360,7 +361,7 @@ def refreshdb(cli, nick, chan, rest):
reply(cli, nick, chan, "Done.")
@cmd("die", "bye", "fdie", "fbye", flag="D", pm=True)
def forced_exit(cli, nick, chan, rest):
def forced_exit(cli, nick, chan, rest): # XXX: sighandler (top of file) also needs updating alongside this one
"""Forces the bot to close."""
args = rest.split()
@ -413,7 +414,7 @@ def _restart_program(cli, mode=None):
@cmd("restart", "frestart", flag="D", pm=True)
def restart_program(cli, nick, chan, rest):
def restart_program(cli, nick, chan, rest): # XXX: sighandler (top of file) also needs updating alongside this one
"""Restarts the bot."""
args = rest.split()
@ -791,7 +792,7 @@ def toggle_altpinged_status(nick, value, old=None):
var.PING_IF_NUMS[old].discard(hostmask)
@handle_error
def join_timer_handler(cli):
def join_timer_handler():
with var.WARNING_LOCK:
var.PINGING_IFS = True
to_ping = []
@ -830,7 +831,7 @@ def join_timer_handler(cli):
var.PINGING_IFS = False
return
@hook("whoreply", hookid=387)
@hook("whoreply", hookid=387) # FIXME: Use events
def ping_altpingers_noacc(cli, bot_server, bot_nick, chan, ident, host, server, nick, status, hopcount_gecos):
if ("G" in status or is_user_stasised(nick) or not var.PINGING_IFS or
nick == bot_nick or nick in pl):
@ -865,7 +866,7 @@ def join_timer_handler(cli):
var.PINGED_ALREADY.add(hostmask)
@hook("endofwho", hookid=387)
def fetch_altpingers(*stuff):
def fetch_altpingers(cli, *stuff):
# fun fact: if someone joined 10 seconds after someone else, the bot would break.
# effectively, the join would delete join_pinger from var.TIMERS and this function
# here would be reached before it was created again, thus erroring and crashing.
@ -882,11 +883,7 @@ def join_timer_handler(cli):
cli.msg(botconfig.CHANNEL, msg)
# FIXME
if not var.DISABLE_ACCOUNTS:
cli.who(botconfig.CHANNEL, "%tcuihsnfdlar,")
else:
cli.who(botconfig.CHANNEL)
channels.Main.who()
def get_deadchat_pref(nick):
if users.exists(nick):
@ -991,8 +988,8 @@ def deadchat_pref(cli, nick, chan, rest):
reply(cli, nick, chan, msg, private=True)
@cmd("join", "j", pm=True)
def join(cli, nick, chan, rest):
@command("join", "j", pm=True)
def join(var, wrapper, message):
"""Either starts a new game of Werewolf or joins an existing game that has not started yet."""
# keep this and the event in fjoin() in sync
evt = Event("join", {
@ -1000,72 +997,57 @@ def join(cli, nick, chan, rest):
"join_deadchat": join_deadchat,
"vote_gamemode": vote_gamemode
})
if not evt.dispatch(cli, var, nick, chan, rest, forced=False):
if not evt.dispatch(var, wrapper, message, forced=False):
return
if var.PHASE in ("none", "join"):
if chan == nick:
if wrapper.private:
return
if var.ACCOUNTS_ONLY:
if users.exists(nick) and (not users.get(nick).account or users.get(nick).account == "*"):
cli.notice(nick, messages["not_logged_in"])
if users._get(nick).account is None: # FIXME
wrapper.pm(messages["not_logged_in"])
return
if evt.data["join_player"](cli, nick, chan) and rest:
evt.data["vote_gamemode"](cli, nick, chan, rest.lower().split()[0], False)
if evt.data["join_player"](var, wrapper) and message:
evt.data["vote_gamemode"](var, wrapper, message.lower().split()[0], doreply=False)
else: # join deadchat
if chan == nick and nick != botconfig.NICK:
evt.data["join_deadchat"](cli, nick)
if wrapper.private and wrapper.source is not wrapper.target:
evt.data["join_deadchat"](var, wrapper)
def join_player(cli, player, chan, who=None, forced=False, *, sanity=True):
def join_player(var, wrapper, who=None, forced=False, *, sanity=True):
if who is None:
who = player
who = wrapper.source
pl = list_players()
if chan != botconfig.CHANNEL:
if wrapper.target is not channels.Main:
return False
if users.exists(player):
ident = irc_lower(users.get(player).ident)
host = users.get(player).host.lower()
acc = irc_lower(users.get(player).account)
hostmask = player + "!" + ident + "@" + host
elif is_fake_nick(player) and botconfig.DEBUG_MODE:
# fakenick
ident = None
host = None
acc = None
hostmask = None
else:
return False # Not normal
if not acc or acc == "*" or var.DISABLE_ACCOUNTS:
acc = None
stasis = is_user_stasised(player)
stasis = wrapper.source.stasis_count()
if stasis > 0:
if forced and stasis == 1:
decrement_stasis(player)
decrement_stasis(wrapper.source.nick)
else:
cli.notice(who, messages["stasis"].format(
"you are" if player == who else player + " is", stasis,
"s" if stasis != 1 else ""))
who.send(messages["stasis"].format(
"you are" if wrapper.source is who else wrapper.source.nick + " is", stasis,
"s" if stasis != 1 else ""), notice=True)
return False
temp = wrapper.source.lower()
# don't check unacked warnings on fjoin
if who == player and db.has_unacknowledged_warnings(acc, hostmask):
cli.notice(player, messages["warn_unacked"])
if wrapper.source is who and db.has_unacknowledged_warnings(temp.account, temp.rawnick):
wrapper.pm(messages["warn_unacked"])
return False
cmodes = [("+v", player)]
cmodes = [("+v", wrapper.source.nick)]
if var.PHASE == "none":
if var.AUTO_TOGGLE_MODES and users.exists(player) and users.get(player).modes:
for mode in users.get(player).modes:
cmodes.append(("-"+mode, player))
users.get(player).moded.update(users.get(player).modes)
users.get(player).modes = set()
mass_mode(cli, cmodes, [])
var.ROLES["person"].add(player)
var.ALL_PLAYERS.append(player)
if var.AUTO_TOGGLE_MODES and users.get(player).modes: # FIXME: Need to properly handle mode changes (whole block)
for mode in users.get(wrapper.source.nick).modes:
cmodes.append(("-"+mode, wrapper.source.nick))
users.get(wrapper.source.nick).moded.update(users.get(wrapper.source.nick).modes)
users.get(wrapper.source.nick).modes.clear()
var.ROLES["person"].add(wrapper.source.nick) # FIXME: Need to store Users, not nicks
var.ALL_PLAYERS.append(wrapper.source.nick)
var.PHASE = "join"
with var.WAIT_TB_LOCK:
var.WAIT_TB_TOKENS = var.WAIT_TB_INIT
@ -1073,12 +1055,12 @@ def join_player(cli, player, chan, who=None, forced=False, *, sanity=True):
var.GAME_ID = time.time()
var.PINGED_ALREADY_ACCS = set()
var.PINGED_ALREADY = set()
if host:
var.JOINED_THIS_GAME.add(ident + "@" + host)
if acc:
var.JOINED_THIS_GAME_ACCS.add(acc)
if wrapper.source.userhost:
var.JOINED_THIS_GAME.add(wrapper.source.userhost)
if wrapper.source.account:
var.JOINED_THIS_GAME_ACCS.add(wrapper.source.account)
var.CAN_START_TIME = datetime.now() + timedelta(seconds=var.MINIMUM_WAIT)
cli.msg(chan, messages["new_game"].format(player, botconfig.CMD_CHAR))
wrapper.send(messages["new_game"].format(wrapper.source.nick, botconfig.CMD_CHAR))
# Set join timer
if var.JOIN_TIME_LIMIT > 0:
@ -1087,52 +1069,50 @@ def join_player(cli, player, chan, who=None, forced=False, *, sanity=True):
t.daemon = True
t.start()
elif player in pl:
cli.notice(who, messages["already_playing"].format("You" if who == player else "They"))
elif wrapper.source.nick in pl: # FIXME: To fix when everything returns Users
who.send(messages["already_playing"].format("You" if who is wrapper.source else "They"), notice=True)
# if we're not doing insane stuff, return True so that one can use !join to vote for a game mode
# even if they are already joined. If we ARE doing insane stuff, return False to indicate that
# the player was not successfully joined by this call.
return sanity
elif len(pl) >= var.MAX_PLAYERS:
cli.notice(who, messages["too_many_players"])
who.send(messages["too_many_players"], notice=True)
return False
elif sanity and var.PHASE != "join":
cli.notice(who, messages["game_already_running"])
who.send(messages["game_already_running"], notice=True)
return False
else:
if acc is not None and not botconfig.DEBUG_MODE:
for user in pl:
if irc_lower(users.get(user).account) == acc:
if not botconfig.DEBUG_MODE:
for nick in pl:
if users._get(nick).lower().account == temp.account: # FIXME
msg = messages["account_already_joined"]
if who == player:
cli.notice(who, msg.format(user, "your", messages["join_swap_instead"].format(botconfig.CMD_CHAR)))
if who is wrapper.source:
who.send(msg.format(user, "your", messages["join_swap_instead"].format(botconfig.CMD_CHAR)), notice=True)
else:
cli.notice(who, msg.format(user, "their", ""))
who.send(msg.format(user, "their", ""), notice=True)
return
var.ALL_PLAYERS.append(player)
if not is_fake_nick(player) or not botconfig.DEBUG_MODE:
if var.AUTO_TOGGLE_MODES and users.get(player).modes:
for mode in var.USERS[player]["modes"]:
cmodes.append(("-"+mode, player))
users.get(player).moded.update(users.get(player).modes)
users.get(player).modes = set()
mass_mode(cli, cmodes, [])
cli.msg(chan, messages["player_joined"].format(player, len(pl) + 1))
var.ALL_PLAYERS.append(wrapper.source.nick)
if not wrapper.source.is_fake or not botconfig.DEBUG_MODE:
if var.AUTO_TOGGLE_MODES and users.get(wrapper.source.nick).modes:
for mode in users.get(wrapper.source.nick).modes:
cmodes.append(("-"+mode, wrapper.source.nick))
users.get(wrapper.source.nick).moded.update(users.get(wrapper.source.nick).modes)
users.get(wrapper.source.nick).modes.clear()
wrapper.send(messages["player_joined"].format(wrapper.source.nick, len(pl) + 1))
if not sanity:
# Abandon Hope All Ye Who Enter Here
leave_deadchat(cli, player)
var.SPECTATING_DEADCHAT.discard(player)
var.SPECTATING_WOLFCHAT.discard(player)
leave_deadchat(wrapper.source.client, wrapper.source.nick)
var.SPECTATING_DEADCHAT.discard(wrapper.source.nick)
var.SPECTATING_WOLFCHAT.discard(wrapper.source.nick)
return True
var.ROLES["person"].add(player)
if not is_fake_nick(player):
hostmask = ident + "@" + host
if hostmask not in var.JOINED_THIS_GAME and (not acc or acc not in var.JOINED_THIS_GAME_ACCS):
var.ROLES["person"].add(wrapper.source.nick)
if not wrapper.source.is_fake:
if wrapper.source.userhost not in var.JOINED_THIS_GAME and wrapper.source.account not in var.JOINED_THIS_GAME_ACCS:
# make sure this only happens once
var.JOINED_THIS_GAME.add(hostmask)
if acc:
var.JOINED_THIS_GAME_ACCS.add(acc)
var.JOINED_THIS_GAME.add(wrapper.source.userhost)
if wrapper.source.account:
var.JOINED_THIS_GAME_ACCS.add(wrapper.source.account)
now = datetime.now()
# add var.EXTRA_WAIT_JOIN to wait time
@ -1154,11 +1134,13 @@ def join_player(cli, player, chan, who=None, forced=False, *, sanity=True):
if "join_pinger" in var.TIMERS:
var.TIMERS["join_pinger"][0].cancel()
t = threading.Timer(10, join_timer_handler, (cli,))
t = threading.Timer(10, join_timer_handler)
var.TIMERS["join_pinger"] = (t, time.time(), 10)
t.daemon = True
t.start()
channels.Main.mode(*cmodes)
return True
@handle_error
@ -1179,8 +1161,8 @@ def kill_join(cli, chan):
var.AFTER_FLASTGAME = None
@cmd("fjoin", flag="A")
def fjoin(cli, nick, chan, rest):
@command("fjoin", flag="A")
def fjoin(var, wrapper, message):
"""Forces someone to join a game."""
# keep this and the event in def join() in sync
evt = Event("join", {
@ -1188,24 +1170,26 @@ def fjoin(cli, nick, chan, rest):
"join_deadchat": join_deadchat,
"vote_gamemode": vote_gamemode
})
if not evt.dispatch(cli, var, nick, chan, rest, forced=True):
if not evt.dispatch(var, wrapper, message, forced=True):
return
noticed = False
fake = False
if not rest.strip():
evt.data["join_player"](cli, nick, chan, forced=True)
if not message.strip():
evt.data["join_player"](var, wrapper, forced=True)
for tojoin in re.split(" +",rest):
for tojoin in re.split(" +", message):
tojoin = tojoin.strip()
if "-" in tojoin:
first, hyphen, last = tojoin.partition("-")
if first.isdigit() and last.isdigit():
if int(last)+1 - int(first) > var.MAX_PLAYERS - len(list_players()):
cli.msg(chan, messages["too_many_players_to_join"].format(nick))
wrapper.send(messages["too_many_players_to_join"].format(wrapper.source.nick))
break
fake = True
for i in range(int(first), int(last)+1):
evt.data["join_player"](cli, str(i), chan, forced=True, who=nick)
user = users._add(wrapper.source.client, nick=str(i)) # FIXME
channels.Dummy.users.add(user) # keep a strong reference to fake users (this will be removed eventually)
evt.data["join_player"](var, type(wrapper)(user, wrapper.target), forced=True, who=wrapper.source)
continue
if not tojoin:
continue
@ -1214,23 +1198,23 @@ def fjoin(cli, nick, chan, rest):
if tojoin.lower() not in ull or not var.USERS[ul[ull.index(tojoin.lower())]]["inchan"]:
if not is_fake_nick(tojoin) or not botconfig.DEBUG_MODE:
if not noticed: # important
cli.msg(chan, nick+messages["fjoin_in_chan"])
wrapper.send(wrapper.source.nick+messages["fjoin_in_chan"])
noticed = True
continue
if not is_fake_nick(tojoin):
tojoin = ul[ull.index(tojoin.lower())].strip()
if not botconfig.DEBUG_MODE and var.ACCOUNTS_ONLY:
if not users.get(tojoin).account or users.get(tojoin).account == "*":
cli.notice(nick, messages["account_not_logged_in"].format(tojoin))
wrapper.pm(messages["account_not_logged_in"].format(tojoin))
return
elif botconfig.DEBUG_MODE:
fake = True
if tojoin != botconfig.NICK:
evt.data["join_player"](cli, tojoin, chan, forced=True, who=nick)
if tojoin != users.Bot.nick:
evt.data["join_player"](var, type(wrapper)(users._add(wrapper.source.client, nick=tojoin), wrapper.target), forced=True, who=wrapper.source)
else:
cli.notice(nick, messages["not_allowed"])
wrapper.pm(messages["not_allowed"])
if fake:
cli.msg(chan, messages["fjoin_success"].format(nick, len(list_players())))
wrapper.send(messages["fjoin_success"].format(wrapper.source.nick, len(list_players())))
@cmd("fleave", "fquit", flag="A", pm=True, phases=("join", "day", "night"))
def fleave(cli, nick, chan, rest):
@ -2381,7 +2365,7 @@ def stop_game(cli, winner="", abort=False, additional_winners=None, log=True):
survived = list_players()
if not pentry["dced"]:
evt = Event("player_win", {"won": won, "iwon": iwon, "special": pentry["special"]})
evt.dispatch(cli, var, splr, rol, winner, splr in survived)
evt.dispatch(var, users._get(splr), rol, winner, splr in survived) # FIXME
won = evt.data["won"]
iwon = evt.data["iwon"]
# ensure that it is a) a list, and b) a copy (so it can't be mutated out from under us later)
@ -3757,7 +3741,7 @@ def transition_day(cli, gameid=0):
for mm in var.ROLES["matchmaker"]:
if mm not in var.MATCHMAKERS:
lovers = random.sample(pl, 2)
choose.func(cli, mm, mm, lovers[0] + " " + lovers[1], sendmsg=False)
choose.func(cli, mm, mm, lovers[0] + " " + lovers[1], sendmsg=False) # XXX: Old API
pm(cli, mm, messages["random_matchmaker"])
# Reset daytime variables
@ -4653,35 +4637,34 @@ def retract(cli, nick, chan, rest):
else:
cli.notice(nick, messages["pending_vote"])
@cmd("shoot", playing=True, silenced=True, phases=("day",))
def shoot(cli, nick, chan, rest):
@command("shoot", playing=True, silenced=True, phases=("day",))
def shoot(var, wrapper, message):
"""Use this to fire off a bullet at someone in the day if you have bullets."""
if chan != botconfig.CHANNEL:
if wrapper.target is not channels.Main:
return
if nick not in var.GUNNERS.keys():
cli.notice(nick, messages["no_gun"])
if wrapper.source.nick not in var.GUNNERS.keys():
wrapper.pm(messages["no_gun"])
return
elif not var.GUNNERS.get(nick):
cli.notice(nick, messages["no_bullets"])
elif not var.GUNNERS.get(wrapper.source.nick):
wrapper.pm(messages["no_bullets"])
return
victim = get_victim(cli, nick, re.split(" +",rest)[0], True)
victim = get_victim(wrapper.source.client, wrapper.source.nick, re.split(" +", message)[0], True)
if not victim:
return
if victim == nick:
cli.notice(nick, messages["gunner_target_self"])
if victim == wrapper.source.nick:
wrapper.pm(messages["gunner_target_self"])
return
# get actual victim
victim = choose_target(nick, victim)
victim = choose_target(wrapper.source.nick, victim)
wolfshooter = nick in list_players(var.WOLFCHAT_ROLES)
var.GUNNERS[nick] -= 1
wolfshooter = wrapper.source.nick in list_players(var.WOLFCHAT_ROLES)
var.GUNNERS[wrapper.source.nick] -= 1
rand = random.random()
if nick in var.ROLES["village drunk"]:
if wrapper.source.nick in var.ROLES["village drunk"]:
chances = var.DRUNK_GUN_CHANCES
elif nick in var.ROLES["sharpshooter"]:
elif wrapper.source.nick in var.ROLES["sharpshooter"]:
chances = var.SHARPSHOOTER_GUN_CHANCES
else:
chances = var.GUN_CHANCES
@ -4698,26 +4681,26 @@ def shoot(cli, nick, chan, rest):
if rand <= chances[0] and not (wolfshooter and wolfvictim) and not alwaysmiss:
# didn't miss or suicide and it's not a wolf shooting another wolf
cli.msg(chan, messages["shoot_success"].format(nick, victim))
wrapper.send(messages["shoot_success"].format(wrapper.source.nick, victim))
an = "n" if victimrole.startswith(("a", "e", "i", "o", "u")) else ""
if realrole in var.WOLF_ROLES:
if var.ROLE_REVEAL == "on":
cli.msg(chan, messages["gunner_victim_wolf_death"].format(victim,an, victimrole))
wrapper.send(messages["gunner_victim_wolf_death"].format(victim,an, victimrole))
else: # off and team
cli.msg(chan, messages["gunner_victim_wolf_death_no_reveal"].format(victim))
if not del_player(cli, victim, killer_role = get_role(nick)):
wrapper.send(messages["gunner_victim_wolf_death_no_reveal"].format(victim))
if not del_player(wrapper.source.client, victim, killer_role=get_role(wrapper.source.nick)):
return
elif random.random() <= chances[3]:
accident = "accidentally "
if nick in var.ROLES["sharpshooter"]:
if wrapper.source.nick in var.ROLES["sharpshooter"]:
accident = "" # it's an accident if the sharpshooter DOESN'T headshot :P
cli.msg(chan, messages["gunner_victim_villager_death"].format(victim, accident))
wrapper.send(messages["gunner_victim_villager_death"].format(victim, accident))
if var.ROLE_REVEAL in ("on", "team"):
cli.msg(chan, messages["gunner_victim_role"].format(an, victimrole))
if not del_player(cli, victim, killer_role = get_role(nick)):
wrapper.send(messages["gunner_victim_role"].format(an, victimrole))
if not del_player(wrapper.source.client, victim, killer_role=get_role(wrapper.source.nick)):
return
else:
cli.msg(chan, messages["gunner_victim_injured"].format(victim))
wrapper.send(messages["gunner_victim_injured"].format(victim))
var.WOUNDED.add(victim)
lcandidates = list(var.VOTES.keys())
for cand in lcandidates: # remove previous vote
@ -4726,16 +4709,16 @@ def shoot(cli, nick, chan, rest):
if not var.VOTES.get(cand):
del var.VOTES[cand]
break
chk_decision(cli)
chk_win(cli)
chk_decision(wrapper.source.client)
chk_win(wrapper.source.client)
elif rand <= chances[0] + chances[1]:
cli.msg(chan, messages["gunner_miss"].format(nick))
wrapper.send(messages["gunner_miss"].format(wrapper.source.nick))
else:
if var.ROLE_REVEAL in ("on", "team"):
cli.msg(chan, messages["gunner_suicide"].format(nick, get_reveal_role(nick)))
wrapper.send(messages["gunner_suicide"].format(wrapper.source.nick, get_reveal_role(wrapper.source.nick)))
else:
cli.msg(chan, messages["gunner_suicide_no_reveal"].format(nick))
if not del_player(cli, nick, killer_role = "villager"): # blame explosion on villager's shoddy gun construction or something
wrapper.send(messages["gunner_suicide_no_reveal"].format(wrapper.source.nick))
if not del_player(wrapper.source.client, wrapper.source.nick, killer_role="villager"): # blame explosion on villager's shoddy gun construction or something
return # Someone won.
def is_safe(nick, victim): # helper function
@ -4788,7 +4771,7 @@ def consecrate(cli, nick, chan, rest):
# (for example, if there was a role that could raise corpses as undead somethings, this would prevent that from working)
# regardless if this has any actual effect or not, it still removes the priest from being able to vote
from src.roles import vengefulghost
if victim in vengefulghost.GHOSTS:
if users._get(victim) in vengefulghost.GHOSTS:
var.SILENCED.add(victim)
var.CONSECRATING.add(nick)
@ -4967,7 +4950,7 @@ def hvisit(cli, nick, chan, rest):
return
if nick == victim: # Staying home (same as calling pass, so call pass)
pass_cmd.func(cli, nick, chan, "")
pass_cmd.func(cli, nick, chan, "") # XXX: Old API
return
else:
victim = choose_target(nick, victim)
@ -5108,7 +5091,7 @@ def bite_cmd(cli, nick, chan, rest):
@cmd("pass", chan=False, pm=True, playing=True, phases=("night",),
roles=("harlot", "turncoat", "warlock", "succubus"))
def pass_cmd(cli, nick, chan, rest):
def pass_cmd(cli, nick, chan, rest): # XXX: hvisit (3 functions above this one) also needs updating alongside this
"""Decline to use your special power for that night."""
nickrole = get_role(nick)
@ -5174,7 +5157,7 @@ def change_sides(cli, nick, chan, rest, sendmsg=True):
@cmd("choose", chan=False, pm=True, playing=True, phases=("night",), roles=("matchmaker",))
@cmd("match", chan=False, pm=True, playing=True, phases=("night",), roles=("matchmaker",))
def choose(cli, nick, chan, rest, sendmsg=True):
def choose(cli, nick, chan, rest, sendmsg=True): # XXX: transition_day also needs updating alongside this one
"""Select two players to fall in love. You may select yourself as one of the lovers."""
if not var.FIRST_NIGHT:
return
@ -7069,7 +7052,7 @@ def myrole(cli, nick, chan, rest):
pm(cli, nick, message)
@cmd("aftergame", "faftergame", flag="D", raw_nick=True, pm=True)
def aftergame(cli, rawnick, chan, rest):
def aftergame(cli, rawnick, chan, rest): # XXX: lastgame (just below this one) and sighandler (top of file) also need updating alongside this one
"""Schedule a command to be run after the current game."""
nick = parse_nick(rawnick)[0]
if not rest.strip():
@ -7116,7 +7099,7 @@ def flastgame(cli, rawnick, chan, rest):
var.ADMIN_TO_PING = nick
if rest.strip():
aftergame.func(cli, rawnick, botconfig.CHANNEL, rest)
aftergame.func(cli, rawnick, botconfig.CHANNEL, rest) # XXX: Old API
@cmd("gamestats", "gstats", pm=True)
def game_stats(cli, nick, chan, rest):
@ -7159,7 +7142,7 @@ def game_stats(cli, nick, chan, rest):
# Attempt to find game stats for the given game size
reply(cli, nick, chan, db.get_game_stats(gamemode, gamesize))
@cmd("playerstats", "pstats", "player", "p", pm=True)
@cmd("playerstats", "pstats", "player", "p", pm=True) # XXX: mystats (just after this) needs updating along this one
def player_stats(cli, nick, chan, rest):
"""Gets the stats for the given player and role or a list of role totals if no role is given."""
if (chan != nick and var.LAST_PSTATS and var.PSTATS_RATE_LIMIT and
@ -7225,49 +7208,48 @@ def my_stats(cli, nick, chan, rest):
player_stats.func(cli, nick, chan, " ".join([nick] + rest))
# Called from !game and !join, used to vote for a game mode
def vote_gamemode(cli, nick, chan, gamemode, doreply):
def vote_gamemode(var, wrapper, gamemode, doreply):
if var.FGAMED:
if doreply:
cli.notice(nick, messages["admin_forced_game"])
wrapper.pm(messages["admin_forced_game"])
return
if gamemode not in var.GAME_MODES.keys():
match, _ = complete_match(gamemode, var.GAME_MODES.keys() - ["roles", "villagergame"] - var.DISABLED_GAMEMODES)
if not match:
if doreply:
cli.notice(nick, messages["invalid_mode_no_list"].format(gamemode))
wrapper.pm(messages["invalid_mode_no_list"].format(gamemode))
return
gamemode = match
if gamemode != "roles" and gamemode != "villagergame" and gamemode not in var.DISABLED_GAMEMODES:
if var.GAMEMODE_VOTES.get(nick) == gamemode:
cli.notice(nick, messages["already_voted_game"].format(gamemode))
if var.GAMEMODE_VOTES.get(wrapper.source.nick) == gamemode:
wrapper.pm(messages["already_voted_game"].format(gamemode))
else:
var.GAMEMODE_VOTES[nick] = gamemode
cli.msg(chan, messages["vote_game_mode"].format(nick, gamemode))
var.GAMEMODE_VOTES[wrapper.source.nick] = gamemode
wrapper.send(messages["vote_game_mode"].format(wrapper.source.nick, gamemode))
else:
if doreply:
cli.notice(nick, messages["vote_game_fail"])
wrapper.pm(messages["vote_game_fail"])
@cmd("game", playing=True, phases=("join",))
def game(cli, nick, chan, rest):
@command("game", playing=True, phases=("join",))
def game(var, wrapper, message):
"""Vote for a game mode to be picked."""
if rest:
vote_gamemode(cli, nick, chan, rest.lower().split()[0], True)
if message:
vote_gamemode(var, wrapper, message.lower().split()[0], doreply=True)
else:
gamemodes = ", ".join("\u0002{0}\u0002".format(gamemode) if len(list_players()) in range(var.GAME_MODES[gamemode][1],
var.GAME_MODES[gamemode][2]+1) else gamemode for gamemode in var.GAME_MODES.keys() if gamemode != "roles" and
gamemode != "villagergame" and gamemode not in var.DISABLED_GAMEMODES)
cli.notice(nick, messages["no_mode_specified"] + gamemodes)
wrapper.pm(messages["no_mode_specified"] + gamemodes)
return
@cmd("games", "modes", pm=True)
def show_modes(cli, nick, chan, rest):
@command("games", "modes", pm=True)
def show_modes(var, wrapper, message):
"""Show the available game modes."""
msg = messages["available_modes"]
modes = "\u0002, \u0002".join(sorted(var.GAME_MODES.keys() - {"roles", "villagergame"} - var.DISABLED_GAMEMODES))
reply(cli, nick, chan, msg + modes + "\u0002", private=True)
wrapper.pm("{0}{1}\u0002".format(messages["available_modes"], modes))
def game_help(args=""):
return (messages["available_mode_setters_help"] +
@ -7359,52 +7341,53 @@ def update(cli, nick, chan, rest):
if ret:
restart_program.caller(cli, nick, chan, "Updating bot")
@cmd("send", "fsend", flag="F", pm=True)
def fsend(cli, nick, chan, rest):
@command("send", "fsend", flag="F", pm=True)
def fsend(var, wrapper, message):
"""Forcibly send raw IRC commands to the server."""
cli.send(rest)
wrapper.source.client.send(message)
def _say(cli, raw_nick, rest, command, action=False):
(nick, _, ident, host) = parse_nick(raw_nick)
def _say(wrapper, rest, cmd, action=False):
rest = rest.split(" ", 1)
if len(rest) < 2:
pm(cli, nick, messages["fsend_usage"].format(
botconfig.CMD_CHAR, command))
wrapper.pm(messages["fsend_usage"].format(botconfig.CMD_CHAR, cmd))
return
(target, message) = rest
target, message = rest
if not is_admin(nick, ident, host):
if not users.exists(nick):
pm(cli, nick, messages["wrong_channel"].format(
botconfig.CHANNEL))
if target.startswith(tuple(hooks.Features["CHANTYPES"])):
targ = channels.get(target, allow_none=True)
else:
targ = users._get(target, allow_multiple=True) # FIXME
if len(targ) == 1:
targ = targ[0]
else:
targ = None
return
if rest[0] != botconfig.CHANNEL:
pm(cli, nick, messages["invalid_fsend_permissions"])
if targ is None:
targ = IRCClient(target, wrapper.source.client)
if not wrapper.source.is_admin():
if targ is not channels.Main:
wrapper.pm(messages["invalid_fsend_permissions"])
return
if action:
message = "\u0001ACTION {0}\u0001".format(message)
cli.send("PRIVMSG {0} :{1}".format(target, message))
targ.send(message, privmsg=True)
@cmd("say", "fsay", flag="s", raw_nick=True, pm=True)
def fsay(cli, raw_nick, chan, rest):
@command("say", "fsay", flag="s", pm=True)
def fsay(var, wrapper, message):
"""Talk through the bot as a normal message."""
_say(cli, raw_nick, rest, "say")
_say(wrapper, message, "say")
@cmd("act", "do", "me", "fact", "fdo", "fme", flag="s", raw_nick=True, pm=True)
def fact(cli, raw_nick, chan, rest):
@command("act", "do", "me", "fact", "fdo", "fme", flag="s", pm=True)
def fact(var, wrapper, message):
"""Act through the bot as an action."""
_say(cli, raw_nick, rest, "act", action=True)
_say(wrapper, message, "act", action=True)
def can_run_restricted_cmd(nick):
def can_run_restricted_cmd(user):
# if allowed in normal games, restrict it so that it can only be used by dead players and
# non-players (don't allow active vengeful ghosts either).
# also don't allow in-channel (e.g. make it pm only)
@ -7414,35 +7397,34 @@ def can_run_restricted_cmd(nick):
pl = list_participants()
if nick in pl:
if user.nick in pl: # FIXME: Need to update this once list_participants() holds User instances
return False
if users.exists(nick) and users.get(nick).account in [users.get(player).account for player in pl if users.exists(player)]:
if user.account in [users._get(player).account for player in pl]: # FIXME
return False
hostmask = users.get(nick).ident + "@" + users.get(nick).host
if users.exists(nick) and hostmask in [users.get(player).ident + "@" + users.get(player).host for player in pl if users.exists(player)]:
if user.userhost in [users._get(player).userhost for player in pl]: # FIXME
return False
return True
@cmd("spectate", "fspectate", flag="A", pm=True, phases=("day", "night"))
def fspectate(cli, nick, chan, rest):
@command("spectate", "fspectate", flag="A", pm=True, phases=("day", "night"))
def fspectate(var, wrapper, message):
"""Spectate wolfchat or deadchat."""
if not can_run_restricted_cmd(nick):
if not can_run_restricted_cmd(wrapper.source):
pm(cli, nick, messages["fspectate_restricted"])
return
params = rest.split(" ")
params = message.split(" ")
on = "on"
if not len(params):
pm(cli, nick, messages["fspectate_help"])
wrapper.pm(messages["fspectate_help"])
return
elif len(params) > 1:
on = params[1].lower()
what = params[0].lower()
if what not in ("wolfchat", "deadchat") or on not in ("on", "off"):
pm(cli, nick, messages["fspectate_help"])
wrapper.pm(messages["fspectate_help"])
return
if on == "off":
@ -7450,54 +7432,50 @@ def fspectate(cli, nick, chan, rest):
var.SPECTATING_WOLFCHAT.discard(nick)
else:
var.SPECTATING_DEADCHAT.discard(nick)
pm(cli, nick, messages["fspectate_off"].format(what))
wrapper.pm(messages["fspectate_off"].format(what))
else:
players = []
if what == "wolfchat":
var.SPECTATING_WOLFCHAT.add(nick)
players = (p for p in list_players() if in_wolflist(p, p))
elif var.ENABLE_DEADCHAT:
if nick in var.DEADCHAT_PLAYERS:
pm(cli, nick, messages["fspectate_in_deadchat"])
if wrapper.source.nick in var.DEADCHAT_PLAYERS: # FIXME
wrapper.pm(messages["fspectate_in_deadchat"])
return
var.SPECTATING_DEADCHAT.add(nick)
players = var.DEADCHAT_PLAYERS
else:
pm(cli, nick, messages["fspectate_deadchat_disabled"])
wrapper.pm(messages["fspectate_deadchat_disabled"])
return
pm(cli, nick, messages["fspectate_on"].format(what))
pm(cli, nick, "People in {0}: {1}".format(what, ", ".join(players)))
wrapper.pm(messages["fspectate_on"].format(what))
wrapper.pm("People in {0}: {1}".format(what, ", ".join(players)))
before_debug_mode_commands = list(COMMANDS.keys())
if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
@cmd("eval", owner_only=True, pm=True)
def pyeval(cli, nick, chan, rest):
@command("eval", owner_only=True, pm=True)
def pyeval(var, wrapper, message):
"""Evaluate a Python expression."""
try:
a = str(eval(rest))
if len(a) < 500:
cli.msg(chan, a)
else:
cli.msg(chan, a[:500])
wrapper.send(str(eval(message))[:500])
except Exception as e:
cli.msg(chan, "{e.__class__.__name__}: {e}".format(e=e))
wrapper.send("{e.__class__.__name__}: {e}".format(e=e))
@cmd("exec", owner_only=True, pm=True)
def py(cli, nick, chan, rest):
@command("exec", owner_only=True, pm=True)
def py(var, wrapper, message):
"""Execute arbitrary Python code."""
try:
exec(rest)
exec(message)
except Exception as e:
cli.msg(chan, "{e.__class__.__name__}: {e}".format(e=e))
wrapper.send("{e.__class__.__name__}: {e}".format(e=e))
@cmd("revealroles", flag="a", pm=True, phases=("day", "night"))
def revealroles(cli, nick, chan, rest):
@command("revealroles", flag="a", pm=True, phases=("day", "night"))
def revealroles(var, wrapper, message):
"""Reveal role information."""
if not can_run_restricted_cmd(nick):
reply(cli, nick, chan, messages["temp_invalid_perms"], private=True)
if not can_run_restricted_cmd(wrapper.source):
wrapper.pm(messages["temp_invalid_perms"])
return
output = []
@ -7523,7 +7501,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
if var.TURNCOATS[nickname][0] != "none" else "not currently on any side")
evt = Event("revealroles_role", {"special_case": special_case})
evt.dispatch(cli, var, nickname, role)
evt.dispatch(var, wrapper, nickname, role)
special_case = evt.data["special_case"]
if not evt.prevent_default and nickname not in var.ORIGINAL_ROLES[role] and role not in var.TEMPLATE_RESTRICTIONS:
@ -7568,12 +7546,12 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
output.append("\u0002charmed players\u0002: {0}".format(", ".join(var.CHARMED | var.TOBECHARMED)))
evt = Event("revealroles", {"output": output})
evt.dispatch(cli, var)
evt.dispatch(var, wrapper)
if botconfig.DEBUG_MODE:
cli.msg(chan, break_long_message(output, " | "))
wrapper.send(*output, sep=" | ")
else:
reply(cli, nick, chan, break_long_message(output, " | "), private=True)
wrapper.pm(*output, sep=" | ")
@cmd("fgame", flag="d", raw_nick=True, phases=("join",))
@ -7661,7 +7639,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
if fn.chan:
fn.caller(cli, user, chan, " ".join(rst))
else:
fn.caller(cli, user, user, " ".join(rst))
fn.caller(cli, user, users.Bot.nick, " ".join(rst))
cli.msg(chan, messages["operation_successful"])
else:
cli.msg(chan, messages["command_not_found"])
@ -7701,7 +7679,7 @@ if botconfig.DEBUG_MODE or botconfig.ALLOWED_NORMAL_MODE_COMMANDS:
if fn.chan:
fn.caller(cli, user, chan, " ".join(rst))
else:
fn.caller(cli, user, user, " ".join(rst))
fn.caller(cli, user, users.Bot.nick, " ".join(rst))
cli.msg(chan, messages["operation_successful"])
else:
cli.msg(chan, messages["command_not_found"])