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.", "admin_forced_game": "A game mode has already been forced by an admin.",
"vote_game_fail": "You can't vote for that game mode.", "vote_game_fail": "You can't vote for that game mode.",
"fsend_usage": "Usage: {0}{1} <target> <message>", "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.", "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.", "temp_invalid_perms": "You are not allowed to use that command right now.",
"fgame_success": "\u0002{0}\u0002 has changed the game settings successfully.", "fgame_success": "\u0002{0}\u0002 has changed the game settings successfully.",

View File

@ -27,7 +27,13 @@ class _States(Enum):
def predicate(name): def predicate(name):
return not name.startswith(tuple(Features["CHANTYPES"])) 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=""): def add(name, cli, key=""):
"""Add and return a new channel, or an existing one if it exists.""" """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 botconfig
import src.settings as var import src.settings as var
from src.dispatcher import MessageDispatcher
from src.utilities import * from src.utilities import *
from src import channels, users, logger, errlog, events
from src.messages import messages from src.messages import messages
from src import channels, users, logger, errlog, events
adminlog = logger.logger("audit.log") adminlog = logger.logger("audit.log")
@ -181,6 +182,121 @@ class handle_error:
with print_traceback(): with print_traceback():
return self.func(*args, **kwargs) 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: class cmd:
def __init__(self, *cmds, raw_nick=False, flag=None, owner_only=False, def __init__(self, *cmds, raw_nick=False, flag=None, owner_only=False,
chan=True, pm=False, playing=False, silenced=False, chan=True, pm=False, playing=False, silenced=False,
@ -222,8 +338,11 @@ class cmd:
return self return self
@handle_error @handle_error
def caller(self, *args): def caller(self, cli, rawnick, chan, rest):
largs = list(args) 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 cli, rawnick, chan, rest = largs
nick, mode, ident, host = parse_nick(rawnick) 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.utilities import *
from src.messages import messages from src.messages import messages
from src.decorators import handle_error 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 game_mode(name, minp, maxp, likelihood = 0):
def decor(c): def decor(c):
@ -1186,23 +1186,24 @@ class MaelstromMode(GameMode):
if not var.ACCOUNTS_ONLY: if not var.ACCOUNTS_ONLY:
self.DEAD_HOSTS.add(var.USERS[nick]["host"].lower()) self.DEAD_HOSTS.add(var.USERS[nick]["host"].lower())
def on_join(self, evt, cli, var, nick, chan, rest, forced=False): def on_join(self, evt, var, wrapper, message, forced=False):
if var.PHASE != "day" or (nick != chan and chan != botconfig.CHANNEL): if var.PHASE != "day" or (wrapper.public and wrapper.target is not channels.Main):
return return
if (irc_lower(nick) in (irc_lower(x) for x in var.ALL_PLAYERS) or temp = wrapper.source.lower()
irc_lower(var.USERS[nick]["account"]) in self.DEAD_ACCOUNTS or if (temp.nick in (irc_lower(x) for x in var.ALL_PLAYERS) or # FIXME
var.USERS[nick]["host"].lower() in self.DEAD_HOSTS): temp.account in self.DEAD_ACCOUNTS or
cli.notice(nick, messages["maelstrom_dead"]) temp.host in self.DEAD_HOSTS):
wrapper.pm(messages["maelstrom_dead"])
return return
if not forced and evt.data["join_player"](cli, nick, botconfig.CHANNEL, sanity=False): if not forced and evt.data["join_player"](var, type(wrapper)(wrapper.source, channels.Main), sanity=False):
self._on_join(cli, var, nick, chan) self._on_join(var, wrapper)
evt.prevent_default = True evt.prevent_default = True
elif forced: elif forced:
# in fjoin, handle this differently # in fjoin, handle this differently
jp = evt.data["join_player"] 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) role = random.choice(self.roles)
lpl = len(list_players()) + 1 lpl = len(list_players()) + 1
@ -1232,31 +1233,30 @@ class MaelstromMode(GameMode):
elif role == "succubus": elif role == "succubus":
lsuccubi += 1 lsuccubi += 1
if self.chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, cli, end_game=False): if self.chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, ltraitors, lpipers, lsuccubi, 0, wrapper.client, end_game=False):
return self._on_join(cli, var, nick, chan) return self._on_join(var, wrapper)
var.ROLES[role].add(nick) var.ROLES[role].add(wrapper.source.nick)
var.ORIGINAL_ROLES[role].add(nick) var.ORIGINAL_ROLES[role].add(wrapper.source.nick)
var.FINAL_ROLES[nick] = role var.FINAL_ROLES[wrapper.source.nick] = role
var.LAST_SAID_TIME[nick] = datetime.now() var.LAST_SAID_TIME[wrapper.source.nick] = datetime.now()
if nick in var.USERS: if wrapper.source.nick in var.USERS:
var.PLAYERS[nick] = var.USERS[nick] var.PLAYERS[wrapper.source.nick] = var.USERS[wrapper.source.nick]
if role == "doctor": if role == "doctor":
lpl = len(list_players()) 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 # let them know their role
# FIXME: this is fugly
from src.decorators import COMMANDS 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 they're a wolfchat role, alert the other wolves
if role in var.WOLFCHAT_ROLES: 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 # TODO: make this part of !myrole instead, no reason we can't give out wofllist in that
wolves = list_players(var.WOLFCHAT_ROLES) wolves = list_players(var.WOLFCHAT_ROLES)
pl = list_players() pl = list_players()
random.shuffle(pl) random.shuffle(pl)
pl.remove(nick) pl.remove(wrapper.source.nick)
for i, player in enumerate(pl): for i, player in enumerate(pl):
prole = get_role(player) prole = get_role(player)
if prole in var.WOLFCHAT_ROLES: if prole in var.WOLFCHAT_ROLES:
@ -1266,7 +1266,7 @@ class MaelstromMode(GameMode):
pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole) pl[i] = "\u0002{0}\u0002 ({1}{2})".format(player, cursed, prole)
elif player in var.ROLES["cursed villager"]: elif player in var.ROLES["cursed villager"]:
pl[i] = player + " (cursed)" 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): def role_attribution(self, evt, cli, var, chk_win_conditions, villagers):
self.chk_win_conditions = chk_win_conditions 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 if notice and "!" not in rawnick or not rawnick: # server notice; we don't care about those
return 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 return
if (notice and ((not users.equals(chan, botconfig.NICK) and not botconfig.ALLOW_NOTICE_COMMANDS) or if (notice and ((not users.equals(chan, users.Bot.nick) and not botconfig.ALLOW_NOTICE_COMMANDS) or
(users.equals(chan, botconfig.NICK) and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))): (users.equals(chan, users.Bot.nick) and not botconfig.ALLOW_PRIVATE_NOTICE_COMMANDS))):
return # not allowed in settings return # not allowed in settings
if users.equals(chan, botconfig.NICK):
chan = users.parse_rawnick_as_dict(rawnick)["nick"]
for fn in decorators.COMMANDS[""]: for fn in decorators.COMMANDS[""]:
fn.caller(cli, rawnick, chan, msg) fn.caller(cli, rawnick, chan, msg)
phase = var.PHASE phase = var.PHASE
for x in list(decorators.COMMANDS.keys()): 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 break # channel message but no prefix; ignore
if msg.lower().startswith(botconfig.CMD_CHAR+x): if msg.lower().startswith(botconfig.CMD_CHAR+x):
h = msg[len(x)+len(botconfig.CMD_CHAR):] 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"]) pm(cli, nick, messages["retracted_kill"])
@event_listener("player_win") @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": if role != "dullahan":
return return
alive = set(list_players()) alive = set(list_players())
if nick in var.ENTRANCED: if user.nick in var.ENTRANCED:
alive -= var.ROLES["succubus"] alive -= var.ROLES["succubus"]
if not TARGETS[nick] & alive: if not TARGETS[user.nick] & alive:
evt.data["iwon"] = True evt.data["iwon"] = True
@event_listener("del_player") @event_listener("del_player")
@ -222,7 +222,7 @@ def on_myrole(evt, cli, var, nick):
evt.data["messages"].append(messages["dullahan_targets_dead"]) evt.data["messages"].append(messages["dullahan_targets_dead"])
@event_listener("revealroles_role") @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: if role == "dullahan" and nickname in TARGETS:
targets = TARGETS[nickname] - var.DEAD targets = TARGETS[nickname] - var.DEAD
if targets: 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("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) @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.""" """Give a totem to a player."""
victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True) victim = get_victim(cli, nick, re.split(" +",rest)[0], False, True)
if not victim: 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) 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") @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: if rol == "crazed shaman" and survived and not winner.startswith("@") and singular(winner) not in var.WIN_STEALER_ROLES:
evt.data["iwon"] = True evt.data["iwon"] = True
@ -320,7 +320,7 @@ def on_transition_day_begin(evt, cli, var):
ps.remove(succubus) ps.remove(succubus)
if ps: if ps:
target = random.choice(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: else:
LASTGIVEN[shaman] = None LASTGIVEN[shaman] = None
elif shaman not in SHAMANS: 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])) evt.data["messages"].append(messages["totem_simple"].format(TOTEMS[nick]))
@event_listener("revealroles_role") @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 role in var.TOTEM_ORDER and nickname in TOTEMS:
if nickname in SHAMANS: if nickname in SHAMANS:
evt.data["special_case"].append("giving {0} totem to {1}".format(TOTEMS[nickname], SHAMANS[nickname][0])) 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 botconfig
import src.settings as var import src.settings as var
from src.utilities import * from src.utilities import *
from src import debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.decorators import cmd, event_listener from src.decorators import command, event_listener
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event

