class ESpeaker: def __init__(self, channel, port, **kwargs): self.bot = IRCBot(**kwargs) self.bot.load_events(self) self.channel = channel self.port = port self.server = None self.clients = set() async def start_server(self): def client_connected(reader, writer): self.clients.add((reader, writer)) writer.write(conf.connect_message.encode("utf8")) self.server = await asyncio.start_server( client_connected, "", self.port, ) async def stop_server(self): for reader, writer in self.clients: writer.close() for reader, writer in self.clients: try: await writer.drain() except ConnectionError: pass self.server.close() await self.server.wait_closed() async def broadcast(self, message): message_bytes = message.encode("utf8") disconnected = set() for reader, writer in self.clients: writer.write(message_bytes) for reader, writer in self.clients: try: await writer.drain() except (ConnectionError, TimeoutError): disconnected.add((reader, writer)) self.clients -= disconnected @Event.privmsg async def on_privmsg(self, sender, channel, message): if channel is None: return voice, pitch = get_voice(sender) ssml_data = SSML_TEMPLATE.format(voice, pitch, escape(message)) print("SSML data:", ssml_data[1:], end="") await self.broadcast(ssml_data) async def start_async(self, hostname, port, ssl, nickname, password): await self.bot.connect(hostname, port, ssl=ssl) await self.bot.register(nickname, password=password) await self.bot.join(self.channel) await self.start_server() await self.bot.listen() def start(self, hostname, port, ssl, nickname, password): self.bot.call_coroutine( self.start_async(hostname, port, ssl, nickname, password), )
class IRCInterface(Interface): def __init__(self, nick, channel): self.bot = IRCBot(log_communication=True) self.bot.load_events(self) self.commands = dict() self.load_commands() self.nick = nick self.channel = channel self.game = Game(interface=self) def load_commands(self): for thing in dir(self): thing = getattr(self, thing) if not getattr(thing, "_is_command", False): continue for name in thing._command_names: self.commands[name] = thing def start(self): self.bot.call_coroutine(self.start_async()) async def start_async(self): await self.bot.connect("chat.freenode.net", 6697, ssl=True) await self.bot.register(self.nick) await self.bot.join(self.channel) await self.bot.listen() @Event.privmsg def on_privmsg(self, sender, channel, message): if channel is None: # ignore PMs return message = message.strip() if not message.startswith(PREFIX): # ignore non-commands return command_and_args = message[1:].lower().split() command, args = command_and_args[0], command_and_args[1:] try: self.handle_command(sender, command, args) except InvalidGameState as e: self.tell(str(e)) @Event.nick def on_nick(self, sender, new_nick): if sender in self.game.players: self.game.players[sender].rename(new_nick) def handle_command(self, actor, command, args): if command not in self.commands: return command = self.commands[command] if command._during_game: if self.game.phase == GamePhase.PRE_GAME: self.tell("Sorry, this command only works during a game.") return if command._during_game or command._only_for_joined: actor = self.game.players.get(actor) if actor is None: self.tell("Sorry, you need to be in the game to do that.") return command(actor, args) @command({"j", "join", "jonge", "jord"}) def command_join(self, actor, args): if args: team_pref = TEAM_MAP.get(args[0].lower()) else: team_pref = None self.game.join(actor, team_pref) @command({"l", "leave"}) def command_leave(self, actor, args): self.game.leave(actor) @command({"t", "team"}, only_for_joined=True) def command_team_pref(self, actor, args): if not args: raise InvalidGameState( "You need to specify a team preference (green, pink, gray).") if self.game.phase is not GamePhase.PRE_GAME: raise InvalidGameState( "You can only specify a team preference before the game.") team_pref = TEAM_MAP.get(args[0].lower(), MISSING) if team_pref is MISSING: raise InvalidGameState("Invalid team preference.") actor.team = team_pref if team_pref is not None: self.tell( f"{self.format_player(actor)} wants to be on the {self.format_team(team_pref)} team." ) else: self.tell(f"{self.format_player(actor)} has no team preference.") @command({"gamemode", "mode", "game"}, only_for_joined=True) def command_gamemode(self, actor, args): if not args: raise InvalidGameState("You need to specify a game mode.") mode = MODE_MAP.get(args[0].lower()) if mode is None: raise InvalidGameState( "You need to specify a game mode that exists.") if self.game.phase is not GamePhase.PRE_GAME: raise InvalidGameState( "You can only specify a game mode before the game.") self.game.mode = mode self.tell( f"{self.format_player(actor)} has set the game mode to {B}{mode}{N}" ) @command({"spymaster"}, only_for_joined=True) def command_spymaster_pref(self, actor, args): if self.game.phase is not GamePhase.PRE_GAME: raise InvalidGameState( "You can only specify a spymaster preference before the game.") actor.toggle_spymaster_preference() pref = "wants" if actor.spymaster_preference else "does not want" self.tell(f"{self.format_player(actor)} {pref} to be a spymaster.") @command({"start"}) def command_start(self, actor, args): self.game.start_game() @command({"endgame"}) def command_force_endgame(self, actor, args): self.tell( f"{self.format_player(actor)} is forcing the game to end immediately." ) self.tell(self.full_words_view()) self.game = Game(interface=self) @command({"c", "clue", "h", "hint"}, only_during_game=True) def command_hint(self, actor, args): if len(args) != 2: raise InvalidGameState( "Invalid syntax. !c takes two arguments, a word and a number.") if not args[1].isnumeric(): if args[1] in {"unlimited", "infinity", "infty", "inf"}: args[1] = UNLIMITED else: raise InvalidGameState( "Invalid syntax. Number must be numeric or 'unlimited'") else: args[1] = int(args[1]) self.game.hint(actor, args[0], args[1]) @command({"g", "guess"}, only_during_game=True) def command_guess(self, actor, args): if len(args) == 0: raise InvalidGameState( "Invalid syntax. Specify a word you want to guess.") word = " ".join(args) self.game.guess(actor, word) @command({"s", "stop"}, only_during_game=True) def command_stop(self, actor, args): self.game.stop(actor) @command({"stats", "status", "players"}) def command_stats(self, actor, args): if self.game.phase in {GamePhase.GUESSING, GamePhase.HINTING}: self.notify_teams() action = ( "guess" if self.game.phase == GamePhase.GUESSING else "hint" if self.game.phase == GamePhase.HINTING else "???") self.tell(f"It's {self.game.active_team}'s turn to {action}.") elif self.game.phase == GamePhase.PRE_GAME and self.game.players: n = len(self.game.players) players = ", ".join( self.format_player(p) for p in self.game.players) self.tell( f"{plural(n, 'player', 'players')}: {players}. The game hasn't started yet." ) else: self.tell("No players yet.") @command({"w", "words"}, only_during_game=True) def command_words(self, actor, args): remaining = ", ".join(self.game.all_words) greens = ", ".join(self.game.guessed_words[Team.GREEN]) pinks = ", ".join(self.game.guessed_words[Team.PINK]) civilians = ", ".join(self.game.guessed_words[Team.GRAY]) self.tell( f"{B}{GRN}Green{N}: {greens} | {B}{PNK}Pink{N}: {pinks} | {B}Civilians{N}: {civilians}" ) self.tell(f"Remaining words: {remaining}.") if actor in { self.game.teams[Team.GREEN].spymaster, self.game.teams[Team.PINK].spymaster }: self.tell_private(actor, self.spymaster_view()) @command({"pony"}) def command_pony(self, actor, args): self.tell(f"{B}{actor}{N} flips a pony into the air...") v = random.random() result = "head" if v < 0.3 else "tail" if v < 0.9 else "side" self.tell(f"The pony lands on its {B}{result}{N}.") def notify_winner(self): super().notify_winner() self.game = Game(interface=self) def format_player(self, player): if not isinstance(player, Player) or self.game.phase == GamePhase.PRE_GAME: return f"{B}{player}{N}" color = { Team.GREEN: GRN, Team.PINK: PNK, Team.GRAY: "", None: "" }[player.team] return f"{B}{color}{player}{N}" def format_team(self, team): return { Team.GREEN: f"{B}{GRN}Green{N}", Team.PINK: f"{B}{PNK}Pink{N}", Team.GRAY: f"{B}Gray{N}", }[team] def tell(self, message): self.bot.privmsg(self.channel, message) def tell_private(self, player, message): self.bot.privmsg(player.name, message)
class MyBot: def __init__(self): # You can set log_communication to False to disable logging. self.slow = False self.last = 0 self.bot = IRCBot(log_communication=True) self.bot.load_events(self) def start(self): self.bot.call_coroutine(self.start_async()) async def start_async(self): await self.bot.connect("irc.supernets.org", 6667) await self.bot.register(NICK) await self.bot.join(CHAN) await self.bot.privmsg(CHAN, "A wild dewgong has appeared!") self.bot.schedule_coroutine(self.auto_time_loop(60 * 30)) await self.bot.listen() @Event.privmsg async def on_privmsg(self, sender, channel, message): if time.time() - self.last < 3: if not self.slow: self.slow = True self.bot.privmsg(channel, sender + ":SLOW DOWN NERD") self.last = time.time() else: self.slow = False if message == "!kush": self.bot.privmsg(channel, "kush") self.last = time.time() # if message == "!time": # cmd_time = str(datetime.utcnow()) # if channel is None: # self.bot.privmsg(sender, cmd_time) # else: # self.bot.privmsg(channel, sender + ": " + cmd_time) # return # Join the specified channel (channel ops only). # if message.startswith("!join ") and channel is not None: # # User must be an operator. # if not self.bot.users[channel][sender].has_prefix("@"): # return # # try: # new_channel = message.split()[1] # except IndexError: # return # # if new_channel in self.bot.channels: # response = "{}: Already in {}".format(sender, new_channel) # self.bot.privmsg(channel, response) # return # # result = await self.bot.join(new_channel) # self.bot.privmsg(channel, "{}: {} {}.".format( # sender, "Joined" if result.success else "Could not join", # new_channel, # )) async def auto_time_loop(self, interval): # Say the time at specified intervals. while True: await asyncio.sleep(interval) time = str(datetime.utcnow()) for channel in self.bot.channels: self.bot.privmsg(channel, "DEWGONG GONG GONG")
class Nimbot: def __init__(self, check_id, force_id, channel, **kwargs): self.bot = IRCBot(**kwargs) self.bot.load_events(self) self.channel = channel self.check_id = check_id or force_id self.force_id = force_id if check_id or force_id: self.bot.track_known_id_statuses = True self.msg_index = 0 self.mail_users = mentions.MailUserDict() # If more than this number of messages are after # a given message, the message is considered old. self.old_message = 50 # If fewer than this number of messages are after # a given message, the message is considered new. self.new_message = 40 self.read_users() self.read_mentions() def deliver(self, nickname, mentions): for mention in mentions: message = "[{}] <{}> {}".format( naturaltime(mention.time), mention.sender, mention.message) if mention.private: message = "[private] " + message self.bot.privmsg(nickname, message) log("[deliver -> {}] {}".format(nickname, message)) @Event.query_command("help") def on_cmd_help(self, user, message): response = HELP_MESSAGE.format( self.bot.nickname, "enabled" if user.enabled else "disabled", ).splitlines() for line in response: self.bot.privmsg(user, line) @Event.query_command("check") def on_cmd_check(self, user, message): if user.mentions: self.deliver(user, user.mentions) user.clear_mentions() elif user.enabled: self.bot.privmsg(user, "No new mail.") else: self.bot.privmsg( user, "No new mail. (Note: %s is disabled.)" % self.bot.nickname, ) @Event.query_command("clear") def on_cmd_clear(self, user, message): user.clear_mentions() self.bot.privmsg(user, "Mail cleared.") @Event.query_command("enable") def on_cmd_enable(self, user, message): user.enabled = True self.bot.privmsg(user, "%s enabled." % self.bot.nickname) @Event.query_command("disable") def on_cmd_disable(self, user, message): user.enabled = False self.bot.privmsg( user, "%s disabled. You'll still receive messages sent to you " "when you're offline." % self.bot.nickname, ) @Event.query_command("send") def on_cmd_send(self, user, message): args = message.split(None, 1) if len(args) < 2: self.on_cmd_other(user, message) return target, msg = args if target not in self.mail_users: self.bot.privmsg( user, "You can send private messages only to users " "%s is aware of." % self.bot.nickname, ) elif target in self.bot.users[self.channel]: self.bot.privmsg( user, "%s is online. Send them a regular " "private message." % target, ) else: target_user = self.mail_users[target] target_user.mentions.append(Mention( msg, user, target_user, 0, datetime.now(), private=True)) self.bot.privmsg(user, "Message sent.") @Event.query_command("enabled?") def on_cmd_enabled(self, user, message): if " " in message: self.on_cmd_other(user, message) return if not message: state = "enabled" if user.enabled else "disabled" self.bot.privmsg( user, "You have %s %s." % (self.bot.nickname, state)) return target = message if target not in self.mail_users: self.bot.privmsg( user, "%s is not aware of that user." % self.bot.nickname) return target_user = self.mail_users[target] state = "enabled" if target_user.enabled else "disabled" self.bot.privmsg( user, "%s has %s %s." % (target, self.bot.nickname, state)) @Event.query_command("other") def on_cmd_other(self, user, message): self.bot.privmsg(user, 'Type "help" for help.') @Event.privmsg async def on_privmsg(self, sender, channel, message): if channel is None: await self.on_query(sender, message) return sender_user = self.mail_users[sender] self.msg_index += 1 log("[{}] <{}> {}".format(channel, sender, message)) mentioned_users = list(filter(None, ( self.mail_users.get(n.strip(":,")) for n in re.match(r"([^:, ]+[:,] +)*", message).group(0).split()))) for user in mentioned_users: if user.enabled and user != sender_user: user.mentions.append(Mention( message, sender_user, user, self.msg_index, datetime.now())) if mentioned_users: log("[mentioned] %s" % ", ".join(map(str, mentioned_users))) if not sender_user.enabled and sender in self.bot.users[self.channel]: return identified = await self.identify_user(sender) mentions = [] keep_mentions = [] for mention in sender_user.mentions: access_allowed = identified or ( not mention.private and mention.index > sender_user.identified_below) is_old = self.msg_index - mention.index > self.old_message has_reply = mention.sender in mentioned_users new_msg_exists = any( self.msg_index - m.index < self.new_message and m.sender == mention.sender for m in sender_user.mentions) if not access_allowed: keep_mentions.append(mention) elif is_old and (not has_reply or new_msg_exists): mentions.append(mention) self.deliver(sender, mentions) sender_user.mentions = keep_mentions async def on_query(self, sender, message): log("[query] <{}> {}".format(sender, message)) if sender not in self.mail_users: self.bot.privmsg( sender, "Please join the channel monitored by %s." % self.bot.nickname) return identified = await self.identify_user(sender, communicate=True) if not identified: return mail_user = self.mail_users[sender] mail_user.save = True command, message, *_ = message.split(None, 1) + ["", ""] event_id = ("query_command", command) if not self.bot.any_event_handlers(Event, event_id): event_id = ("query_command", "other") await self.bot.call(Event, event_id, mail_user, message) @Event.join async def on_join(self, sender, channel): if sender == self.bot.nickname: for nickname in self.bot.users[channel]: if nickname != self.bot.nickname: self.mail_users[nickname] return self.mail_users[sender] await self.identify_user(sender) if not self.check_id: self.on_id_status_known(sender, Status.logged_in) @Event.nick async def on_nick(self, old_nickname, new_nickname): if new_nickname == self.bot.nickname: return old_user = self.mail_users[old_nickname] if new_nickname not in self.mail_users: new_user = self.mail_users[new_nickname] new_user.enabled = old_user.enabled await self.identify_user(new_nickname) if not self.check_id: self.on_id_status_known(new_nickname, Status.logged_in) async def identify_user(self, nickname, communicate=False): def privmsg(*args, **kwargs): if not communicate: return return self.bot.privmsg(*args, **kwargs) if not self.check_id: return True if not self.bot.is_id_status_synced(nickname): msg = "Checking your account; please wait..." if nickname not in self.bot.users[self.channel]: msg += " (Join the channel monitored by %s to prevent this.)" msg %= self.bot.nickname privmsg(nickname, msg) result = await self.bot.get_id_status(nickname) if not result.success: privmsg(nickname, "Error: Could not check your account.") return False id_status = result.value if id_status == Status.logged_in: return True if id_status != Status.no_account or self.force_id: privmsg(nickname, "Please log in with NickServ.") return False if self.bot.is_id_status_synced(nickname): return True if self.bot.is_tracking_known_id_statuses: privmsg(nickname, ( "Please either join the channel monitored by %s, " "or log in with NickServ." % self.bot.nickname)) return False privmsg(nickname, "Please log in with NickServ.") return False @Event.id_status_known def on_id_status_known(self, nickname, status): user = self.mail_users[nickname] user.identified_below = self.msg_index if user.enabled and self.identified_with_status(status): self.deliver(user, user.mentions) user.clear_mentions() def identified_with_status(self, status): if not self.check_id: return True if status == Status.logged_in: return True if status == Status.no_account and not self.force_id: return True return False def serialize_users(self): users = (u for u in self.mail_users.values() if u.save) for user in users: yield user.to_string() def serialize_mentions(self): users = (u for u in self.mail_users.values() if u.save) mentions = (m for u in users for m in u.mentions) for mention in mentions: offset = 0 if mention.private else self.msg_index yield mention.to_string(offset) def save_users(self): with open(USERS_PATH, "w") as f: print("# <nickname> <enabled (True/False)>", file=f) for line in self.serialize_users(): print(line, file=f) def save_mentions(self): with open(MENTIONS_PATH, "w") as f: for line in self.serialize_mentions(): print(line, file=f) def read_users(self): if not os.path.isfile(USERS_PATH): return with open(USERS_PATH) as f: for line in f: if not line.startswith("#"): user = MailUser.from_string(line.rstrip()) self.mail_users[user.nickname] = user def read_mentions(self): if not os.path.isfile(MENTIONS_PATH): return with open(MENTIONS_PATH) as f: for line in f: mention = Mention.from_string(line, self.mail_users) mention.target.mentions.append(mention) async def command_loop(self): while True: try: command = await astdio.input() except EOFError: break text = "Commands: users, mentions" if command == "users": text = "\n".join(( "<nickname> <enabled>", *self.serialize_users(), )) elif command == "mentions": text = "\n".join(( "<date> <offset> <private> <sender> <target> <message>", *self.serialize_mentions(), )) await stderr_async(text) async def start_async(self, hostname, port, ssl, nickname, password): await self.bot.connect(hostname, port, ssl=ssl) await self.bot.register(nickname, password=password) if self.check_id: if not self.bot.is_tracking_known_id_statuses: raise RuntimeError( "The IRC server must support account-notify " "when using --check-id and --force-id.", ) self.bot.join(self.channel) await self.bot.listen() def start(self, hostname, port, ssl, nickname, password): self.bot.schedule_coroutine(self.command_loop()) self.bot.call_coroutine( self.start_async(hostname, port, ssl, nickname, password), )