Пример #1
0
async def update_db(loop):
    current = await get_list()  # list of comics currently in the database
    db = database.XKCD_Database()
    r = requests.get("https://xkcd.com/archive/"
                     )  # Grab the archive page, a list of all xkcd comics
    raw_names = re.findall(
        r'[0-9]{0,4}/" title="[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}">[^<>]+<',
        r.content.decode(),
    )  # Grab sections of html containing names
    xkcds = []
    for name in raw_names:
        strip = xkcd.from_raw_name(
            name)  # Create an xkcd object from the raw data
        if (not strip.name
                in current):  # We only need to add comics we don't have
            xkcds.append(strip)

    with concurrent.futures.ThreadPoolExecutor(max_workers=8) as executor:
        calls = [
            loop.run_in_executor(executor, strip.get_uri_alt)
            for strip in xkcds
        ]

    strips = await asyncio.gather(*calls)

    for strip in strips:  # Run calls
        await db.insert_xkcd(strip)  # Add to db
        utilities.log_message(f"Added new xkcd comic {strip.name}.")

    utilities.log_message("xkcd database up to date!")
Пример #2
0
    async def set_dm(self, guild):
        # guild: the Guild object of the relevant server
        dm_role = await self.get_dm_role(guild)

        if not dm_role:
            utilities.log_message(f"Missing role permissions in {guild.name}.")
            return

        campaign = await self.get_active_campaign(guild.id)

        for player in campaign.players:
            member = await utilities.get_member(guild, player)
            if (not utilities.is_guild_owner(guild, member.id)
                    and dm_role in member.roles):
                await member.remove_roles(dm_role)

        if not campaign.dm:
            return

        try:
            dm = await utilities.get_member(guild, campaign.dm)
            if not utilities.is_guild_owner(guild, dm.id):
                await dm.add_roles(dm_role)
        except AttributeError:
            # didn't find dm for some reason
            utilities.log_message(
                f"Failed to find DM for campaign {campaign.name} in "
                f"{guild.name}.")
Пример #3
0
    def __init__(self, client, loop):
        config = utilities.load_config()
        config["client"] = client
        self.client = client

        self.db = database.Discord_Database()

        self.commands = {}
        for i in Bot.INSTRUCTIONS:
            try:
                cmd = i(config)
                for c in cmd.commands:
                    self.commands[c] = cmd
            except AssertionError:
                utilities.log_message(f"{i} disabled.")

        self.patterns = []
        for p in Bot.PATTERNS:
            try:
                self.patterns.append(p(config))
            except AssertionError:
                utilities.log_message(f"{p} disabled.")

        self.reaction_handlers = []
        for h in self.patterns + list(self.commands.values()):
            if h.monitors_reactions:
                self.reaction_handlers.append(h)

        self.token = config["token"]

        loop.run_until_complete(database.init_db(config["db_file"]))
Пример #4
0
    async def handle(self, message):
        self.delete_message = True
        self.will_send = True

        user = message.author
        mention = user.display_name
        server = message.guild
        channel = message.channel

        string = message.content.lower().strip()
        for c in self.commands:
            if string.startswith(c):
                command = c
                break
        string = string.replace(command, "", 1).strip()

        if "stats" in string:
            await self.handle_stats(string, user, server, mention, channel)
            return

        try:
            rolls = roll.get_rolls(string)
            assert len(rolls) > 0
        except Exception as e:
            self.delete_message = False
            await channel.send(f"{self.failstr}. {e}")
            return

        if server and user:
            for r in rolls:
                for dice_str, values in r.roll_info():
                    await self.db.insert_roll(dice_str,
                                              ",".join(map(str, values)), user,
                                              server)

        e = build_embed(rolls, mention, string)
        if command == "--roll":
            try:
                await channel.send(embed=e)
            except discord.errors.HTTPException as e:
                await channel.send(
                    "Ran into an error. The message may have been too long.")

        elif command in ["--dmroll", "--gmroll"]:
            dm = await self.get_dm(message.guild.members)
            if dm is None:
                self.delete_message = False
                await channel.send(
                    f'Couldn\'t find a member with the role "{self.dm_role}".')

            try:
                await dm.send(embed=e)
                if user != dm:
                    await user.send(embed=e)
            except discord.errors.HTTPException as e:
                self.delete_message = False
                await channel.send("Ran into an error.")
                utilities.log_message(f"Error sending roll: {e}")
                return
