Example #1
0
def run_db_migrations():
    dbv = int(Configuration.get_persistent_var('db_version', 0))
    Logging.info(f"db version is {dbv}")
    dbv_list = [f for f in glob.glob("db_migrations/db_migrate_*.py")]
    dbv_pattern = re.compile(r'db_migrations/db_migrate_(\d+)\.py',
                             re.IGNORECASE)
    migration_count = 0
    for filename in sorted(dbv_list):
        # get the int version number from filename
        version = int(re.match(dbv_pattern, filename)[1])
        if version > dbv:
            try:
                Logging.info(
                    f"--- running db migration version number {version}")
                spec = importlib.util.spec_from_file_location(
                    f"migrator_{version}", filename)
                dbm = importlib.util.module_from_spec(spec)
                spec.loader.exec_module(dbm)
                Configuration.set_persistent_var('db_version', version)
                migration_count = migration_count + 1
            except Exception as e:
                # throw a fit if it doesn't work
                raise e
    Logging.info(
        f"--- {migration_count if migration_count else 'no'} db migration{'' if migration_count == 1 else 's'} run"
    )
Example #2
0
    async def periodic_task(self):
        # periodic task to run while cog is loaded

        # remove expired cooldowns
        now = datetime.now().timestamp()
        cooldown = Configuration.get_persistent_var(f"mischief_cooldown", dict())

        try:
            # key for loaded dict is a string
            updated_cooldown = {}
            for str_uid, member_last_access_time in cooldown.items():
                if (now - member_last_access_time) < self.cooldown_time:
                    updated_cooldown[str_uid] = member_last_access_time
            Configuration.set_persistent_var(f"mischief_cooldown", updated_cooldown)
        except:
            Logging.info("can't clear cooldown")

        # update role count storage (because it's slow)
        try:
            guild = Utils.get_home_guild()
            for role_id in self.role_map.values():
                my_role = guild.get_role(role_id)
                if my_role is not None:
                    self.role_counts[str(role_id)] = len(my_role.members)
        except:
            Logging.info("can't update role counts")
Example #3
0
    async def process_reaction_add(self, timestamp, event):
        emoji_used = event.emoji
        member = event.member
        guild = self.bot.get_guild(event.guild_id)
        channel = self.bot.get_channel(event.channel_id)
        log_channel = self.bot.get_guild_log_channel(event.guild_id)

        # Act on log, remove, and mute:
        e_db = None
        if str(emoji_used) in self.emoji[event.guild_id]:
            e_db = self.emoji[event.guild_id][str(emoji_used)]
            if not e_db.log and not e_db.remove and not e_db.mute:
                # No actions to take. Stop processing
                return

        if not e_db:
            return

        # check mute/warn list for reaction_add - log to channel
        # for reaction_add, remove if threshold for quick-remove is passed
        try:
            # message fetch is API call. Only do it if needed
            message = await channel.fetch_message(event.message_id)
        except (NotFound, HTTPException) as e:
            # Can't track reactions on a message I can't find
            # Happens for deleted messages. Safe to ignore.
            # await Utils.handle_exception(f"Failed to get message {channel.id}/{event.message_id}", self, e)
            return

        log_msg = f"{Utils.get_member_log_name(member)} used emoji "\
                  f"[ {emoji_used} ] in #{channel.name}.\n"\
                  f"{message.jump_url}"

        if e_db.remove:
            await message.clear_reaction(emoji_used)
            log_msg = f"{log_msg}\n--- I **removed** the reaction"

        if e_db.mute:
            guild_config = self.bot.get_guild_db_config(guild.id)
            if guild_config and guild_config.mutedrole:
                try:
                    mute_role = guild.get_role(guild_config.mutedrole)
                    await member.add_roles(mute_role)
                    self.mutes[guild.id][str(member.id)] = timestamp
                    Configuration.set_persistent_var(f"react_mutes_{guild.id}",
                                                     self.mutes[guild.id])
                    log_msg = f"{log_msg}\n--- I **muted** them"
                except Exception as e:
                    await Utils.handle_exception(
                        "reactmon failed to mute member", self.bot, e)
            else:
                await self.bot.guild_log(
                    event.guild_id,
                    "**I can't mute for reacts because `!guildconfig` mute role is not set."
                )

        if (e_db.log or e_db.remove or e_db.mute) and log_channel:
            await log_channel.send(log_msg)
