class Bot(discord.Client): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.model, self.tokenizer = load_model_and_tokenizer(config["eval"]["model_path"]) self.database_accessor = DatabaseAccessor() async def on_ready(self): print("Ready, logged in as {}".format(self.user)) self.update_status.start() @tasks.loop(minutes=config["bot"]["update_status_interval_mins"]) async def update_status(self): print("update") await self.change_presence(activity=discord.Game(name=",help | {}".format(random.choice(status_messages)))) async def on_message(self, message: discord.Message): print("Received message from {}: {}".format(message.author.name, message.content)) channel = message.channel # check for prefix/eagerness/interactivity/since_last_reply/last_forget if channel.type == discord.ChannelType.text: # create guild if doesn't exist if not self.database_accessor.guild_exists(channel.guild.id): print("no guild, creating") self.database_accessor.add_guild(channel.guild.id, channel.guild.name) # create chat if doesn't exist if not self.database_accessor.chat_exists(channel.id): print("no chat, creating") self.database_accessor.add_chat(channel.id, True, "text", channel.guild.id, channel.name) guild_obj = self.database_accessor.get_guild_by_id(channel.guild.id) chat_obj = self.database_accessor.get_chat_by_id(channel.id) if chat_obj.override_chat_settings: eagerness, interactivity = chat_obj.eagerness, chat_obj.interactivity else: eagerness, interactivity = guild_obj.eagerness, guild_obj.interactivity prefix = guild_obj.prefix elif channel.type in (discord.ChannelType.private, discord.ChannelType.group): # create chat if doesn't exist if not self.database_accessor.chat_exists(channel.id): self.database_accessor.add_chat(channel.id, False, "private" if channel.type == discord.ChannelType.private else "group", None, channel.recipient.name if channel.type == discord.ChannelType.private else channel.name) chat_obj = self.database_accessor.get_chat_by_id(channel.id) eagerness, interactivity = chat_obj.eagerness, chat_obj.interactivity prefix = chat_obj.prefix else: raise NotImplementedError("Channel type not implemented: {}".format(str(channel.type))) since_last_reply = chat_obj.since_last_reply last_forget = chat_obj.last_forget print("prefix: {} eagerness: {} interactivity: {} since_last_reply: {} last_forget: {}" .format(prefix, eagerness, interactivity, since_last_reply, last_forget)) # Commands force_reply = False if message.content.startswith(prefix): command = message.content[len(prefix):].strip() split_command = command.split(" ") keyword = split_command[0] clean_prefix = prefix + " " if prefix[-1] in string.ascii_letters else prefix if keyword == "forcereply": force_reply = True elif keyword == "help": embed = discord.Embed(title="Help", description=help_text.format(clean_prefix)) await channel.send(embed=embed) return elif keyword == "forget": await channel.send("^^I have forgotten everything before this message!") self.database_accessor.reset_last_forget_chat(channel.id) return elif keyword == "options": if len(split_command) <= 1: await channel.send("^^Command not recognized. Please type `{0}help` for more info.".format(clean_prefix)) if split_command[1] == "prefix": new_prefix = " ".join(split_command[2:]) if len(new_prefix) > 8: await channel.send("^^Prefix is too long. Max length is 8 characters.") elif len(new_prefix) == 0: await channel.send("^^Prefix cannot be empty!") else: if channel.type == discord.ChannelType.text: self.database_accessor.set_guild_prefix(channel.guild.id, new_prefix) else: self.database_accessor.set_chat_prefix(channel.id, new_prefix) await channel.send("^^Success! Prefix changed to `{0}`".format(new_prefix)) elif split_command[1] in ("mode", "smode"): if split_command[1] == "smode" and channel.type != discord.ChannelType.text: await channel.send("^^This command is only available on servers.") return if len(split_command) == 2: await channel.send("^^Command options not recognized. Please type `{0}help` for more info.".format(clean_prefix)) elif len(split_command) == 3: if split_command[2] == "default" and split_command[1] == "mode": if channel.type == discord.ChannelType.text: self.database_accessor.set_chat_override(channel.id, False) await channel.send("^^Success! Options reset to server default.") else: await channel.send("^^This command is only available on servers.") elif split_command[2] == "get": if split_command[1] == "mode": e, i = eagerness, interactivity else: e, i = guild_obj.eagerness, guild_obj.interactivity for preset, values in conversation_presets.items(): if (e, i) == values: await channel.send("^^This {}'s mode is currently: {} (eagerness {}, interactivity {})" .format("channel" if split_command[1] == "mode" else "server", preset, e, i)) break else: await channel.send("^^This {}'s mode is currently: eagerness {}, interactivity {}" .format("channel" if split_command[1] == "mode" else "server", e, i)) else: for preset, values in conversation_presets.items(): if split_command[2] == preset: if split_command[1] == "mode": self.database_accessor.set_chat_eagerness_interactivity(channel.id, *values) if channel.type == discord.ChannelType.text: self.database_accessor.set_chat_override(channel.id, True) await channel.send("^^Success! Channel options set to {}.".format(preset)) else: self.database_accessor.set_guild_eagerness_interactivity(channel.guild.id, *values) await channel.send("^^Success! Server options set to {}.".format(preset)) break else: await channel.send("^^Command options not recognized. Please type `{0}help` for more info.".format(clean_prefix)) else: try: eagerness = float(split_command[2]) interactivity = float(split_command[3]) if not (0 <= eagerness <= 1000 and 0 <= interactivity <= 1000): await channel.send("^^Eagerness and interactivity values must be between 0 and 1000 inclusive.") else: if split_command[1] == "mode": self.database_accessor.set_chat_eagerness_interactivity(channel.id, eagerness, interactivity) if channel.type == discord.ChannelType.text: self.database_accessor.set_chat_override(channel.id, True) await channel.send("^^Success! Channel eagerness set to {} and interactivity set to {}.".format(eagerness, interactivity)) else: self.database_accessor.set_guild_eagerness_interactivity(channel.guild.id, eagerness, interactivity) await channel.send("^^Success! Server eagerness set to {} and interactivity set to {}.".format(eagerness, interactivity)) except ValueError: await channel.send("^^Command options not recognized. Please type `{0}help` for more info.".format(clean_prefix)) else: await channel.send("^^Command options not recognized. Please type `{0}help` for more info.".format(clean_prefix)) return else: await channel.send("^^Command options not recognized. Please type `{0}help` for more info.".format(clean_prefix)) return else: if message.author.id == self.user.id or message.author.bot or re.match(likely_command_regex, message.content): print("Skipping") return history = await channel.history(limit=config["bot"]["history_limit"]).flatten() history.reverse() # filter bots except itself, and likely commands history = filter(lambda x: (not x.author.bot or x.author.id == self.user.id) and not re.match(likely_command_regex, x.content) and (last_forget is None or x.created_at > last_forget), history) history = list(map(lambda x: (x.author.id == self.user.id, x.content), history)) print("History length: {}".format(len(history))) reply_chance = chance_reply(history, self.tokenizer, self.model, torch.device(config["bot"]["device"])) # probability = 1 - np.exp(-interactivity * since_last_reply) * ((1 - reply_chance) ** eagerness) probability = 1 - (1 - reply_chance) ** (eagerness + since_last_reply * interactivity) print("Chance: {:.03f} Probability: {:.03f}".format(reply_chance, probability)) if force_reply or np.random.binomial(1, probability): async with channel.typing(): print("Replying") reply = generate_from_history(history, self.tokenizer, self.model, torch.device(config["bot"]["device"]), token_blacklist=[photo, call, video, voice, sticker]) for x in reply: await channel.send(x) self.database_accessor.set_since_last_reply(channel.id, 0) else: print("Not replying") self.database_accessor.increment_last_reply(channel.id)