Пример #5
0
    async def handle_reaction(self, reaction, _):
        if reaction.message.id not in self.sent:
            return

        emoji = utilities.get_emoji_name(reaction.emoji)
        embed_style = None
        for key in ScryfallHandler.EMBED_EMOJI_MAPPING.keys():
            if emoji in ScryfallHandler.EMBED_EMOJI_MAPPING[key]:
                embed_style = key
                break

        index = (int(emoji[-1:]) - 1 if emoji.startswith("keycap_")
                 and 0 < int(emoji[-1]) < 6 else None)
        remove_message = emoji in ScryfallHandler.REMOVE_EMOJIS

        utilities.log_message(f'Scryfall message reacted to with "{emoji}".')

        if remove_message:
            await reaction.message.delete()
            utilities.log_message("Deleted message.")
            return

        message, sent = self.sent[reaction.message.id]
        if isinstance(sent, CardList) and index is not None:
            try:
                card = sent.select_option(index)
            except IndexError:
                return

            if type(card) == Card:
                await reaction.message.edit(embed=card.get_embed())
                self.log_sent(reaction.message, card)
            elif type(card) == DoubleFacedCard:
                await reaction.message.delete()
                await self.send(card, reaction.message.channel)

            utilities.log_message("Option selected from scryfall card list.")
            await reaction.clear()
            return

        if embed_style is None:
            return
        elif embed_style == sent.embed_style:
            await reaction.clear()
            return

        if isinstance(sent, Card):
            await reaction.message.edit(embed=sent.get_embed(embed_style))
        else:
            utilities.log_message(f"Strange scryfall sent type: {type(sent)}")
            return

        if type(sent) in [DoubleFacedCard, BackFace]:
            for message, content in self.sent.values():
                if content is sent.other_face:
                    await message.edit(embed=content.get_embed(embed_style))

        await reaction.clear()
        utilities.log_message("Scryfall embed size edited.")
Пример #6
0
 async def make_connection(self, file):
     self.file = file
     run_migration = os.path.isfile(file)
     self.connection = await aiosqlite.connect(self.file)
     if run_migration:
         await self.migrate()
     for command in self.startup_commands:
         await self.execute(command, trans_type=TransTypes.COMMIT)
     utilities.log_message("Established connection and set up database.")
Пример #7
0
    def get_result(self):
        if self.result is None:
            self.resolve()

        if self.result is None:
            utilities.log_message(
                "Failed to find card while searching scryfall for "
                f'"{self.query}".')
            return ("Oops, something went wrong when I was looking for " +
                    f'"{utilities.capitalise(self.query)}". Let Owen know!')

        return self.result
Пример #8
0
    def log_message(self, message):
        guild_string = message.guild
        if guild_string is None:
            guild_string = "me"

        if message.content:
            utilities.log_message(
                message.author.display_name +
                f' sent "{message.content}" to {guild_string}.')
        else:
            utilities.log_message(message.author.display_name +
                                  f" sent an attachment to {guild_string}.")
Пример #9
0
 async def get_random_xkcd(self):
     newest = (await database.execute("SELECT max(id) FROM xkcds;",
                                      trans_type=TransTypes.GETONE))[0]
     comic = random.randint(newest - await self.xkcd_count(), newest)
     try:
         return self.interpret_xkcd(await database.execute(
             "SELECT id, name, uri, alt FROM xkcds WHERE id = ?;",
             (str(comic), ),
             TransTypes.GETONE,
         ))
     except TypeError:
         utilities.log_message(f"Missing xkcd #{comic}.")
         return await self.get_random_xkcd()