Example #4
0
    async def react_time(self, ctx: commands.Context, react_time: float):
        """
        Reacts removed before this duration will trigger react-watch

        react_time: time in seconds, floating point e.g. 0.25
        """
        self.min_react_lifespan[ctx.guild.id] = react_time
        Configuration.set_persistent_var(f"min_react_lifespan_{ctx.guild.id}", react_time)
        await ctx.send(f"Reactions that are removed before {react_time} seconds have passed will be flagged")
Example #5
0
 async def startup_cleanup(self):
     for name, cid in Configuration.get_var("channels").items():
         channel = self.bot.get_channel(cid)
         shutdown_id = Configuration.get_persistent_var(f"{name}_shutdown")
         if shutdown_id is not None:
             message = await channel.fetch_message(shutdown_id)
             if message is not None:
                 await message.delete()
             Configuration.set_persistent_var(f"{name}_shutdown", None)
         await self.send_bug_info(name)
Example #6
0
    async def shutdown(self):
        for row in BugReportingChannel.select():
            try:
                cid = row.channelid
                name = f"{row.platform.platform}_{row.platform.branch}"
                guild_id = row.guild.serverid
                channel = self.bot.get_channel(cid)

                message = await channel.send(Lang.get_locale_string("bugs/shutdown_message"))
                Configuration.set_persistent_var(f"{guild_id}_{name}_shutdown", message.id)
            except Exception as e:
                message = f"Failed sending shutdown message <#{cid}> in server {guild_id} for {name}"
                await self.bot.guild_log(guild_id, message)
                await Utils.handle_exception(message, self.bot, e)
Example #7
0
    async def send_bug_info(self, key):
        channel = self.bot.get_channel(Configuration.get_var("channels")[key])
        bug_info_id = Configuration.get_persistent_var(f"{key}_message")
        if bug_info_id is not None:
            try:
                message = await channel.fetch_message(bug_info_id)
            except NotFound:
                pass
            else:
                await message.delete()
                if message.id in self.bug_messages:
                    self.bug_messages.remove(message.id)

        bugemoji = Emoji.get_emoji('BUG')
        message = await channel.send(
            Lang.get_string("bugs/bug_info", bug_emoji=bugemoji))
        await message.add_reaction(bugemoji)
        self.bug_messages.add(message.id)
        Configuration.set_persistent_var(f"{key}_message", message.id)
Example #8
0
    async def send_bug_info(self, *args):
        for channel_id in args:
            channel = self.bot.get_channel(channel_id)
            if channel is None:
                await Logging.bot_log(f"can't send bug info to nonexistent channel {channel_id}")
                continue

            bug_info_id = Configuration.get_persistent_var(f"{channel.guild.id}_{channel_id}_bug_message")

            ctx = None
            tries = 0
            while not ctx and tries < 5:
                tries += 1
                # this API call fails on startup because connection is not made yet.
                # TODO: properly wait for connection to be initialized
                try:
                    last_message = await channel.send('preparing bug reporting...')
                    ctx = await self.bot.get_context(last_message)

                    if bug_info_id is not None:
                        try:
                            message = await channel.fetch_message(bug_info_id)
                        except (NotFound, HTTPException):
                            pass
                        else:
                            await message.delete()
                            if message.id in self.bug_messages:
                                self.bug_messages.remove(message.id)

                    bugemoji = Emoji.get_emoji('BUG')
                    message = await channel.send(Lang.get_locale_string("bugs/bug_info", ctx, bug_emoji=bugemoji))
                    self.bug_messages.add(message.id)
                    await message.add_reaction(bugemoji)
                    Configuration.set_persistent_var(f"{channel.guild.id}_{channel_id}_bug_message", message.id)
                    Logging.info(f"Bug report message sent in channel #{channel.name} ({channel.id})")
                    await last_message.delete()
                except Exception as e:
                    await self.bot.guild_log(channel.guild.id, f'Having trouble sending bug message in {channel.mention}')
                    await Utils.handle_exception(
                        f"Bug report message failed to send in channel #{channel.name} ({channel.id})", self.bot, e)
                    await asyncio.sleep(0.5)