View File

@ -4,79 +4,79 @@ from collections import defaultdict
import src.settings as var import src.settings as var
from src.utilities import * from src.utilities import *
from src import debuglog, errlog, plog from src import channels, users, debuglog, errlog, plog
from src.decorators import cmd, event_listener from src.decorators import command, event_listener
from src.messages import messages from src.messages import messages
from src.events import Event from src.events import Event
KILLS = {} # type: Dict[str, str] 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 # temporary holding variable, only non-empty during transition_day
# as such, no need to track nick changes, etc. with it # as such, no need to track nick changes, etc. with it
drivenoff = {} # type: Dict[str, str] drivenoff = {} # type: Dict[str, str]
@cmd("kill", chan=False, pm=True, playing=False, silenced=True, phases=("night",), nicks=GHOSTS) @command("kill", chan=False, pm=True, playing=False, silenced=True, phases=("night",), users=GHOSTS)
def vg_kill(cli, nick, chan, rest): def vg_kill(var, wrapper, message):
"""Take revenge on someone each night after you die.""" """Take revenge on someone each night after you die."""
if GHOSTS[nick][0] == "!": if GHOSTS[wrapper.source][0] == "!":
return 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: if not victim:
return return
if victim == nick: if victim == wrapper.source.nick:
pm(cli, nick, messages["player_dead"]) wrapper.pm(messages["player_dead"])
return return
wolves = list_players(var.WOLFTEAM_ROLES) wolves = list_players(var.WOLFTEAM_ROLES)
if GHOSTS[nick] == "wolves" and victim not in wolves: if GHOSTS[wrapper.source] == "wolves" and victim not in wolves:
pm(cli, nick, messages["vengeful_ghost_wolf"]) wrapper.pm(messages["vengeful_ghost_wolf"])
return return
elif GHOSTS[nick] == "villagers" and victim in wolves: elif GHOSTS[wrapper.source] == "villagers" and victim in wolves:
pm(cli, nick, messages["vengeful_ghost_villager"]) wrapper.pm(messages["vengeful_ghost_villager"])
return return
orig = victim orig = victim
evt = Event("targeted_command", {"target": victim, "misdirection": True, "exchange": False}) 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: if evt.prevent_default:
return return
victim = evt.data["target"] victim = evt.data["target"]
KILLS[nick] = victim KILLS[wrapper.source.nick] = victim
msg = messages["wolf_target"].format(orig) 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))) debuglog("{0} ({1}) KILL: {2} ({3})".format(wrapper.source.nick, get_role(wrapper.source.nick), victim, get_role(victim)))
chk_nightdone(cli) chk_nightdone(wrapper.source.client)
@cmd("retract", "r", chan=False, pm=True, playing=False, phases=("night",)) @command("retract", "r", chan=False, pm=True, playing=False, phases=("night",))
def vg_retract(cli, nick, chan, rest): def vg_retract(var, wrapper, message):
"""Removes a vengeful ghost's kill selection.""" """Removes a vengeful ghost's kill selection."""
if nick not in GHOSTS: if wrapper.source not in GHOSTS:
return return
if nick in KILLS: if wrapper.source.nick in KILLS:
del KILLS[nick] del KILLS[wrapper.source.nick]
pm(cli, nick, messages["retracted_kill"]) wrapper.pm(messages["retracted_kill"])
@event_listener("list_participants") @event_listener("list_participants")
def on_list_participants(evt, var): 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]) evt.data["pl"].extend([p for p in drivenoff])
@event_listener("player_win", priority=1) @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 # alive VG winning is handled in villager.py
# extending VG to work with new teams can be done by registering # extending VG to work with new teams can be done by registering
# a listener at priority > 1, importing src.roles.vengefulghost, # a listener at priority > 1, importing src.roles.vengefulghost,
# and checking if the nick is in GHOSTS. # and checking if the user is in GHOSTS.
if nick in GHOSTS: if user in GHOSTS:
evt.data["special"].append("vg activated") evt.data["special"].append("vg activated")
against = GHOSTS[nick] against = GHOSTS[user]
if GHOSTS[nick][0] == "!": if against[0] == "!":
evt.data["special"].append("vg driven off") evt.data["special"].append("vg driven off")
against = against[1:] against = against[1:]
if against == "villagers" and winner == "wolves": 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] del KILLS[h]
# extending VG to work with new teams can be done by registering a listener # extending VG to work with new teams can be done by registering a listener
# at priority < 6, importing src.roles.vengefulghost, and setting # at priority < 6, importing src.roles.vengefulghost, and setting
# GHOSTS[nick] to something; if that is done then this logic is not run. # GHOSTS[user] to something; if that is done then this logic is not run.
if death_triggers and nickrole == "vengeful ghost" and nick not in GHOSTS: 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: if evt.params.killer_role in var.WOLFTEAM_ROLES:
GHOSTS[nick] = "wolves" GHOSTS[user] = "wolves"
else: else:
GHOSTS[nick] = "villagers" GHOSTS[user] = "villagers"
pm(cli, nick, messages["vengeful_turn"].format(GHOSTS[nick])) user.send(messages["vengeful_turn"].format(GHOSTS[user]))
debuglog(nick, "(vengeful ghost) TRIGGER", GHOSTS[nick]) debuglog(nick, "(vengeful ghost) TRIGGER", GHOSTS[user])
@event_listener("rename_player") @event_listener("rename_player")
def on_rename(evt, cli, var, prefix, nick): def on_rename(evt, cli, var, prefix, nick):
@ -118,8 +119,6 @@ def on_rename(evt, cli, var, prefix, nick):
KILLS.update(kvp) KILLS.update(kvp)
if prefix in KILLS: if prefix in KILLS:
del KILLS[prefix] del KILLS[prefix]
if prefix in GHOSTS:
GHOSTS[nick] = GHOSTS.pop(prefix)
@event_listener("night_acted") @event_listener("night_acted")
def on_acted(evt, cli, var, nick, sender): 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)) wolves = set(list_players(var.WOLFTEAM_ROLES))
villagers = set(list_players()) - wolves villagers = set(list_players()) - wolves
for ghost, target in GHOSTS.items(): for ghost, target in GHOSTS.items():
if target[0] == "!" or ghost in var.SILENCED: if target[0] == "!" or ghost.nick in var.SILENCED:
continue continue
if ghost not in KILLS: if ghost.nick not in KILLS:
choice = set() choice = set()
if target == "wolves": if target == "wolves":
choice = wolves.copy() choice = wolves.copy()
elif target == "villagers": elif target == "villagers":
choice = villagers.copy() choice = villagers.copy()
evt = Event("vg_kill", {"pl": choice}) evt = Event("vg_kill", {"pl": choice})
evt.dispatch(cli, var, ghost, target) evt.dispatch(var, ghost, target)
choice = evt.data["pl"] choice = evt.data["pl"]
# roll this into the above event once succubus is split off # 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"] choice -= var.ROLES["succubus"]
if choice: if choice:
KILLS[ghost] = random.choice(list(choice)) KILLS[ghost.nick] = random.choice(list(choice))
@event_listener("transition_day", priority=2) @event_listener("transition_day", priority=2)
def on_transition_day(evt, cli, var): 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) @event_listener("transition_day", priority=3.01)
def on_transition_day3(evt, cli, var): def on_transition_day3(evt, cli, var):
for k, d in list(KILLS.items()): 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].remove(k)
evt.data["killers"][d].insert(0, k) evt.data["killers"][d].insert(0, k)
@event_listener("transition_day", priority=6.01) @event_listener("transition_day", priority=6.01)
def on_transition_day6(evt, cli, var): def on_transition_day6(evt, cli, var):
for k, d in list(KILLS.items()): 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].remove(k)
evt.data["killers"][d].insert(0, k) evt.data["killers"][d].insert(0, k)
# important, otherwise our del_player listener messages the vg # important, otherwise our del_player listener messages the vg
del KILLS[k] 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): def on_retribution_kill(evt, cli, var, victim, orig_target):
t = evt.data["target"] t = evt.data["target"]
if t in GHOSTS: if users._get(t) in GHOSTS:
drivenoff[t] = GHOSTS[t] drivenoff[t] = GHOSTS[users._get(t)]
GHOSTS[t] = "!" + GHOSTS[t] GHOSTS[users._get(t)] = "!" + GHOSTS[users._get(t)]
evt.data["message"].append(messages["totem_banish"].format(victim, t)) evt.data["message"].append(messages["totem_banish"].format(victim, t))
evt.data["target"] = None evt.data["target"] = None
@event_listener("get_participant_role") @event_listener("get_participant_role")
def on_get_participant_role(evt, var, nick): def on_get_participant_role(evt, var, nick):
if nick in GHOSTS: if users._get(nick) in GHOSTS: # FIXME
if nick in drivenoff: if nick in drivenoff:
against = drivenoff[nick] against = drivenoff[nick]
else: else:
against = GHOSTS[nick] against = GHOSTS[users._get(nick)]
if against == "villagers": if against == "villagers":
evt.data["role"] = "wolf" evt.data["role"] = "wolf"
elif against == "wolves": elif against == "wolves":
@ -196,7 +195,7 @@ def on_get_participant_role(evt, var, nick):
@event_listener("chk_nightdone") @event_listener("chk_nightdone")
def on_chk_nightdone(evt, cli, var): def on_chk_nightdone(evt, cli, var):
evt.data["actedcount"] += len(KILLS) 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) @event_listener("transition_night_end", priority=2)
def on_transition_night_end(evt, cli, var): def on_transition_night_end(evt, cli, var):
@ -215,27 +214,28 @@ def on_transition_night_end(evt, cli, var):
random.shuffle(pl) random.shuffle(pl)
if v_ghost in var.PLAYERS and not is_user_simple(v_ghost): if not v_ghost.prefers_simple():
pm(cli, v_ghost, messages["vengeful_ghost_notify"].format(who)) v_ghost.send(messages["vengeful_ghost_notify"].format(who))
else: else:
pm(cli, v_ghost, messages["vengeful_ghost_simple"]) v_ghost.send(messages["vengeful_ghost_simple"])
pm(cli, v_ghost, who.capitalize() + ": " + ", ".join(pl)) v_ghost.send(who.capitalize() + ": " + ", ".join(pl))
debuglog("GHOST: {0} (target: {1}) - players: {2}".format(v_ghost, who, ", ".join(pl))) debuglog("GHOST: {0} (target: {1}) - players: {2}".format(v_ghost.nick, who, ", ".join(pl)))
@event_listener("myrole") @event_listener("myrole")
def on_myrole(evt, cli, var, nick): def on_myrole(evt, cli, var, nick):
if nick in GHOSTS: user = users._get(nick)
if user in GHOSTS:
evt.prevent_default = True evt.prevent_default = True
if GHOSTS[nick][0] != "!": if GHOSTS[user][0] != "!":
pm(cli, nick, messages["vengeful_role"].format(GHOSTS[nick])) user.send(messages["vengeful_role"].format(GHOSTS[user]))
@event_listener("revealroles") @event_listener("revealroles")
def on_revealroles(evt, cli, var): def on_revealroles(evt, var, wrapper):
if GHOSTS: if GHOSTS:
glist = [] glist = []
for ghost, team in GHOSTS.items(): for ghost, team in GHOSTS.items():
dead = "driven away, " if team[0] == "!" else "" 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))) evt.data["output"].append("\u0002dead vengeful ghost\u0002: {0}".format(", ".join(glist)))
@event_listener("begin_day") @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 # No listeners should register before this one
# This sets up the initial state, based on village/wolfteam/neutral affiliation # This sets up the initial state, based on village/wolfteam/neutral affiliation
@event_listener("player_win", priority=0) @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 # init won/iwon to False
evt.data["won"] = False evt.data["won"] = False
evt.data["iwon"] = 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"]) pm(cli, child, messages["child_simple"])
@event_listener("revealroles_role") @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 role == "wild child":
if nick in IDOLS: if nick in IDOLS:
evt.data["special_case"].append("picked {0} as idol".format(IDOLS[nick])) 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) 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: if new is not Bot:
try:
hash(new)
except ValueError:
pass
else:
_users.add(new) _users.add(new)
return new return new
def add(nick, **blah): # backwards-compatible API def add(nick, **blah): # backwards-compatible API
@ -246,7 +252,7 @@ class User(IRCContext):
return True return True
for hostmask in hosts: for hostmask in hosts:
if match_hostmask(hostmask, self.nick, self.ident, self.host): if self.match_hostmask(hostmask):
return True return True
return False return False
@ -268,7 +274,7 @@ class User(IRCContext):
return True return True
for hostmask in hosts: for hostmask in hosts:
if match_hostmask(hostmask, self.nick, self.ident, self.host): if self.match_hostmask(hostmask):
return True return True
except AttributeError: except AttributeError:
pass pass

View File

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