Пример #10
0
    async def _handle(self, guild, campaign, _arg, _target):
        out = f"Members of campaign {campaign.name}"

        if campaign.day >= 0 and campaign.time >= 0:
            hour = str(campaign.time // 3600)
            minute = str(campaign.time % 3600 // 60)
            while len(minute) < 2:
                minute += "0"

            out += (" (" + utilities.number_to_weekday(campaign.day) +
                    f" at {hour}:{minute})")

        out += ":\n\t"

        dm_string = ""
        if campaign.dm:
            try:
                dm_name = (await utilities.get_member(guild, campaign.dm)).name
                dm_string = "DM: " + dm_name

                if campaign.dm in campaign.players:
                    nick = campaign.nicks[campaign.players.index(campaign.dm)]
                    dm_string += f" ({nick})" if nick else ""

            except Exception as e:
                dm_string = ""
                utilities.log_message(f"Failed to add DM name: {e}")

        out += (dm_string if dm_string else "No DM") + "\n\t"

        member_names = []
        if campaign.players:
            for p, n in zip(campaign.players, campaign.nicks):
                if p == campaign.dm:
                    continue

                try:
                    name = (await utilities.get_member(guild, p)).name
                    name += f" ({n})" if n else ""
                    member_names.append(name)
                except Exception as e:
                    utilities.log_message(f"Failed to add name: {e}")

        if member_names:
            out += "\n\t".join(member_names)
        else:
            out += "No players"

        return out
Пример #11
0
    async def execute(self, command, args=None, trans_type=TransTypes.GETALL):
        try:
            if args is not None:
                cursor = await self.connection.execute(command, args)
            else:
                cursor = await self.connection.execute(command)

            if trans_type == TransTypes.GETALL:
                return await cursor.fetchall()
            elif trans_type == TransTypes.GETONE:
                return await cursor.fetchone()
            elif trans_type == TransTypes.COMMIT:
                await self.save()
        except Exception as e:
            utilities.log_message(f"Database error: {e}")
            utilities.log_message(f"Ocurred on command: {command}")
Пример #12
0
    def __init__(self, config):
        assert config["dnd_campaign"]
        super().__init__(config, commands=["--dnd"])
        self.dm_role = config["dm_role"]
        self.campaigns = {}
        self.db = database.Campaign_Database()
        self.help_message = utilities.load_help()["dnd"]
        config["client"].loop.create_task(self.notify(config["client"]))

        self.options = {}
        for i in CampaignSwitcher.INSTRUCTIONS:
            try:
                option = i(config)
                option.meta = self
                for c in option.commands:
                    self.options[c] = option
            except AssertionError:
                utilities.log_message(f"Campaign option {i} disabled.")
Пример #13
0
 async def handle(self, message):
     self.delete_message = False
     argument = self.remove_command_string(message.content)
     if argument:
         try:
             await message.channel.send(
                 wordart.handle_wordart_request(argument,
                                                self.default_emoji))
             self.delete_message = True
         except discord.HTTPException as e:
             utilities.log_message(f"Error attempting to send wordart: {e}")
             await message.channel.send(
                 "Ran into an error sending this wordart. The message " +
                 "was probably too long, usually around 4 characters " +
                 "is the maximum.")
     else:
         await message.channel.send(
             "Usage: `--wa <message>` to create word art. " +
             "Messages must be very short: around 4 characters.")
Пример #14
0
    async def notify(self, client, period=60, delta=1800):
        await client.wait_until_ready()

        while not client.is_closed():
            reminders = await self.db.get_reminders(period, delta)
            for name, channel, players in reminders:
                try:
                    mention_string = " ".join(
                        [f"<@{p}>" for p in parse_player_string(players)])

                    channel = discord.utils.find(lambda c: c.id == channel,
                                                 client.get_all_channels())

                    await channel.send(
                        f"A game for {name} begins in " +
                        f"{int(round(delta / 60, 0))} minutes.\n\n" +
                        mention_string)
                except Exception as e:
                    utilities.log_message(
                        "Ran into an issue sending " +
                        f"notification for campaign {name}: {e}")

            await asyncio.sleep(period)
Пример #15
0
    def _update_screen(self):
        self.screen.fill(self.settings.bg_color)
        self.earth.blitme()
        for factory in self.earth.factories:
            factory.blitme()
        for ship in self.earth.ships:
            ship.blitme()
        for enemy in self.encounter.enemies:
            enemy.blitme()
        for bullet in self.bullets:
            bullet.blitme()

        self.screen.blit(update_fps(self), (3, 0))
        self.screen.blit(
            log_message(self, self.logger.log_message, self.logger.color),
            (800, 0))

        pygame.display.flip()
Пример #16
0
    async def migrate(self):
        (from_version, ) = await self.execute("PRAGMA user_version;",
                                              trans_type=TransTypes.GETONE)

        if from_version == Database.VERSION:
            utilities.log_message("Database schema up to date!")
        elif from_version == 0:
            utilities.log_message(
                f"Database at version 0: updating to {Database.VERSION}")
            await self.execute("PRAGMA foreign_keys = OFF;")
            await self.execute(
                "CREATE TABLE _new_campaigns("
                "id INTEGER PRIMARY KEY AUTOINCREMENT, "
                "name TEXT COLLATE NOCASE, server INTEGER, "
                "dm INTEGER, players TEXT, nicks TEXT, active INTEGER, "
                "day INTEGER, time INTEGER, notify INTEGER, channel INTEGER, "
                "FOREIGN KEY(dm) REFERENCES users(id), "
                "FOREIGN KEY(server) REFERENCES servers(id), "
                "UNIQUE(name, server));")
            await self.execute("INSERT INTO _new_campaigns("
                               "name, server, dm, players, nicks, "
                               "active, day, time, notify, channel"
                               ") SELECT * FROM campaigns;")
            await self.execute("DROP TABLE campaigns;")
            await self.execute(
                "ALTER TABLE _new_campaigns RENAME TO campaigns;")
            await self.execute("PRAGMA foreign_keys = ON;")
            await self.execute(
                "ALTER TABLE rolls ADD COLUMN campaign INTEGER REFERENCES "
                "campaigns(id) ON DELETE SET NULL;")
            await self.save()
            utilities.log_message("Database migration successful!")
        else:
            utilities.log_message(
                "Don't know how to update database from version "
                f"{from_version} to version {Database.VERSION}. Exiting.")
            exit(1)
Пример #17
0
    async def handle(self, message):
        campaign = await self.meta.get_active_campaign(message.guild.id)
        arg = re.sub(self.regex,
                     "",
                     message.content,
                     count=1,
                     flags=re.IGNORECASE).strip()

        nick = arg
        if message.mentions:
            if len(message.mentions) > 1:
                return ("This command requires a single mention. e.g. "
                        "`--dnd setnick <mention> <name>`.")

            target = message.mentions[0]

            if not target.id in campaign.players:
                return (
                    f"{target.display_name} is not in {campaign.name} "
                    "so I cannot set their nickname. The DM can add them "
                    "`--dnd add <mention>` or they can join with `--dnd join`."
                )

            if (message.author.id in [campaign.dm, target.id]
                    or campaign.dm == None):

                for uid in message.raw_mentions:
                    nick = nick.replace(f"<@{uid}>", "")
                    nick = nick.replace(f"<@!{uid}>", "")
                nick = nick.strip()
            else:
                return "Only the campaign dm can set other players nicknames."
        else:
            target = message.author

            if not target.id in campaign.players:
                return (f"You are not in {campaign.name}, so I cannot set "
                        "your nickname. Join with `--dnd join`.")

        if len(nick) == 0:
            return ("Usage: `--dnd nick <nickname>` or "
                    "`--dnd setnick <mention> <nickname>`.")
        if self.nick_regex.match(nick) is None:
            return ("A nickname must be 1-32 non-special characters. "
                    f'"{nick}" is inadmissable.')

        if not message.author.id in campaign.players:
            return ("You must join the campaign with `--dnd join` "
                    "before you can set a nickname.")
        if utilities.is_guild_owner(message.guild, target.id):
            if target.id == message.author.id:
                return ("You are the server owner which means I can't "
                        "set your nickname.")
            else:
                return (f"{target.display_name} is the guild owner which "
                        "means I can't set their nickname")

        try:
            await target.edit(nick=nick)
        except discord.Forbidden as e:
            utilities.log_message(
                f"Failed to set nick of {target.display_name} in "
                f"{message.guild.name} due to: {e}")
            return ("I was unable to set your nickname. Either I lack the "
                    "permission to do so or you are the server owner.")

        campaign.set_nick(target.id, nick)
        await self.meta.db.add_campaign(campaign)
        return (f"Set the nickname for {target.name} "
                f"in {campaign.name} to {nick}.")
Пример #18
0
 def __init__(self, config):
     assert config["dnd_spells"]
     super().__init__(config, commands=["--spell"])
     self.sb = spellbook.Spellbook(config["spellbook_url"])
     utilities.log_message("Successfully downloaded spellbook.")
Пример #19
0
async def on_ready():
    utilities.log_message(f"Logged in as {client.user.name}," +
                          f" ID: {client.user.id}")
    utilities.log_message("==== BEGIN LOG ====")
Пример #20
0
 async def save(self):
     try:
         await self.connection.commit()
     except Exception as e:
         utilities.log_message(f"Database error: {e}")
Пример #21
0
def load_wa_alphabet():
    try:
        with open("resources/alphabet.json", "r") as f:
            wa_alphabet.update(json.load(f))
    except FileNotFoundError:
        utilities.log_message("Failed to load wordart alphabet.")
Пример #22
0
    async def handle_command(self, message):
        if message.author == client.user or message.author.bot:
            return

        if message.guild is not None:
            await self.db.insert_user(message.author)
            await self.db.insert_server(message.guild)
        self.log_message(message)

        cmd = None
        if message.content.startswith("--"):
            match = re.search(r"^--[a-zA-Z]+", message.content.lower())

            if match is None:
                await message.channel.send(
                    "Commands are called via `--<command>`. Try `--all` " +
                    "to see a list of commands or `--help` for further assistance."
                )
                return

            cmd_str = match.group(0)
            if cmd_str == "--all":
                await message.channel.send("\n".join(self.commands))
                return
            elif cmd_str in self.commands:
                cmd = self.commands[match.group(0)]
            else:
                suggestions = difflib.get_close_matches(cmd_str, self.commands)
                if suggestions:
                    await message.channel.send(
                        f"Command `{cmd_str}` doesn't exist. " +
                        f"Perhaps you meant `{suggestions[0]}`?")
                else:
                    await message.channel.send(
                        f"Command `{cmd_str}` doesn't exist. Try `--all` " +
                        "to see a list of commands.")
                return
        else:
            for pattern in self.patterns:
                if re.search(pattern.regex, message.content):
                    cmd = pattern
            if cmd is None:
                return

        try:
            resp = await cmd.handle(message)
        except Exception as e:
            utilities.log_message("Ran into issue handling command " +
                                  f"{message.content}: {e}")
            utilities.log_message(f"Stack trace:\n{traceback.format_exc()}")
            await message.channel.send("Ran into an issue with that command.")
            return

        if not cmd.will_send:
            try:
                if type(resp) is str:
                    await message.channel.send(resp)
                elif type(resp) is discord.Embed:
                    await message.channel.send(embed=resp)
                elif type(resp) is list:
                    for e in resp:
                        if type(e) is str:
                            await message.channel.send(e)
                        elif type(e) is discord.Embed:
                            await message.channel.send(embed=e)
                        else:
                            utilities.log_message(
                                "List from command "
                                f'"{message.content}" contained strange type: '
                                f"{type(resp)}.")
                            await message.channel.send(
                                "Ran into an issue with that command. ")
                else:
                    utilities.log_message(
                        "Got strange type from command " +
                        f'"{message.content}": {type(resp)}.')
                    await message.channel.send(
                        "Ran into an issue with that command.")
            except Exception as e:
                utilities.log_message(f"Ran into issue sending response: {e}")
                utilities.log_message(
                    f"Stack trace:\n{traceback.format_exc()}")
                await message.channel.send("Failed to send response.")

        if cmd.delete_message:
            try:
                await message.delete()
                utilities.log_message("Deleted command message.")
            except discord.errors.Forbidden:
                utilities.log_message("Couldn't delete command message; " +
                                      "insufficient permissions.")
            except discord.errors.NotFound:
                utilities.log_message("Couldn't find message to delete. " +
                                      "Already gone?")
Пример #23
0
def start_db_thread(interval, client):
    thread = threading.Thread(target=update_db_schedule,
                              args=[interval, client])
    thread.start()
    utilities.log_message("Started XKCD update thread.")
Пример #24
0
    async def handle(self, message):
        self.scrub_votes()
        self.scrub_cooldown()

        if not message.mentions:
            return "Usage: `--kick <mention>` e.g. `--kick @BadPerson`."
        if len(message.mentions) > 1:
            return "I can only kick one person at a time!"
        target = message.mentions[0]

        if type(target) != discord.Member:
            return ("Sorry, I can't find voice information for "
                    f"{target.display_name}.")

        if self.on_cooldown(target):
            return (f"{target.display_name} has been kicked too recently. "
                    "I only kick people at most once every "
                    f"{self.interval // 60} minutes.")

        if target.voice is None:
            return f"{target.display_name} isn't in a voice channel."
        if message.author.voice is None:
            return ("You can't kick people you aren't "
                    "in a voice channel with!")

        channel = target.voice.channel
        if channel is None or channel != message.author.voice.channel:
            return ("You can't kick people you aren't "
                    "in a voice channel with!")

        voice_members = sum([1 if not m.bot else 0 for m in channel.members])
        if voice_members < 3:
            return ("Sorry, I don't kick people from voice channels "
                    "with less than 3 members.")
        required_votes = int(voice_members / 2 + 1)

        self.add_vote(target, message.author, channel)
        vote_count = self.vote_count(target, channel)

        if vote_count >= required_votes:
            try:
                await target.move_to(None,
                                     reason="Democracy is a beautiful thing.")
                self.cooldown.append((target, time.time()))
            except discord.Forbidden:
                return "Tragically, I don't have permission to do that."

            if self.limit_channel:
                try:
                    await channel.edit(
                        reason="Prevent kicked user rejoining.",
                        user_limit=voice_members - 1,
                    )
                except discord.Forbidden:
                    utilities.log_message("Failed to set member limit.")

            self.scrub_votes(target)
            return discord.Embed(
                title="User kicked.",
                description=f"The council has spoken. {target.mention} has"
                " been disconnected.",
            ).set_thumbnail(url=Kick.THUMBNAIL_URL)
        else:
            needed_votes = required_votes - vote_count
            return f"Vote received! {needed_votes} more votes required."