Example #9
0
    async def mute_new_member(self, member):
        # is this feature turned on?
        if not self.mute_new_members:
            return

        # give other bots a chance to perform other actions first (like mute)
        await asyncio.sleep(0.5)
        # refresh member for up-to-date roles
        member = member.guild.get_member(member.id)

        mute_role = member.guild.get_role(Configuration.get_var("muted_role"))

        # only add mute if it hasn't already been added. This allows other mod-bots (gearbot) to mute re-joined members
        # and not interfere by allowing skybot to automatically un-muting later.
        if mute_role not in member.roles:
            self.join_cooldown[str(member.guild.id)][str(
                member.id)] = datetime.now().timestamp()
            Configuration.set_persistent_var("join_cooldown",
                                             self.join_cooldown)
            log_channel = self.bot.get_config_channel(member.guild.id,
                                                      Utils.log_channel)
            if mute_role:
                # Auto-mute new members, pending cooldown
                await member.add_roles(mute_role)
Example #10
0
 async def restart(self, ctx):
     """Restart the bot"""
     shutdown_message = await ctx.send("Restarting...")
     if shutdown_message:
         cid = shutdown_message.channel.id
         mid = shutdown_message.id
         Configuration.set_persistent_var("bot_restart_channel_id", cid)
         Configuration.set_persistent_var("bot_restart_message_id", mid)
         Configuration.set_persistent_var("bot_restart_author_id", ctx.author.id)
     await self.bot.close()
Example #11
0
    async def mute_config(self,
                          ctx,
                          active: bool = None,
                          mute_minutes_old: int = 10,
                          mute_minutes_new: int = 20):
        """
        Mute settings for new members

        active: mute on or off
        mute_minutes_old: how long (minutes) to mute established accounts (default 10)
        mute_minutes_new: how long (minutes) to mute accounts < 1 day old (default 20)
        """
        self.set_verification_mode(ctx.guild)
        if self.discord_verification_flow[ctx.guild.id]:
            # discord verification flow precludes new-member muting
            await ctx.send("""
            Discord verification flow is in effect. Mute is configured in discord moderation settings.
            To enable skybot muting, unset entry_channel: `!channel_config set entry_channel 0`
            """)
            return

        if active is not None:
            self.mute_new_members[ctx.guild.id] = active
            self.mute_minutes_old_account[ctx.guild.id] = mute_minutes_old
            self.mute_minutes_new_account[ctx.guild.id] = mute_minutes_new
            Configuration.set_persistent_var(
                f"{ctx.guild.id}_mute_new_members", active)
            Configuration.set_persistent_var(
                f"{ctx.guild.id}_mute_minutes_old_account", mute_minutes_old)
            Configuration.set_persistent_var(
                f"{ctx.guild.id}_mute_minutes_new_account", mute_minutes_new)

        status = discord.Embed(timestamp=ctx.message.created_at,
                               color=0x663399,
                               title=Lang.get_locale_string(
                                   "welcome/mute_settings_title",
                                   ctx,
                                   server_name=ctx.guild.name))

        status.add_field(
            name="Mute new members",
            value=
            f"**{'ON' if self.mute_new_members[ctx.guild.id] else 'OFF'}**",
            inline=False)
        status.add_field(
            name="Mute duration",
            value=f"{self.mute_minutes_old_account[ctx.guild.id]} minutes",
            inline=False)
        status.add_field(
            name="New account mute duration\n(< 1 day old) ",
            value=f"{self.mute_minutes_new_account[ctx.guild.id]} minutes",
            inline=False)
        await ctx.send(embed=status)
