def __call__(self, *args, **kwargs): if self.func is None: func = args[0] if isinstance(func, event_listener): func = func.func self.func = handle_error(func) events.add_listener(self.event, self.func, self.priority) self.__doc__ = self.func.__doc__ return self else: return self.func(*args, **kwargs)
def connect_callback(cli): regaincount = 0 releasecount = 0 @hook("endofmotd", hookid=294) @hook("nomotd", hookid=294) def prepare_stuff(cli, prefix, *args): alog("Received end of MOTD from {0}".format(prefix)) # This callback only sets up event listeners wolfgame.connect_callback() # just in case we haven't managed to successfully auth yet if botconfig.PASS and not botconfig.SASL_AUTHENTICATION: cli.ns_identify(botconfig.USERNAME or botconfig.NICK, botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_IDENTIFY_COMMAND) channels.Main = channels.add(botconfig.CHANNEL, cli) channels.Dummy = channels.add("*", cli) if botconfig.ALT_CHANNELS: for chan in botconfig.ALT_CHANNELS.split(","): channels.add(chan, cli) if botconfig.DEV_CHANNEL: channels.Dev = channels.add(botconfig.DEV_CHANNEL, cli) if var.LOG_CHANNEL: channels.add(var.LOG_CHANNEL, cli) #if var.CHANSERV_OP_COMMAND: # TODO: Add somewhere else if needed # cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL)) users.Bot.change_nick(botconfig.NICK) if var.SERVER_PING_INTERVAL > 0: def ping_server_timer(cli): ping_server(cli) t = threading.Timer(var.SERVER_PING_INTERVAL, ping_server_timer, args=(cli, )) t.daemon = True t.start() ping_server_timer(cli) def setup_handler(evt, var, target): target.client.command_handler["privmsg"] = on_privmsg target.client.command_handler["notice"] = functools.partial( on_privmsg, notice=True) events.remove_listener("who_end", setup_handler) events.add_listener("who_end", setup_handler) def mustregain(cli, server, bot_nick, nick, msg): nonlocal regaincount if not botconfig.PASS or bot_nick == nick or regaincount > 3: return if var.NICKSERV_REGAIN_COMMAND: cli.ns_regain(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_REGAIN_COMMAND) else: cli.ns_ghost(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) # it is possible (though unlikely) that regaining the nick fails for some reason and we would loop infinitely # as such, keep track of a count of how many times we regain, and after 3 times we no longer attempt to regain nicks # Since we'd only be regaining on initial connect, this should be safe. The same trick is used below for release as well regaincount += 1 users.Bot.change_nick(botconfig.NICK) def mustrelease(cli, server, bot_nick, nick, msg): nonlocal releasecount if not botconfig.PASS or bot_nick == nick or releasecount > 3: return # prevents the bot from trying to release without a password if var.NICKSERV_RELEASE_COMMAND: cli.ns_release(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) else: cli.ns_ghost(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) releasecount += 1 users.Bot.change_nick(botconfig.NICK) @hook("unavailresource", hookid=239) @hook("nicknameinuse", hookid=239) def must_use_temp_nick(cli, *etc): users.Bot.nick += "_" users.Bot.change_nick() cli.user(botconfig.NICK, "") # TODO: can we remove this? hook.unhook(239) hook("unavailresource", hookid=240)(mustrelease) hook("nicknameinuse", hookid=241)(mustregain) request_caps = {"account-notify", "extended-join", "multi-prefix"} if botconfig.SASL_AUTHENTICATION: request_caps.add("sasl") supported_caps = set() @hook("cap") def on_cap(cli, svr, mynick, cmd, caps, star=None): if cmd == "LS": if caps == "*": # Multi-line LS supported_caps.update(star.split()) else: supported_caps.update(caps.split()) if botconfig.SASL_AUTHENTICATION and "sasl" not in supported_caps: alog("Server does not support SASL authentication") cli.quit() common_caps = request_caps & supported_caps if common_caps: cli.send("CAP REQ " ":{0}".format(" ".join(common_caps))) elif cmd == "ACK": if "sasl" in caps: cli.send("AUTHENTICATE PLAIN") else: cli.send("CAP END") elif cmd == "NAK": # This isn't supposed to happen. The server claimed to support a # capability but now claims otherwise. alog("Server refused capabilities: {0}".format(" ".join(caps))) if botconfig.SASL_AUTHENTICATION: @hook("authenticate") def auth_plus(cli, something, plus): if plus == "+": account = (botconfig.USERNAME or botconfig.NICK).encode("utf-8") password = botconfig.PASS.encode("utf-8") auth_token = base64.b64encode(b"\0".join( (account, account, password))).decode("utf-8") cli.send("AUTHENTICATE " + auth_token) @hook("903") def on_successful_auth(cli, blah, blahh, blahhh): cli.send("CAP END") @hook("904") @hook("905") @hook("906") @hook("907") def on_failure_auth(cli, *etc): alog("Authentication failed. Did you fill the account name " "in botconfig.USERNAME if it's different from the bot nick?") cli.quit() users.Bot = users.BotUser(cli, botconfig.NICK)
def _reset(evt, var): """Cleans up users that left during game during game end.""" for user in _ghosts: if not user.channels: _users.discard(user) _ghosts.clear() def _swap_player(evt, var, old_user, user): """Mark the user as no longer being a ghost, if they are one.""" _ghosts.discard(old_user) if not old_user.channels: _users.discard(old_user) # Can't use @event_listener decorator since src/decorators.py imports us # (meaning decorator isn't defined at the point in time we are run) events.add_listener("cleanup_user", _cleanup_user) events.add_listener("reset", _reset) events.add_listener("swap_player", _swap_player, priority=1) class User(IRCContext): is_user = True def __new__(cls, cli, nick, ident, host, realname, account): self = super().__new__(cls) super(__class__, self).__init__(nick, cli) self._ident = ident self._host = host self.realname = realname self.account = account
"""Return a tuple of (nick, ident, host) from rawnick.""" return _raw_nick_pattern.search(rawnick).groups(default) def parse_rawnick_as_dict(rawnick, *, default=None): """Return a dict of {"nick": nick, "ident": ident, "host": host}.""" return _raw_nick_pattern.search(rawnick).groupdict(default) def _cleanup_user(evt, var, user): """Removes a user from our global tracking set once it has left all channels.""" _users.discard(user) # Can't use @event_listener decorator since src/decorators.py imports us # (meaning decorator isn't defined at the point in time we are run) events.add_listener("cleanup_user", _cleanup_user) class User(IRCContext): is_user = True def __new__(cls, cli, nick, ident, host, realname, account): self = super().__new__(cls) super(__class__, self).__init__(nick, cli) self._ident = ident self._host = host self.realname = realname self.account = account self.channels = {}
"""Removes a user from our global tracking set once it has left all channels.""" if var.PHASE in var.GAME_PHASES and user in var.ALL_PLAYERS: _ghosts.add(user) else: _users.discard(user) def _reset(evt, var): """Cleans up users that left during game during game end.""" for user in _ghosts: if not user.channels: _users.discard(user) _ghosts.clear() # Can't use @event_listener decorator since src/decorators.py imports us # (meaning decorator isn't defined at the point in time we are run) events.add_listener("cleanup_user", _cleanup_user) events.add_listener("reset", _reset) class User(IRCContext): is_user = True def __new__(cls, cli, nick, ident, host, realname, account): self = super().__new__(cls) super(__class__, self).__init__(nick, cli) self._ident = ident self._host = host self.realname = realname self.account = account self.channels = {}
CLONED[wrapper.source] = target wrapper.pm(messages["clone_target_success"].format(target)) debuglog("{0} (clone) CLONE: {1} ({2})".format(wrapper.source, target, get_main_role(target))) def setup_clone(evt): # We need to add "clone" to the role command exceptions so there's no error # This is done here so that var isn't imported at the global scope # (when we implement proper game state this will be in a different event) from src import settings as var var.ROLE_COMMAND_EXCEPTIONS.add("clone") events.add_listener( "init", setup_clone) # no IRC connection, so no possible error handler yet @event_listener("get_reveal_role") def on_get_reveal_role(evt, var, user): if var.HIDDEN_CLONE and user in var.ORIGINAL_ROLES["clone"]: evt.data["role"] = "clone" @event_listener("del_player") def on_del_player(evt, var, player, all_roles, death_triggers): # clone happens regardless of death_triggers being true or not if var.PHASE not in var.GAME_PHASES: return clones = get_all_players(("clone", ))
def connect_callback(cli): regaincount = 0 releasecount = 0 @hook("endofmotd", hookid=294) @hook("nomotd", hookid=294) def prepare_stuff(cli, prefix, *args): from src import lagcheck alog("Received end of MOTD from {0}".format(prefix)) # This callback only sets up event listeners wolfgame.connect_callback() # just in case we haven't managed to successfully auth yet if botconfig.PASS and not botconfig.SASL_AUTHENTICATION: cli.ns_identify(botconfig.USERNAME or botconfig.NICK, botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_IDENTIFY_COMMAND) # don't join any channels if we're just doing a lag check if not lagcheck: channels.Main = channels.add(botconfig.CHANNEL, cli) channels.Dummy = channels.add("*", cli) if botconfig.ALT_CHANNELS: for chan in botconfig.ALT_CHANNELS.split(","): channels.add(chan, cli) if botconfig.DEV_CHANNEL: channels.Dev = channels.add(botconfig.DEV_CHANNEL, cli) if var.LOG_CHANNEL: channels.add(var.LOG_CHANNEL, cli) else: alog("Preparing lag check") # if we ARE doing a lagcheck, we need at least our own host or things break users.Bot.who() #if var.CHANSERV_OP_COMMAND: # TODO: Add somewhere else if needed # cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL)) users.Bot.change_nick(botconfig.NICK) if var.SERVER_PING_INTERVAL > 0: def ping_server_timer(cli): ping_server(cli) t = threading.Timer(var.SERVER_PING_INTERVAL, ping_server_timer, args=(cli,)) t.daemon = True t.start() ping_server_timer(cli) def setup_handler(evt, var, target): from src import lagcheck if lagcheck: # we just got our own host back target.client.command_handler["privmsg"] = on_privmsg run_lagcheck(target.client) else: target.client.command_handler["privmsg"] = on_privmsg target.client.command_handler["notice"] = functools.partial(on_privmsg, notice=True) events.remove_listener("who_end", setup_handler) events.add_listener("who_end", setup_handler) def mustregain(cli, server, bot_nick, nick, msg): nonlocal regaincount if not botconfig.PASS or bot_nick == nick or regaincount > 3: return if var.NICKSERV_REGAIN_COMMAND: cli.ns_regain(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_REGAIN_COMMAND) else: cli.ns_ghost(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) # it is possible (though unlikely) that regaining the nick fails for some reason and we would loop infinitely # as such, keep track of a count of how many times we regain, and after 3 times we no longer attempt to regain nicks # Since we'd only be regaining on initial connect, this should be safe. The same trick is used below for release as well regaincount += 1 users.Bot.change_nick(botconfig.NICK) def mustrelease(cli, server, bot_nick, nick, msg): nonlocal releasecount if not botconfig.PASS or bot_nick == nick or releasecount > 3: return # prevents the bot from trying to release without a password if var.NICKSERV_RELEASE_COMMAND: cli.ns_release(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) else: cli.ns_ghost(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) releasecount += 1 users.Bot.change_nick(botconfig.NICK) @hook("unavailresource", hookid=239) @hook("nicknameinuse", hookid=239) def must_use_temp_nick(cli, *etc): users.Bot.nick += "_" users.Bot.change_nick() cli.user(botconfig.NICK, "") # TODO: can we remove this? hook.unhook(239) hook("unavailresource", hookid=240)(mustrelease) hook("nicknameinuse", hookid=241)(mustregain) request_caps = {"account-notify", "extended-join", "multi-prefix", "chghost"} if botconfig.SASL_AUTHENTICATION: request_caps.add("sasl") supported_caps = set() supported_sasl = None selected_sasl = None @hook("cap") def on_cap(cli, svr, mynick, cmd, *caps): nonlocal supported_sasl, selected_sasl # caps is a star because we might receive multiline in LS if cmd == "LS": for item in caps[-1].split(): # First item may or may not be *, for multiline try: key, value = item.split("=", 1) except ValueError: key = item value = None supported_caps.add(key) if key == "sasl" and value is not None: supported_sasl = set(value.split(",")) if caps[0] == "*": # Multiline, don't continue yet return if botconfig.SASL_AUTHENTICATION and "sasl" not in supported_caps: alog("Server does not support SASL authentication") cli.quit() raise ValueError("Server does not support SASL authentication") common_caps = request_caps & supported_caps if common_caps: cli.send("CAP REQ " ":{0}".format(" ".join(common_caps))) elif cmd == "ACK": if "sasl" in caps[0]: if var.SSL_CERTFILE: mech = "EXTERNAL" else: mech = "PLAIN" selected_sasl = mech if supported_sasl is None or mech in supported_sasl: cli.send("AUTHENTICATE {0}".format(mech)) else: alog("Server does not support the SASL {0} mechanism".format(mech)) cli.quit() raise ValueError("Server does not support the SASL {0} mechanism".format(mech)) else: cli.send("CAP END") elif cmd == "NAK": # This isn't supposed to happen. The server claimed to support a # capability but now claims otherwise. alog("Server refused capabilities: {0}".format(" ".join(caps[0]))) if botconfig.SASL_AUTHENTICATION: @hook("authenticate") def auth_plus(cli, something, plus): if plus == "+": if selected_sasl == "EXTERNAL": cli.send("AUTHENTICATE +") elif selected_sasl == "PLAIN": account = (botconfig.USERNAME or botconfig.NICK).encode("utf-8") password = botconfig.PASS.encode("utf-8") auth_token = base64.b64encode(b"\0".join((account, account, password))).decode("utf-8") cli.send("AUTHENTICATE " + auth_token, log="AUTHENTICATE [redacted]") @hook("903") def on_successful_auth(cli, blah, blahh, blahhh): cli.send("CAP END") @hook("904") @hook("905") @hook("906") @hook("907") def on_failure_auth(cli, *etc): nonlocal selected_sasl if selected_sasl == "EXTERNAL" and (supported_sasl is None or "PLAIN" in supported_sasl): # EXTERNAL failed, retry with PLAIN as we may not have set up the client cert yet selected_sasl = "PLAIN" alog("EXTERNAL auth failed, retrying with PLAIN... ensure the client cert is set up in NickServ") cli.send("AUTHENTICATE PLAIN") else: alog("Authentication failed. Did you fill the account name " "in botconfig.USERNAME if it's different from the bot nick?") cli.quit() users.Bot = users.BotUser(cli, botconfig.NICK)
def connect_callback(cli): regaincount = 0 releasecount = 0 @hook("endofmotd", hookid=294) @hook("nomotd", hookid=294) def prepare_stuff(cli, prefix, *args): from src import lagcheck alog("Received end of MOTD from {0}".format(prefix)) # This callback only sets up event listeners wolfgame.connect_callback() # just in case we haven't managed to successfully auth yet if botconfig.PASS and not botconfig.SASL_AUTHENTICATION: cli.ns_identify(botconfig.USERNAME or botconfig.NICK, botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_IDENTIFY_COMMAND) # don't join any channels if we're just doing a lag check if not lagcheck: channels.Main = channels.add(botconfig.CHANNEL, cli) channels.Dummy = channels.add("*", cli) if botconfig.ALT_CHANNELS: for chan in botconfig.ALT_CHANNELS.split(","): channels.add(chan, cli) if botconfig.DEV_CHANNEL: channels.Dev = channels.add(botconfig.DEV_CHANNEL, cli) if var.LOG_CHANNEL: channels.add(var.LOG_CHANNEL, cli) else: alog("Preparing lag check") # if we ARE doing a lagcheck, we need at least our own host or things break users.Bot.who() #if var.CHANSERV_OP_COMMAND: # TODO: Add somewhere else if needed # cli.msg(var.CHANSERV, var.CHANSERV_OP_COMMAND.format(channel=botconfig.CHANNEL)) users.Bot.change_nick(botconfig.NICK) if var.SERVER_PING_INTERVAL > 0: def ping_server_timer(cli): ping_server(cli) t = threading.Timer(var.SERVER_PING_INTERVAL, ping_server_timer, args=(cli, )) t.daemon = True t.start() ping_server_timer(cli) def setup_handler(evt, var, target): from src import lagcheck if lagcheck: # we just got our own host back target.client.command_handler["privmsg"] = on_privmsg run_lagcheck(target.client) else: target.client.command_handler["privmsg"] = on_privmsg target.client.command_handler["notice"] = functools.partial( on_privmsg, notice=True) events.remove_listener("who_end", setup_handler) events.add_listener("who_end", setup_handler) def mustregain(cli, server, bot_nick, nick, msg): nonlocal regaincount if not botconfig.PASS or bot_nick == nick or regaincount > 3: return if var.NICKSERV_REGAIN_COMMAND: cli.ns_regain(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_REGAIN_COMMAND) else: cli.ns_ghost(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) # it is possible (though unlikely) that regaining the nick fails for some reason and we would loop infinitely # as such, keep track of a count of how many times we regain, and after 3 times we no longer attempt to regain nicks # Since we'd only be regaining on initial connect, this should be safe. The same trick is used below for release as well regaincount += 1 users.Bot.change_nick(botconfig.NICK) def mustrelease(cli, server, bot_nick, nick, msg): nonlocal releasecount if not botconfig.PASS or bot_nick == nick or releasecount > 3: return # prevents the bot from trying to release without a password if var.NICKSERV_RELEASE_COMMAND: cli.ns_release(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) else: cli.ns_ghost(nick=botconfig.NICK, password=botconfig.PASS, nickserv=var.NICKSERV, command=var.NICKSERV_GHOST_COMMAND) releasecount += 1 users.Bot.change_nick(botconfig.NICK) @hook("unavailresource", hookid=239) @hook("nicknameinuse", hookid=239) def must_use_temp_nick(cli, *etc): users.Bot.nick += "_" users.Bot.change_nick() cli.user(botconfig.NICK, "") # TODO: can we remove this? hook.unhook(239) hook("unavailresource", hookid=240)(mustrelease) hook("nicknameinuse", hookid=241)(mustregain) request_caps = { "account-notify", "extended-join", "multi-prefix", "chghost" } if botconfig.SASL_AUTHENTICATION: request_caps.add("sasl") supported_caps = set() supported_sasl = None selected_sasl = None @hook("cap") def on_cap(cli, svr, mynick, cmd, *caps): nonlocal supported_sasl, selected_sasl # caps is a star because we might receive multiline in LS if cmd == "LS": for item in caps[-1].split( ): # First item may or may not be *, for multiline try: key, value = item.split("=", 1) except ValueError: key = item value = None supported_caps.add(key) if key == "sasl" and value is not None: supported_sasl = set(value.split(",")) if caps[0] == "*": # Multiline, don't continue yet return if botconfig.SASL_AUTHENTICATION and "sasl" not in supported_caps: alog("Server does not support SASL authentication") cli.quit() raise ValueError("Server does not support SASL authentication") common_caps = request_caps & supported_caps if common_caps: cli.send("CAP REQ " ":{0}".format(" ".join(common_caps))) elif cmd == "ACK": if "sasl" in caps[0]: if var.SSL_CERTFILE: mech = "EXTERNAL" else: mech = "PLAIN" selected_sasl = mech if supported_sasl is None or mech in supported_sasl: cli.send("AUTHENTICATE {0}".format(mech)) else: alog("Server does not support the SASL {0} mechanism". format(mech)) cli.quit() raise ValueError( "Server does not support the SASL {0} mechanism". format(mech)) else: cli.send("CAP END") elif cmd == "NAK": # This isn't supposed to happen. The server claimed to support a # capability but now claims otherwise. alog("Server refused capabilities: {0}".format(" ".join(caps[0]))) if botconfig.SASL_AUTHENTICATION: @hook("authenticate") def auth_plus(cli, something, plus): if plus == "+": if selected_sasl == "EXTERNAL": cli.send("AUTHENTICATE +") elif selected_sasl == "PLAIN": account = (botconfig.USERNAME or botconfig.NICK).encode("utf-8") password = botconfig.PASS.encode("utf-8") auth_token = base64.b64encode(b"\0".join( (account, account, password))).decode("utf-8") cli.send("AUTHENTICATE " + auth_token, log="AUTHENTICATE [redacted]") @hook("903") def on_successful_auth(cli, blah, blahh, blahhh): cli.send("CAP END") @hook("904") @hook("905") @hook("906") @hook("907") def on_failure_auth(cli, *etc): nonlocal selected_sasl if selected_sasl == "EXTERNAL" and (supported_sasl is None or "PLAIN" in supported_sasl): # EXTERNAL failed, retry with PLAIN as we may not have set up the client cert yet selected_sasl = "PLAIN" alog( "EXTERNAL auth failed, retrying with PLAIN... ensure the client cert is set up in NickServ" ) cli.send("AUTHENTICATE PLAIN") else: alog( "Authentication failed. Did you fill the account name " "in botconfig.USERNAME if it's different from the bot nick?" ) cli.quit() users.Bot = users.BotUser(cli, botconfig.NICK)
if len(cats & {"Wolfteam", "Village", "Neutral", "Hidden"}) != 1: raise RuntimeError("Invalid categories for {0}: Must have exactly one of {{Wolfteam, Village, Neutral, Hidden}}, got {1}".format(role, cats)) ROLES[role] = frozenset(cats) for cat in cats: if cat not in ROLE_CATS: raise ValueError("{0!r} is not a valid role category".format(cat)) globals()[cat.replace(" ", "_")]._roles.add(role) All._roles.add(role) for cat in ROLE_CATS: cls = globals()[cat.replace(" ", "_")] cls._roles = frozenset(cls._roles) All._roles = frozenset(All._roles) FROZEN = True events.add_listener("init", register_roles, 1) class Category: """Base class for role categories.""" def __init__(self, name): self.name = name self._roles = set() def __len__(self): if not FROZEN: raise RuntimeError("Fatal: Role categories are not ready") return len(self._roles) def __iter__(self): if not FROZEN:
ROLES[role] = frozenset(cats) for cat in cats: if cat not in ROLE_CATS: raise ValueError( "{0!r} is not a valid role category".format(cat)) globals()[cat.replace(" ", "_")]._roles.add(role) All._roles.add(role) for cat in ROLE_CATS: cls = globals()[cat.replace(" ", "_")] cls._roles = frozenset(cls._roles) All._roles = frozenset(All._roles) FROZEN = True events.add_listener("init", register_roles, 1) class Category: """Base class for role categories.""" def __init__(self, name): self.name = name self._roles = set() def __len__(self): if not FROZEN: raise RuntimeError("Fatal: Role categories are not ready") return len(self._roles) def __iter__(self): if not FROZEN:
def startup(self): events.add_listener("chk_win", self.chk_win, 1)
def startup(self): events.add_listener("role_attribution", self.role_attribution, 1)