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 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() 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) hook.unhook(294) def setup_handler(evt, 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) who_end.remove("who_end") who_end = EventListener(setup_handler) who_end.install("who_end") 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": acked_caps = caps[0].split() for c in acked_caps: enabled = True if c[0] == "-": enabled = False c = c[1:] Features[c] = enabled if Features.get("sasl", False): 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("saslsuccess") def on_successful_auth(cli, blah, blahh, blahhh): nonlocal selected_sasl Features["sasl"] = selected_sasl cli.send("CAP END") @hook("saslfail") @hook("sasltoolong") @hook("saslaborted") 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)