Example #12
0
    async def on_message(self, message: discord.Message):
        if message.author.bot:
            return

        uid = message.author.id

        try:
            guild = Utils.get_home_guild()
            my_member: discord.Member = guild.get_member(uid)
            if my_member is None or len(message.content) > 60:
                return
        except:
            return

        # try to create DM channel
        try:
            channel = await my_member.create_dm()
        except:
            # Don't message member because creating DM channel failed
            channel = None

        now = datetime.now().timestamp()

        triggers = [
            "i wish i was",
            "i wish i were",
            "i wish i could be",
            "i wish to be",
            "i wish to become",
            "i wish i could become",
            "i wish i could turn into",
            "i wish to turn into",
            "i wish you could make me",
            "i wish you would make me",
            "i wish you could turn me into",
            "i wish you would turn me into",
        ]

        remove = False
        pattern = re.compile(f"(skybot,? *)?({'|'.join(triggers)}) (.*)", re.I)
        result = pattern.match(message.content)

        if result is None:
            # no match. don't remove or add roles
            return

        # get selection out of matching message
        selection = result.group(3).lower().strip()
        if selection in ["myself", "myself again", "me"]:
            selection = "me again"

        if selection not in self.role_map:
            return

        # Selection is now validated
        # Check Cooldown
        cooldown = Configuration.get_persistent_var(f"mischief_cooldown", dict())
        member_last_access_time = 0 if str(uid) not in cooldown else cooldown[str(uid)]
        cooldown_elapsed = now - member_last_access_time
        remaining = self.cooldown_time - cooldown_elapsed

        ctx = await self.bot.get_context(message)
        if not Utils.can_mod_official(ctx) and (cooldown_elapsed < self.cooldown_time):
            try:
                remaining_time = Utils.to_pretty_time(remaining)
                await channel.send(f"wait {remaining_time} longer before you make another wish...")
            except:
                pass
            return
        # END cooldown

        if selection == "me again":
            remove = True

        # remove all roles
        for key, role_id in self.role_map.items():
            try:
                old_role = guild.get_role(role_id)
                if old_role in my_member.roles:
                    await my_member.remove_roles(old_role)
            except:
                pass

        try:
            member_counts = Configuration.get_persistent_var(f"mischief_usage", dict())
            member_count = 0 if str(uid) not in member_counts else member_counts[str(uid)]
            member_counts[str(uid)] = member_count + 1
            Configuration.set_persistent_var("mischief_usage", member_counts)
            cooldown = Configuration.get_persistent_var("mischief_cooldown", dict())
            cooldown[str(uid)] = now
            Configuration.set_persistent_var("mischief_cooldown", cooldown)
        except Exception as e:
            await Utils.handle_exception("mischief role tracking error", self.bot, e)

        if not remove:
            # add the selected role
            new_role = guild.get_role(self.role_map[selection])
            await my_member.add_roles(new_role)

        if channel is not None:
            try:
                if remove:
                    await channel.send("fine, you're demoted!")
                else:
                    await channel.send(f"""Congratulations, you are now **{selection}**!! You can wish again in my DMs if you want!
You can also use the `!team_mischief` command right here to find out more""")
            except:
                pass
Example #13
0
 def remove_member_from_cooldown(self, guildid, memberid):
     if str(guildid) in self.join_cooldown and str(
             memberid) in self.join_cooldown[str(guildid)]:
         del self.join_cooldown[str(guildid)][str(memberid)]
         Configuration.set_persistent_var("join_cooldown",
                                          self.join_cooldown)
Example #14
0
 async def shutdown(self):
     for name, cid in Configuration.get_var("channels").items():
         channel = self.bot.get_channel(cid)
         message = await channel.send(
             Lang.get_string("bugs/shutdown_message"))
         Configuration.set_persistent_var(f"{name}_shutdown", message.id)