예제 #1
0
class RemindMe(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "RemindMe"
    DESCRIPTION = "Allows users to create reminders"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="max_reminders_per_user",
            label="Maximum reminders per user",
            type="number",
            placeholder="",
            default=3,
        ),
        ModuleSetting(
            key="cost",
            label="Points required to add a reminder",
            type="number",
            placeholder="",
            default="0",
        ),
        ModuleSetting(
            key="emoji",
            label="Emoji for reminder",
            type="text",
            placeholder="🔔",
            default="🔔",
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.redis = RedisManager.get()
        self.reminder_tasks = {}

    @property
    def help(self):
        help_desc = f"""
        `Syntax: {self.bot.command_prefix}remindme <time> <text>`
        Send you <text> when the time is up.
        Accepts: seconds, minutes, hours, days, weeks
        Examples:
        - {self.bot.command_prefix}remindme 2min Do that thing in 2 minutes
        - {self.bot.command_prefix}remindme 3h40m Do that thing in 3 hours and 40 minutes
        """
        data = discord.Embed(
            title="RemindMe help Menu",
            description=help_desc,
            colour=discord.Colour.red(),
        )
        data.set_thumbnail(url=self.bot.discord_bot.client.user.avatar_url)
        return data

    async def create_reminder(self, bot, author, channel, message, args):
        command_args = message.split(" ") if message else []
        try:
            reminders_list = json.loads(
                self.redis.get(f"{self.bot.bot_name}:remind-me-reminders"))
            """
            { 
                user_id: [
                    {
                        "message_id": message_id,
                        "channel_id": channel_id,
                        "message": message,
                        "date_of_reminder": date_of_reminder,
                        "date_reminder_set": date_reminder_set
                    },
                ],
            }
            """
        except:
            self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                           json.dumps({}))
            reminders_list = {}
        user_reminders = (reminders_list[str(author.id)]
                          if str(author.id) in reminders_list else [])
        if len(user_reminders) >= int(self.settings["max_reminders_per_user"]):
            await self.bot.say(
                channel,
                f"{author.mention} you already have {len(user_reminders)} reminders!",
            )
            return False
        if len(command_args) == 0:
            await self.bot.say(channel, embed=self.help)
            return False
        time_delta = utils.parse_timedelta(command_args[0])
        if not time_delta:
            await self.bot.say(
                channel, f"{author.mention} invalid time: {command_args[0]}")
            return False
        await self.bot.say(
            channel,
            f"{author.mention} ill remind you that in {utils.seconds_to_resp(time_delta.total_seconds())}",
        )
        bot_message = await self.bot.say(
            channel,
            f"If anyone else wants to be reminded click the {self.settings['emoji']}",
        )
        salt = utils.random_string()
        await bot_message.add_reaction(self.settings["emoji"])
        reminder = {
            "message_id": bot_message.id,
            "channel_id": bot_message.channel.id,
            "salt": salt,
            "message": " ".join(command_args[1:]),
            "date_of_reminder": str(utils.now() + time_delta),
            "date_reminder_set": str(utils.now()),
        }
        user_reminders.append(reminder)
        reminders_list[str(author.id)] = user_reminders
        self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                       json.dumps(reminders_list))
        self.reminder_tasks[salt] = ScheduleManager.execute_delayed(
            time_delta.total_seconds(),
            self.execute_reminder,
            args=[salt, author.id, reminder],
        )

    async def forgetme(self, bot, author, channel, message, args):
        try:
            reminders_list = json.loads(
                self.redis.get(f"{self.bot.bot_name}:remind-me-reminders"))
            """
            { 
                user_id: [
                    {
                        "message_id": message_id,
                        "channel_id": channel_id,
                        "salt": salt,
                        "message": message,
                        "date_of_reminder": date_of_reminder,
                        "date_reminder_set": date_reminder_set
                    },
                ],
            }
            """
        except:
            self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                           json.dumps({}))
            reminders_list = {}
        user_reminders = reminders_list[str(author.id)] if str(
            author.id) else []
        for reminder in user_reminders:
            self.reminder_tasks.pop(reminder["salt"]).remove()
            try:
                channel = self.bot.discord_bot.guild.get_channel(
                    int(reminder["channel_id"]))
                bot_message = await channel.fetch_message(
                    int(reminder["message_id"]))
                await bot_message.delete()
            except Exception as e:
                log.error(f"Failed to delete message from bot: {e}")
        reminders_list[str(author.id)] = []
        self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                       json.dumps(reminders_list))
        await self.bot.say(channel,
                           f"{author.mention} you have been forgotten")

    def load_commands(self, **options):
        self.commands["remindme"] = Command.raw_command(
            self.create_reminder,
            command="remindme",
            delay_all=0,
            delay_user=0,
            cost=int(self.settings["cost"]),
            can_execute_with_whisper=False,
            description="Creates a reminder",
        )
        self.commands["forgetme"] = Command.raw_command(
            self.forgetme,
            command="forgetme",
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=False,
            description="Creates a reminder",
        )

    async def execute_reminder(self, salt, user_id, reminder):
        self.reminder_tasks.pop(salt)
        try:
            channel = self.bot.discord_bot.guild.get_channel(
                int(reminder["channel_id"]))
            bot_message = await channel.fetch_message(
                int(reminder["message_id"]))
        except:
            return
        message = reminder["message"]
        for reaction in bot_message.reactions:
            if reaction.emoji == self.settings["emoji"]:
                users = await reaction.users().flatten()
                users.remove(self.bot.discord_bot.client.user)
                sender = await self.bot.discord_bot.get_user(user_id)
                if sender and sender not in users:
                    users.append(sender)
                for user in users:
                    date_of_reminder = utils.parse_date(
                        reminder["date_of_reminder"])
                    date_reminder_set = utils.parse_date(
                        reminder["date_reminder_set"])
                    seconds = int(
                        round((date_of_reminder -
                               date_reminder_set).total_seconds()))
                    response_str = utils.seconds_to_resp(seconds)
                    await self.bot.private_message(
                        user,
                        f"Hello! You asked me to remind you {response_str} ago:\n{message}",
                    )
                break
        try:
            await bot_message.delete()
        except Exception as e:
            log.error(f"Failed to delete message from bot: {e}")
        try:
            reminders_list = json.loads(
                self.redis.get(f"{self.bot.bot_name}:remind-me-reminders"))
            """
            { 
                user_id: [
                    {
                        "message_id": message_id,
                        "channel_id": channel_id,
                        "salt": salt,
                        "message": message,
                        "date_of_reminder": date_of_reminder,
                        "date_reminder_set": date_reminder_set
                    },
                ],
            }
            """
        except:
            self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                           json.dumps({}))
            reminders_list = {}
        user_reminders = (reminders_list[str(user_id)]
                          if str(user_id) in reminders_list else [])
        for _reminder in user_reminders:
            if _reminder == reminder:
                user_reminders.remove(_reminder)
                break
        reminders_list[str(user_id)] = user_reminders
        self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                       json.dumps(reminders_list))

    def enable(self, bot):
        if not bot:
            return
        try:
            reminders_list = json.loads(
                self.redis.get(f"{self.bot.bot_name}:remind-me-reminders"))
            """
            { 
                user_id: [
                    {
                        "message_id": message_id,
                        "channel_id": channel_id,
                        "salt": salt,
                        "message": message,
                        "date_of_reminder": date_of_reminder,
                        "date_reminder_set": date_reminder_set
                    },
                ],
            }
            """
        except:
            self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                           json.dumps({}))
            reminders_list = {}
        new_reminders_list = {}
        for user_id in reminders_list:
            user_reminders = reminders_list[user_id]
            new_user_reminders = []
            for reminder in user_reminders:
                salt = reminder["salt"]
                date_of_reminder = utils.parse_date(
                    reminder["date_of_reminder"])
                if date_of_reminder < utils.now():
                    continue
                new_user_reminders.append(reminder)
                self.reminder_tasks[salt] = ScheduleManager.execute_delayed(
                    (date_of_reminder - utils.now()).total_seconds(),
                    self.execute_reminder,
                    args=[salt, user_id, reminder],
                )
            new_reminders_list[user_id] = new_user_reminders
        self.redis.set(f"{self.bot.bot_name}:remind-me-reminders",
                       json.dumps(new_reminders_list))

    def disable(self, bot):
        if not bot:
            return
예제 #2
0
class AdvancedAdminLog(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "AdvancedAdminLog"
    DESCRIPTION = "Logs Everything"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="ingore_channels",
            label="Channels to ignore for message edit/delete seperated by a space",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="output_channel",
            label="Channels to send logs to",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="level_to_query",
            label="Level required to query",
            type="number",
            placeholder=500,
            default=500,
        ),
        ModuleSetting(
            key="log_edit_message",
            label="Log Edit Message Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_delete_message",
            label="Log Delete Message Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_member_update",
            label="Log Member Update Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_member_update_nickname",
            label="Log Member Update Nickname Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_role_update",
            label="Log Role Update Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_role_create",
            label="Log Role Create Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_voice_change",
            label="Log Voice Change Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_member_join",
            label="Log Member Join Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_member_remove",
            label="Log Member Leave Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_channel_update",
            label="Log Channel Update Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_channel_create",
            label="Log Channel Create Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_channel_delete",
            label="Log Channel Delete Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_guild_update",
            label="Log Guild Update Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_emoji_update",
            label="Log Emoji Update Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_invite_create",
            label="Log Invite Create Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_invite_delete",
            label="Log Invite Delete Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot

    async def message_delete(self, payload):
        if not self.settings["log_delete_message"]:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        message_id = payload.message_id
        with DBManager.create_session_scope() as db_session:
            db_message = Message._get(db_session, message_id)
            if not db_message:
                return
            content = json.loads(db_message.content)
            author_id = db_message.user_id
        sent_in_channel = self.bot.filters.get_channel([int(payload.channel_id)], None, {})[0]
        channels = (
            self.settings["ingore_channels"].split(" ")
            if self.settings["ingore_channels"] != ""
            else []
        )
        if not sent_in_channel or (len(channels) > 0 and str(sent_in_channel.id) in channels):
            return
        author = self.bot.discord_bot.get_member(int(author_id))
        if author == self.bot.discord_bot.client.user:
            return
        embed = discord.Embed(
            colour=await self.get_event_colour(author.guild, "message_delete"),
            timestamp=utils.now(),
        )
        embed.add_field(name="Message", value=content[-1] if content[-1] else "None", inline=False)
        embed.add_field(name="Channel", value=sent_in_channel, inline=False)
        embed.add_field(
            name="ID",
            value=f"```Message ID: {message_id}\nUser ID: {author_id}\nChannel ID: {sent_in_channel.id}```",
            inline=False,
        )
        action = discord.AuditLogAction.message_delete
        perp = None
        async for _log in self.bot.discord_bot.guild.audit_logs(limit=2, action=action):
            same_chan = _log.extra.channel.id == sent_in_channel.id
            if _log.target.id == int(author_id) and same_chan:
                perp = f"{_log.user}({_log.user.id})"
                break
        if perp:
            embed.add_field(name="Deleted by", value=perp, inline=False)
        embed.set_footer(text="User ID: " + str(author_id))
        embed.set_author(
            name=f"{author} ({author.id})- Deleted Message",
            icon_url=str(author.avatar_url),
        )
        await self.bot.say(out_channel, embed=embed)

    async def message_edit(self, payload):
        if not self.settings["log_edit_message"]:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        sent_in_channel = self.bot.filters.get_channel([int(payload.data["channel_id"])], None, {})[0]
        channels = (
            self.settings["ingore_channels"].split(" ")
            if self.settings["ingore_channels"] != ""
            else []
        )
        if not sent_in_channel or (len(channels) > 0 and str(sent_in_channel.id) in channels):
            return
        message_id = payload.message_id
        guild_id = payload.data.get("guild_id", None)

        message = await sent_in_channel.fetch_message(int(message_id))
        if not guild_id or self.bot.discord_bot.guild.id != int(guild_id):
            return

        with DBManager.create_session_scope() as db_session:
            db_message = Message._get(db_session, str(message_id))
            if not db_message:
                return
            content = json.loads(db_message.content)
            author_id = db_message.user_id
        author = self.bot.discord_bot.get_member(int(author_id))
        if int(author_id) == self.bot.discord_bot.client.user.id:
            return
        embed = discord.Embed(
            description=f"{author} updated their message in {sent_in_channel}",
            colour=await self.get_event_colour(author.guild, "message_edit"),
            timestamp=utils.now(),
        )

        embed.add_field(name="Now:", value=f"{content[-1]}" if content[-1] else "None", inline=False)
        embed.add_field(name="Previous:", value=f"{content[-2]}" if content[-2] else "None", inline=False)
        embed.add_field(
            name="Channel:",
            value=f"{sent_in_channel.mention} ({sent_in_channel})\n[Jump to message]({message.jump_url})",
            inline=False,
        )
        embed.add_field(
            name="ID",
            value=f"```User ID = {author.id}\nMessage ID = {message.id}\nChannel ID = {sent_in_channel.id}```",
            inline=False,
        )
        embed.set_author(
            name=f"{author} ({author.id})", icon_url=str(author.avatar_url),
        )
        await self.bot.say(out_channel, embed=embed)

    async def member_update(self, before, after):
        if not self.settings["log_member_update"]:
            return
        guild = before.guild
        if guild != self.bot.discord_bot.guild:
            return

        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        embed = discord.Embed(
            colour=await self.get_event_colour(guild, "user_change"),
            timestamp=utils.now(),
        )
        emb_msg = f"{before} ({before.id}) updated"
        embed.set_author(name=emb_msg, icon_url=before.avatar_url)
        member_updates = {"nick": "Nickname:", "roles": "Roles:"}
        perp = None
        reason = None
        worth_sending = False
        for attr, name in member_updates.items():
            if attr == "nick" and not self.settings["log_member_update"]:
                continue
            before_attr = getattr(before, attr)
            after_attr = getattr(after, attr)
            if before_attr != after_attr:
                worth_sending = True
                if attr == "roles":
                    b = set(before.roles)
                    a = set(after.roles)
                    before_roles = [list(b - a)][0]
                    after_roles = [list(a - b)][0]
                    if before_roles:
                        for role in before_roles:
                            embed.description = role.mention + " Role removed."
                    if after_roles:
                        for role in after_roles:
                            embed.description = role.mention + " Role applied."
                    action = discord.AuditLogAction.member_role_update
                    async for _log in self.bot.discord_bot.guild.audit_logs(
                        limit=5, action=action
                    ):
                        if _log.target.id == before.id:
                            perp = _log.user
                            if _log.reason:
                                reason = _log.reason
                            break
                else:
                    action = discord.AuditLogAction.member_update
                    async for _log in self.bot.discord_bot.guild.audit_logs(
                        limit=5, action=action
                    ):
                        if _log.target.id == before.id:
                            perp = _log.user
                            if _log.reason:
                                reason = _log.reason
                            break
                    embed.add_field(
                        name="Before " + name,
                        value=str(before_attr)[:1024],
                        inline=False,
                    )
                    embed.add_field(
                        name="After " + name, value=str(after_attr)[:1024], inline=False
                    )
        if not worth_sending:
            return
        if perp:
            embed.add_field(name="Updated by ", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def role_update(self, before, after):
        if not self.settings["log_role_update"]:
            return
        guild = before.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        perp = None
        reason = None
        action = discord.AuditLogAction.role_update
        async for _log in guild.audit_logs(limit=5, action=action):
            if _log.target.id == before.id:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        if perp == self.bot.discord_bot.client.user:
            return
        embed = discord.Embed(
            description=after.mention, colour=after.colour, timestamp=utils.now()
        )
        if after is guild.default_role:
            embed.set_author(name="Updated @everyone role ")
        else:
            embed.set_author(name=f"Updated {before.name} ({before.id}) role ")
        if perp:
            embed.add_field(name="Updated by ", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        role_updates = {
            "name": "Name:",
            "color": "Colour:",
            "mentionable": "Mentionable:",
            "hoist": "Is Hoisted:",
        }
        worth_updating = False
        for attr, name in role_updates.items():
            before_attr = getattr(before, attr)
            after_attr = getattr(after, attr)
            if before_attr != after_attr:
                worth_updating = True
                if before_attr == "":
                    before_attr = "None"
                if after_attr == "":
                    after_attr = "None"
                embed.add_field(
                    name="Before " + name, value=str(before_attr), inline=False
                )
                embed.add_field(
                    name="After " + name, value=str(after_attr), inline=False
                )
        p_msg = await self.get_role_permission_change(before, after)
        if p_msg != "":
            worth_updating = True
            embed.add_field(name="Permissions", value=p_msg[:1024], inline=False)
        if not worth_updating:
            return
        await self.bot.say(channel=out_channel, embed=embed)

    async def role_create(self, role):
        if not self.settings["log_role_create"]:
            return
        guild = role.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        perp = None
        reason = None
        action = discord.AuditLogAction.role_create
        async for _log in guild.audit_logs(limit=5, action=action):
            if _log.target.id == role.id:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        embed = discord.Embed(
            description=role.mention, colour=role.colour, timestamp=utils.now(),
        )
        embed.set_author(name=f"Role created {role.name} ({role.id})")
        if perp:
            embed.add_field(name="Created by", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def role_delete(self, role):
        if not self.settings["log_role_create"]:
            return
        guild = role.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        perp = None
        reason = None
        action = discord.AuditLogAction.role_create
        async for _log in guild.audit_logs(limit=5, action=action):
            if _log.target.id == role.id:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        embed = discord.Embed(
            description=role.name, colour=role.colour, timestamp=utils.now(),
        )
        embed.set_author(name=f"Role deleted {role.name} ({role.id})")
        if perp:
            embed.add_field(name="Deleted by", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def voice_change(self, member, before, after):
        if not self.settings["log_voice_change"]:
            return
        guild = member.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        embed = discord.Embed(
            timestamp=utils.now(),
            colour=await self.get_event_colour(guild, "voice_change"),
        )
        embed.set_author(name=f"{member} ({member.id}) Voice State Update")
        change_type = None
        worth_updating = False
        if before.deaf != after.deaf:
            worth_updating = True
            change_type = "deaf"
            if after.deaf:
                embed.description = member.mention + " was deafened. "
            else:
                embed.description = member.mention + " was undeafened. "
        if before.mute != after.mute:
            worth_updating = True
            change_type = "mute"
            if after.mute:
                embed.description = member.mention + " was muted. "
            else:
                embed.description = member.mention + " was unmuted. "
        if before.channel != after.channel:
            worth_updating = True
            change_type = "channel"
            if before.channel is None:
                embed.description = member.mention + " has joined " + after.channel.name
            elif after.channel is None:
                embed.description = member.mention + " has left " + before.channel.name
            else:
                embed.description = (
                    member.mention
                    + " has moved from "
                    + before.channel.name
                    + " to "
                    + after.channel.name
                )
        if not worth_updating:
            return
        perp = None
        reason = None
        action = discord.AuditLogAction.member_update
        async for _log in guild.audit_logs(limit=5, action=action):
            is_change = getattr(_log.after, change_type, None, {})
            if _log.target.id == member.id and is_change:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        if perp:
            embed.add_field(name="Updated by", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def member_join(self, member):
        if not self.settings["log_member_join"]:
            return
        guild = member.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        users = len(guild.members)
        created_at = member.created_at.replace(tzinfo=datetime.timezone.utc)
        since_created = (utils.now() - created_at).days
        user_created = created_at.strftime("%d %b %Y %H:%M")

        created_on = f"{user_created}\n({since_created} days ago)"

        embed = discord.Embed(
            description=f"@{member}",
            colour=await self.get_event_colour(guild, "user_join"),
            timestamp=member.joined_at if member.joined_at else utils.now(),
        )
        embed.add_field(name="Total Users:", value=str(users), inline=False)
        embed.add_field(name="Account created on:", value=created_on, inline=False)
        embed.set_footer(text="User ID: " + str(member.id))
        embed.set_author(
            name=f"{member} ({member.id}) has joined the guild",
            url=member.avatar_url,
            icon_url=member.avatar_url,
        )
        embed.set_thumbnail(url=member.avatar_url)
        await self.bot.say(channel=out_channel, embed=embed)

    async def member_remove(self, member):
        if not self.settings["log_member_remove"]:
            return
        guild = member.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]

        embed = discord.Embed(
            description=f"@{member}",
            colour=await self.get_event_colour(guild, "user_left"),
            timestamp=utils.now(),
        )
        perp = None
        reason = None
        banned = False
        async for _log in guild.audit_logs(limit=5):
            if _log.action not in [
                discord.AuditLogAction.kick,
                discord.AuditLogAction.ban,
            ]:
                continue
            if _log.target.id == member.id:
                perp = _log.user
                reason = _log.reason
                break
        embed.add_field(
            name="Total Users:", value=str(len(guild.members)), inline=False
        )
        if perp:
            embed.add_field(
                name="Kicked By" if not banned else "Banned By",
                value=perp.mention,
                inline=False,
            )
        if reason:
            embed.add_field(name="Reason", value=str(reason), inline=False)
        embed.set_footer(text="User ID: " + str(member.id))
        embed.set_author(
            name=f"{member} has left the guild",
            url=member.avatar_url,
            icon_url=member.avatar_url,
        )
        embed.set_thumbnail(url=member.avatar_url)
        await self.bot.say(channel=out_channel, embed=embed)

    async def channel_update(self, before, after):
        if not self.settings["log_channel_update"]:
            return
        guild = before.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        channel_type = str(after.type).title()
        embed = discord.Embed(
            description=after.mention,
            timestamp=utils.now(),
            colour=await self.get_event_colour(guild, "channel_create"),
        )
        embed.set_author(
            name=f"{channel_type} Channel Updated {before.name} ({before.id})"
        )
        perp = None
        reason = None
        worth_updating = False
        actions = [
            discord.AuditLogAction.channel_update,
            discord.AuditLogAction.overwrite_create,
            discord.AuditLogAction.overwrite_update,
            discord.AuditLogAction.overwrite_delete,
        ]
        async for _log in guild.audit_logs(limit=5):
            if _log.action not in actions:
                continue
            if _log.target.id == before.id:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        if perp.id == self.bot.discord_bot.client.user.id:
            return
        if type(before) == discord.TextChannel:
            text_updates = {
                "name": "Name:",
                "topic": "Topic:",
                "category": "Category:",
                "slowmode_delay": "Slowmode delay:",
            }

            for attr, name in text_updates.items():
                before_attr = getattr(before, attr)
                after_attr = getattr(after, attr)
                if before_attr != after_attr:
                    worth_updating = True
                    if before_attr == "":
                        before_attr = "None"
                    if after_attr == "":
                        after_attr = "None"
                    embed.add_field(
                        name="Before " + name,
                        value=str(before_attr)[:1024],
                        inline=False,
                    )
                    embed.add_field(
                        name="After " + name, value=str(after_attr)[:1024], inline=False
                    )
            if before.is_nsfw() != after.is_nsfw():
                worth_updating = True
                embed.add_field(
                    name="Before " + "NSFW", value=str(before.is_nsfw()), inline=False
                )
                embed.add_field(
                    name="After " + "NSFW", value=str(after.is_nsfw()), inline=False
                )
            p_msg = await self.get_permission_change(before, after)
            if p_msg != "":
                worth_updating = True
                embed.add_field(name="Permissions", value=p_msg[:1024], inline=False)

        if type(before) == discord.VoiceChannel:
            voice_updates = {
                "name": "Name:",
                "position": "Position:",
                "category": "Category:",
                "bitrate": "Bitrate:",
                "user_limit": "User limit:",
            }
            for attr, name in voice_updates.items():
                before_attr = getattr(before, attr)
                after_attr = getattr(after, attr)
                if before_attr != after_attr:
                    worth_updating = True
                    embed.add_field(
                        name="Before " + name, value=str(before_attr), inline=False
                    )
                    embed.add_field(
                        name="After " + name, value=str(after_attr), inline=False
                    )
            p_msg = await self.get_permission_change(before, after)
            if p_msg != "":
                worth_updating = True
                embed.add_field(name="Permissions", value=p_msg[:1024], inline=False)

        if perp:
            embed.add_field(name="Updated by ", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        if not worth_updating:
            return
        await self.bot.say(channel=out_channel, embed=embed)

    async def channel_create(self, channel):
        if not self.settings["log_channel_create"]:
            return
        guild = channel.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        channel_type = str(channel.type).title()
        embed = discord.Embed(
            description=f"{channel.mention} {channel.name}",
            timestamp=utils.now(),
            colour=await self.get_event_colour(guild, "channel_create"),
        )
        embed.set_author(
            name=f"{channel_type} Channel Created {channel.name} ({channel.id})"
        )
        perp = None
        reason = None
        action = discord.AuditLogAction.channel_create
        async for _log in guild.audit_logs(limit=5, action=action):
            if _log.target.id == channel.id:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        embed.add_field(name="Type", value=channel_type, inline=False)
        if perp:
            embed.add_field(name="Created by ", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def channel_delete(self, channel):
        if not self.settings["log_channel_delete"]:
            return
        guild = channel.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        channel_type = str(channel.type).title()
        embed = discord.Embed(
            description=channel.name,
            timestamp=utils.now(),
            colour=await self.get_event_colour(guild, "channel_delete"),
        )
        embed.set_author(
            name=f"{channel_type} Channel Deleted {channel.name} ({channel.id})"
        )
        perp = None
        reason = None
        action = discord.AuditLogAction.channel_delete
        async for _log in guild.audit_logs(limit=5, action=action):
            if _log.target.id == channel.id:
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        embed.add_field(name="Type", value=channel_type, inline=False)
        if perp:
            embed.add_field(name="Deleted by ", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def guild_update(self, before, after):
        if not self.settings["log_guild_update"]:
            return
        if after != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        embed = discord.Embed(
            timestamp=utils.now(),
            colour=await self.get_event_colour(after, "guild_change"),
        )
        embed.set_author(name="Updated Guild", icon_url=str(after.icon_url))
        embed.set_thumbnail(url=str(after.icon_url))
        guild_updates = {
            "name": "Name:",
            "region": "Region:",
            "afk_timeout": "AFK Timeout:",
            "afk_channel": "AFK Channel:",
            "icon_url": "Server Icon:",
            "owner": "Server Owner:",
            "splash": "Splash Image:",
            "system_channel": "Welcome message channel:",
            "verification_level": "Verification Level:",
        }
        worth_updating = False
        for attr, name in guild_updates.items():
            before_attr = getattr(before, attr)
            after_attr = getattr(after, attr)
            if before_attr != after_attr:
                worth_updating = True
                embed.add_field(
                    name="Before " + name, value=str(before_attr), inline=False
                )
                embed.add_field(
                    name="After " + name, value=str(after_attr), inline=False
                )
        if not worth_updating:
            return
        perps = []
        reasons = []
        action = discord.AuditLogAction.guild_update
        async for _log in self.bot.discord_bot.guild.audit_logs(
            limit=int(len(embed.fields) / 2), action=action
        ):
            perps.append(_log.user)
            if _log.reason:
                reasons.append(_log.reason)
        if perps:
            embed.add_field(
                name="Updated by",
                value=", ".join(p.mention for p in perps),
                inline=False,
            )
        if reasons:
            embed.add_field(
                name="Reasons ", value=", ".join(str(r) for r in reasons), inline=False
            )
        await self.bot.say(channel=out_channel, embed=embed)

    async def emoji_update(self, guild, before, after):
        if not self.settings["log_emoji_update"]:
            return
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        perp = None

        time = datetime.datetime.utcnow()
        embed = discord.Embed(
            description="",
            timestamp=time,
            colour=await self.get_event_colour(guild, "emoji_change"),
        )
        embed.set_author(name="Updated Server Emojis")
        worth_updating = False
        b = set(before)
        a = set(after)
        try:
            added_emoji = (a - b).pop()
        except KeyError:
            added_emoji = None
        try:
            removed_emoji = (b - a).pop()
        except KeyError:
            removed_emoji = None
        if added_emoji is not None:
            to_iter = before + (added_emoji,)
        else:
            to_iter = before
        changed_emoji = set((e, e.name, tuple(e.roles)) for e in after)
        changed_emoji.difference_update((e, e.name, tuple(e.roles)) for e in to_iter)
        try:
            changed_emoji = changed_emoji.pop()[0]
        except KeyError:
            changed_emoji = None
        else:
            for old_emoji in before:
                if old_emoji.id == changed_emoji.id:
                    break
            else:
                changed_emoji = None
        action = None
        if removed_emoji is not None:
            worth_updating = True
            embed.description += (
                f"`{removed_emoji}` (ID: {removed_emoji.id})"
                + " Removed from the guild\n"
            )
            action = discord.AuditLogAction.emoji_delete
        elif added_emoji is not None:
            worth_updating = True
            embed.description += (
                f"{added_emoji} `{added_emoji}`" + " Added to the guild\n"
            )
            action = discord.AuditLogAction.emoji_create
        elif changed_emoji is not None:
            worth_updating = True
            new_msg = f"{changed_emoji} `{changed_emoji}`"
            if old_emoji.name != changed_emoji.name:
                new_msg += (
                    " Renamed from "
                    + old_emoji.name
                    + " to "
                    + f"{changed_emoji.name}\n"
                )
                action = discord.AuditLogAction.emoji_update
            embed.description += new_msg
            if old_emoji.roles != changed_emoji.roles:
                worth_updating = True
                if not changed_emoji.roles:
                    new_msg = " Changed to unrestricted.\n"
                    embed.description += new_msg
                elif not old_emoji.roles:
                    embed.description += " Restricted to roles: " + " ".join(
                        [role.mention for role in changed_emoji.roles]
                    )
                else:
                    embed.description += (
                        " Role restriction changed from "
                        + " ".join([role.mention for role in old_emoji.roles])
                        + " to "
                        + " ".join([role.mention for role in changed_emoji.roles])
                    )
        perp = None
        reason = None
        if not worth_updating:
            return
        if action:
            async for _log in guild.audit_logs(limit=1, action=action):
                perp = _log.user
                if _log.reason:
                    reason = _log.reason
                break
        if perp:
            embed.add_field(name="Updated by ", value=perp.mention, inline=False)
        if reason:
            embed.add_field(name="Reason ", value=reason, inline=False)
        await self.bot.say(channel=out_channel, embed=embed)

    async def invite_create(self, invite):
        if not self.settings["log_invite_create"]:
            return
        guild = invite.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        invite_attrs = {
            "code": "Code:",
            "inviter": "Inviter:",
            "channel": "Channel:",
            "max_uses": "Max Uses:",
        }
        embed = discord.Embed(
            title="Invite Created",
            colour=await self.get_event_colour(guild, "invite_created"),
        )
        worth_updating = False
        for attr, name in invite_attrs.items():
            before_attr = getattr(invite, attr)
            if before_attr:
                worth_updating = True
                embed.add_field(name=name, value=str(before_attr), inline=False)
        if not worth_updating:
            return
        await self.bot.say(channel=out_channel, embed=embed)

    async def invite_delete(self, invite):
        if not self.settings["log_invite_delete"]:
            return
        guild = invite.guild
        if guild != self.bot.discord_bot.guild:
            return
        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )-[0]
        invite_attrs = {
            "code": "Code: ",
            "inviter": "Inviter: ",
            "channel": "Channel: ",
            "max_uses": "Max Uses: ",
            "uses": "Used: ",
        }
        embed = discord.Embed(
            title="Invite Deleted",
            colour=await self.get_event_colour(guild, "invite_deleted"),
        )
        worth_updating = False
        for attr, name in invite_attrs.items():
            before_attr = getattr(invite, attr)
            if before_attr:
                worth_updating = True
                embed.add_field(name=name, value=str(before_attr), inline=False)
        if not worth_updating:
            return
        await self.bot.say(channel=out_channel, embed=embed)

    async def custom_event_log(self, message=None, embed=None):
        if message is None and embed is None:
            return

        out_channel = self.bot.filters.get_channel(
                [int(self.settings["output_channel"])], None, {}
            )[0]
        await self.bot.say(channel=out_channel, message=message, embed=embed)

    async def get_permission_change(self, before, after):
        p_msg = ""
        before_perms = {}
        after_perms = {}
        for o, p in before.overwrites.items():
            before_perms[str(o.id)] = [i for i in p]
        for o, p in after.overwrites.items():
            after_perms[str(o.id)] = [i for i in p]
        for entity in before_perms:
            entity_obj = before.guild.get_role(int(entity))
            if not entity_obj:
                entity_obj = before.guild.get_member(int(entity))
            if entity not in after_perms:
                p_msg += f"{entity_obj.mention} Overwrites removed.\n"
                continue
            if after_perms[entity] != before_perms[entity]:
                a = set(after_perms[entity])
                b = set(before_perms[entity])
                a_perms = list(a - b)
                for diff in a_perms:
                    p_msg += f"{entity_obj.mention} {diff[0]} Set to {diff[1]}\n"
        for entity in after_perms:
            entity_obj = after.guild.get_role(int(entity))
            if not entity_obj:
                entity_obj = after.guild.get_member(int(entity))
            if entity not in before_perms:
                p_msg += f"{entity_obj.mention} Overwrites added.\n"
                continue
        return p_msg

    async def get_role_permission_change(self, before, after):
        permission_list = [
            "create_instant_invite",
            "kick_members",
            "ban_members",
            "administrator",
            "manage_channels",
            "manage_guild",
            "add_reactions",
            "view_audit_log",
            "priority_speaker",
            "read_messages",
            "send_messages",
            "send_tts_messages",
            "manage_messages",
            "embed_links",
            "attach_files",
            "read_message_history",
            "mention_everyone",
            "external_emojis",
            "connect",
            "speak",
            "mute_members",
            "deafen_members",
            "move_members",
            "use_voice_activation",
            "change_nickname",
            "manage_nicknames",
            "manage_roles",
            "manage_webhooks",
            "manage_emojis",
        ]
        p_msg = ""
        for p in permission_list:
            if getattr(before.permissions, p) != getattr(after.permissions, p):
                change = getattr(after.permissions, p)
                p_msg += f"{p} Set to {change}\n"
        return p_msg

    async def get_event_colour(self, guild, event_type, changed_object=None):
        if guild.text_channels:
            cmd_colour = discord.Colour.blue()
        else:
            cmd_colour = discord.Colour.red()
        defaults = {
            "message_edit": discord.Colour.orange(),
            "message_delete": discord.Colour.dark_red(),
            "user_change": discord.Colour.greyple(),
            "role_change": changed_object.colour
            if changed_object
            else discord.Colour.blue(),
            "role_create": discord.Colour.blue(),
            "role_delete": discord.Colour.dark_blue(),
            "voice_change": discord.Colour.magenta(),
            "user_join": discord.Colour.green(),
            "user_left": discord.Colour.dark_green(),
            "channel_change": discord.Colour.teal(),
            "channel_create": discord.Colour.teal(),
            "channel_delete": discord.Colour.dark_teal(),
            "guild_change": discord.Colour.blurple(),
            "emoji_change": discord.Colour.gold(),
            "commands_used": cmd_colour,
            "invite_created": discord.Colour.blurple(),
            "invite_deleted": discord.Colour.blurple(),
        }
        colour = defaults[event_type]
        return colour

    def load_commands(self, **options):
        self.commands["message"] = Command.raw_command(
            self.querymessage,
            level=int(self.settings["level_to_query"]),
            can_execute_with_whisper=True,
            description="Queries a message",
        )

    async def querymessage(self, bot, author, channel, message, args):
        embed = discord.Embed(
            colour=await self.get_event_colour(author.guild, "message_edit"),
            timestamp=utils.now(),
        )
        embed.set_author(name=f"Message Query Result",)
        _args = message.split(" ") if message != "" else []
        if not _args:
            embed.description = f"Invalid Message ID"
            await self.bot.say(channel=channel, embed=embed)
            return
        message_id = _args[0]
        with DBManager.create_session_scope() as db_session:
            db_message = Message._get(db_session, str(message_id))
            if db_message:
                content = json.loads(db_message.content)
                author_id = db_message.user_id
                channel_id = db_message.channel_id

        if not db_message:
            embed.description = f"Message not found with message id {message_id}"
            await self.bot.say(channel=channel, embed=embed)
            return
        sent_in_channel = self.bot.filters.get_channel([int(channel_id)], None, {})[0]

        try:
            message = await sent_in_channel.fetch_message(int(message_id))
        except (discord.NotFound, discord.Forbidden, discord.HTTPException):
            message = None

        embed.add_field(name="Message History:", value="\n".join(content), inline=False)
        jump_url = (
            f"[Jump to message]({message.jump_url})"
            if message
            else "Message was deleted!"
        )
        embed.add_field(
            name="Channel:",
            value=f"{sent_in_channel.mention} ({sent_in_channel})\n{jump_url}",
            inline=False,
        )
        embed.add_field(
            name="ID",
            value=f"```User ID = {author_id}\nMessage ID = {message_id}\nChannel ID = {channel_id}```",
            inline=False,
        )
        await self.bot.say(channel=channel, embed=embed)

    def enable(self, bot):
        if not bot:
            return

        HandlerManager.add_handler("discord_raw_message_edit", self.message_edit)
        HandlerManager.add_handler("discord_raw_message_delete", self.message_delete)
        HandlerManager.add_handler("discord_member_update", self.member_update)
        HandlerManager.add_handler("discord_guild_role_update", self.role_update)
        HandlerManager.add_handler("discord_guild_role_create", self.role_create)
        HandlerManager.add_handler("discord_guild_role_delete", self.role_delete)
        HandlerManager.add_handler("discord_voice_state_update", self.voice_change)
        HandlerManager.add_handler("discord_member_remove", self.member_remove)
        HandlerManager.add_handler("discord_member_join", self.member_join)
        HandlerManager.add_handler("discord_guild_channel_update", self.channel_update)
        HandlerManager.add_handler("discord_guild_channel_create", self.channel_create)
        HandlerManager.add_handler("discord_guild_channel_delete", self.channel_delete)
        HandlerManager.add_handler("discord_guild_update", self.guild_update)
        HandlerManager.add_handler("discord_guild_emojis_update", self.emoji_update)
        HandlerManager.add_handler("discord_invite_create", self.invite_create)
        HandlerManager.add_handler("discord_invite_delete", self.invite_delete)
        HandlerManager.add_handler("aml_custom_log", self.custom_event_log)

    def disable(self, bot):
        if not bot:
            return

        HandlerManager.remove_handler("discord_raw_message_edit", self.message_edit)
        HandlerManager.remove_handler("discord_raw_message_delete", self.message_delete)
        HandlerManager.remove_handler("discord_member_update", self.member_update)
        HandlerManager.remove_handler("discord_guild_role_update", self.role_update)
        HandlerManager.remove_handler("discord_guild_role_create", self.role_create)
        HandlerManager.remove_handler("discord_guild_role_delete", self.role_delete)
        HandlerManager.remove_handler("discord_voice_state_update", self.voice_change)
        HandlerManager.remove_handler("discord_member_remove", self.member_remove)
        HandlerManager.remove_handler("discord_member_join", self.member_join)
        HandlerManager.remove_handler(
            "discord_guild_channel_update", self.channel_update
        )
        HandlerManager.remove_handler(
            "discord_guild_channel_create", self.channel_create
        )
        HandlerManager.remove_handler("discord_guild_update", self.guild_update)
        HandlerManager.remove_handler("discord_guild_emojis_update", self.emoji_update)
        HandlerManager.remove_handler("discord_invite_create", self.invite_create)
        HandlerManager.remove_handler("discord_invite_delete", self.invite_delete)
        HandlerManager.remove_handler("aml_custom_log", self.custom_event_log)
예제 #3
0
class LinkCheckerModule(BaseModule):

    ID = __name__.split(".")[-1]
    NAME = "Link Checker"
    DESCRIPTION = "Checks links if they're bad"
    ENABLED_DEFAULT = True
    CATEGORY = "Filter"
    SETTINGS = [
        ModuleSetting(
            key="ban_pleb_links",
            label="Disallow links from non-subscribers",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="ban_sub_links",
            label="Disallow links from subscribers",
            type="boolean",
            required=True,
            default=False,
        ),
        ModuleSetting(
            key="timeout_length",
            label="Timeout length",
            type="number",
            required=True,
            placeholder="Timeout length in seconds",
            default=60,
            constraints={"min_value": 1, "max_value": 3600},
        ),
        ModuleSetting(
            key="bypass_level",
            label="Level to bypass module",
            type="number",
            required=True,
            placeholder="",
            default=500,
            constraints={"min_value": 100, "max_value": 1000},
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.db_session = None
        self.links = {}

        self.blacklisted_links = []
        self.whitelisted_links = []

        self.cache = (
            LinkCheckerCache()
        )  # cache[url] = True means url is safe, False means the link is bad

        self.safe_browsing_api = None

    def enable(self, bot):
        if not bot:
            return

        HandlerManager.add_handler("on_message", self.on_message, priority=100)
        HandlerManager.add_handler("on_commit", self.on_commit)

        if self.db_session is not None:
            self.db_session.commit()
            self.db_session.close()
            self.db_session = None
        self.db_session = DBManager.create_session()
        self.blacklisted_links = []
        for link in self.db_session.query(BlacklistedLink):
            self.blacklisted_links.append(link)

        self.whitelisted_links = []
        for link in self.db_session.query(WhitelistedLink):
            self.whitelisted_links.append(link)

    def disable(self, bot):
        if not bot:
            return

        greenbot.managers.handler.HandlerManager.remove_handler(
            "on_message", self.on_message
        )
        greenbot.managers.handler.HandlerManager.remove_handler(
            "on_commit", self.on_commit
        )

        if self.db_session is not None:
            self.db_session.commit()
            self.db_session.close()
            self.db_session = None
            self.blacklisted_links = []
            self.whitelisted_links = []

    def reload(self):

        log.info(
            f"Loaded {len(self.blacklisted_links)} bad links and {len(self.whitelisted_links)} good links"
        )
        return self

    super_whitelist = []

    def on_message(self, source, whisper, urls, **rest):
        if whisper:
            return

        if source.level >= self.settings["bypass_level"] or source.moderator is True:
            return

        if len(urls) > 0:
            do_timeout = False
            ban_reason = "You are not allowed to post links in chat"
            whisper_reason = "??? KKona"

            if self.settings["ban_pleb_links"] is True and source.subscriber is False:
                do_timeout = True
                whisper_reason = "You cannot post non-verified links in chat if you're not a subscriber."
            elif self.settings["ban_sub_links"] is True and source.subscriber is True:
                do_timeout = True
                whisper_reason = (
                    "You cannot post non-verified links in chat if you're a subscriber"
                )

            if do_timeout is True:
                # Check if the links are in our super-whitelist.
                for url in urls:
                    parsed_url = Url(url)
                    if len(parsed_url.parsed.netloc.split(".")) < 2:
                        continue
                    whitelisted = False
                    for whitelist in self.super_whitelist:
                        if is_subdomain(parsed_url.parsed.netloc, whitelist):
                            whitelisted = True
                            break
                    if whitelisted is False and self.is_whitelisted(url):
                        whitelisted = True
                    if whitelisted is False:
                        self.bot.timeout(
                            source, self.settings["timeout_length"], reason=ban_reason
                        )
                        if source.time_in_chat_online >= timedelta(hours=1):
                            self.bot.whisper(source, whisper_reason)
                        return False

        for url in urls:
            # Action which will be taken when a bad link is found
            def action():
                self.bot.timeout(
                    source, self.settings["timeout_length"], reason="Banned link"
                )

            # First we perform a basic check
            if self.simple_check(url, action) == self.RET_FURTHER_ANALYSIS:
                # If the basic check returns no relevant data, we queue up a proper check on the URL
                self.bot.action_queue.submit(self.check_url, url, action)

    def on_commit(self, **rest):
        if self.db_session is not None:
            self.db_session.commit()

    def delete_from_cache(self, url):
        if url in self.cache:
            del self.cache[url]

    def cache_url(self, url, safe):
        if url in self.cache and self.cache[url] == safe:
            return

        self.cache[url] = safe
        self.bot.execute_delayed(20, self.delete_from_cache, url)

    def counteract_bad_url(
        self, url, action=None, want_to_cache=True, want_to_blacklist=False
    ):
        log.debug(f"LinkChecker: BAD URL FOUND {url.url}")
        if action:
            action()
        if want_to_cache:
            self.cache_url(url.url, False)
        if want_to_blacklist:
            self.blacklist_url(url.url, url.parsed)
            return True

    def blacklist_url(self, url, parsed_url=None, level=0):
        if not (
            url.lower().startswith("http://") or url.lower().startswith("https://")
        ):
            url = "http://" + url

        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)

        if self.is_blacklisted(url, parsed_url):
            return False

        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()

        if domain.startswith("www."):
            domain = domain[4:]
        if path.endswith("/"):
            path = path[:-1]
        if path == "":
            path = "/"

        link = BlacklistedLink(domain, path, level)
        self.db_session.add(link)
        self.blacklisted_links.append(link)
        self.db_session.commit()

    def whitelist_url(self, url, parsed_url=None):
        if not (
            url.lower().startswith("http://") or url.lower().startswith("https://")
        ):
            url = "http://" + url
        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)
        if self.is_whitelisted(url, parsed_url):
            return

        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()

        if domain.startswith("www."):
            domain = domain[4:]
        if path.endswith("/"):
            path = path[:-1]
        if path == "":
            path = "/"

        link = WhitelistedLink(domain, path)
        self.db_session.add(link)
        self.whitelisted_links.append(link)
        self.db_session.commit()

    def is_blacklisted(self, url, parsed_url=None, sublink=False):
        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)
        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()
        if path == "":
            path = "/"

        domain_split = domain.split(".")
        if len(domain_split) < 2:
            return False

        for link in self.blacklisted_links:
            if link.is_subdomain(domain):
                if link.is_subpath(path):
                    if not sublink:
                        return True
                    elif (
                        link.level >= 1
                    ):  # if it's a sublink, but the blacklisting level is 0, we don't consider it blacklisted
                        return True

        return False

    def is_whitelisted(self, url, parsed_url=None):
        if parsed_url is None:
            parsed_url = urllib.parse.urlparse(url)
        domain = parsed_url.netloc.lower()
        path = parsed_url.path.lower()
        if path == "":
            path = "/"

        domain_split = domain.split(".")
        if len(domain_split) < 2:
            return False

        for link in self.whitelisted_links:
            if link.is_subdomain(domain):
                if link.is_subpath(path):
                    return True

        return False

    RET_BAD_LINK = -1
    RET_FURTHER_ANALYSIS = 0
    RET_GOOD_LINK = 1

    def basic_check(self, url, action, sublink=False):
        """
        Check if the url is in the cache, or if it's
        Return values:
        1 = Link is OK
        -1 = Link is bad
        0 = Link needs further analysis
        """
        if url.url in self.cache:
            if not self.cache[url.url]:  # link is bad
                self.counteract_bad_url(url, action, False, False)
                return self.RET_BAD_LINK
            return self.RET_GOOD_LINK

        if self.is_blacklisted(url.url, url.parsed, sublink):
            self.counteract_bad_url(url, action, want_to_blacklist=False)
            return self.RET_BAD_LINK

        if self.is_whitelisted(url.url, url.parsed):
            self.cache_url(url.url, True)
            return self.RET_GOOD_LINK

        return self.RET_FURTHER_ANALYSIS

    def simple_check(self, url, action):
        url = Url(url)
        if len(url.parsed.netloc.split(".")) < 2:
            # The URL is broken, ignore it
            return self.RET_FURTHER_ANALYSIS

        return self.basic_check(url, action)

    def check_url(self, url, action):
        url = Url(url)
        if len(url.parsed.netloc.split(".")) < 2:
            # The URL is broken, ignore it
            return

        try:
            self._check_url(url, action)
        except:
            log.exception("LinkChecker unhandled exception while _check_url")

    def _check_url(self, url, action):
        # XXX: The basic check is currently performed twice on links found in messages. Solve
        res = self.basic_check(url, action)
        if res == self.RET_GOOD_LINK:
            return
        elif res == self.RET_BAD_LINK:
            return

        connection_timeout = 2
        read_timeout = 1
        try:
            r = requests.head(
                url.url,
                allow_redirects=True,
                timeout=connection_timeout,
                headers={"User-Agent": self.bot.user_agent},
            )
        except:
            self.cache_url(url.url, True)
            return

        checkcontenttype = (
            "content-type" in r.headers
            and r.headers["content-type"] == "application/octet-stream"
        )
        checkdispotype = (
            "disposition-type" in r.headers
            and r.headers["disposition-type"] == "attachment"
        )

        if checkcontenttype or checkdispotype:  # triggering a download not allowed
            self.counteract_bad_url(url, action)
            return

        redirected_url = Url(r.url)
        if is_same_url(url, redirected_url) is False:
            res = self.basic_check(redirected_url, action)
            if res == self.RET_GOOD_LINK:
                return
            elif res == self.RET_BAD_LINK:
                return

        if self.safe_browsing_api and self.safe_browsing_api.is_url_bad(
            redirected_url.url
        ):  # harmful url detected
            log.debug("Google Safe Browsing API lists URL")
            self.counteract_bad_url(url, action, want_to_blacklist=False)
            self.counteract_bad_url(redirected_url, want_to_blacklist=False)
            return

        if "content-type" not in r.headers or not r.headers["content-type"].startswith(
            "text/html"
        ):
            return  # can't analyze non-html content
        maximum_size = 1024 * 1024 * 10  # 10 MB
        receive_timeout = 3

        html = ""
        try:
            response = requests.get(
                url=url.url,
                stream=True,
                timeout=(connection_timeout, read_timeout),
                headers={"User-Agent": self.bot.user_agent},
            )

            content_length = response.headers.get("Content-Length")
            if (
                content_length
                and int(response.headers.get("Content-Length")) > maximum_size
            ):
                log.error("This file is too big!")
                return

            size = 0
            start = pajbot.utils.now().timestamp()

            for chunk in response.iter_content(1024):
                if pajbot.utils.now().timestamp() - start > receive_timeout:
                    log.error("The site took too long to load")
                    return

                size += len(chunk)
                if size > maximum_size:
                    log.error("This file is too big! (fake header)")
                    return
                html += str(chunk)

        except requests.exceptions.ConnectTimeout:
            log.warning(f"Connection timed out while checking {url.url}")
            self.cache_url(url.url, True)
            return
        except requests.exceptions.ReadTimeout:
            log.warning(f"Reading timed out while checking {url.url}")
            self.cache_url(url.url, True)
            return
        except:
            log.exception("Unhandled exception")
            return

        try:
            soup = BeautifulSoup(html, "html.parser")
        except:
            return

        original_url = url
        original_redirected_url = redirected_url
        urls = []
        for link in soup.find_all("a"):  # get a list of links to external sites
            url = link.get("href")
            if url is None:
                continue
            if url.startswith("//"):
                urls.append("http:" + url)
            elif url.startswith("http://") or url.startswith("https://"):
                urls.append(url)

        for url in urls:  # check if the site links to anything dangerous
            url = Url(url)

            if is_subdomain(url.parsed.netloc, original_url.parsed.netloc):
                # log.debug('Skipping because internal link')
                continue

            res = self.basic_check(url, action, sublink=True)
            if res == self.RET_BAD_LINK:
                self.counteract_bad_url(url)
                self.counteract_bad_url(original_url, want_to_blacklist=False)
                self.counteract_bad_url(
                    original_redirected_url, want_to_blacklist=False
                )
                return
            elif res == self.RET_GOOD_LINK:
                continue

            try:
                r = requests.head(
                    url.url,
                    allow_redirects=True,
                    timeout=connection_timeout,
                    headers={"User-Agent": self.bot.user_agent},
                )
            except:
                continue

            redirected_url = Url(r.url)
            if not is_same_url(url, redirected_url):
                res = self.basic_check(redirected_url, action, sublink=True)
                if res == self.RET_BAD_LINK:
                    self.counteract_bad_url(url)
                    self.counteract_bad_url(original_url, want_to_blacklist=False)
                    self.counteract_bad_url(
                        original_redirected_url, want_to_blacklist=False
                    )
                    return
                elif res == self.RET_GOOD_LINK:
                    continue

            if self.safe_browsing_api and self.safe_browsing_api.is_url_bad(
                redirected_url.url
            ):  # harmful url detected
                log.debug(f"Evil sublink {url} by google API")
                self.counteract_bad_url(original_url, action)
                self.counteract_bad_url(original_redirected_url)
                self.counteract_bad_url(url)
                self.counteract_bad_url(redirected_url)
                return

        # if we got here, the site is clean for our standards
        self.cache_url(original_url.url, True)
        self.cache_url(original_redirected_url.url, True)
        return

    def load_commands(self, **options):
        self.commands["add"] = Command.multiaction_command(
            level=100,
            delay_all=0,
            delay_user=0,
            default=None,
            command="add",
            commands={
                "link": Command.multiaction_command(
                    command="add link",
                    level=500,
                    delay_all=0,
                    delay_user=0,
                    default=None,
                    commands={
                        "blacklist": Command.raw_command(
                            self.add_link_blacklist,
                            command="add link blacklist",
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Blacklist a link",
                            examples=[
                                CommandExample(
                                    None,
                                    "Add a link to the blacklist for a shallow search",
                                    chat="user:!add link blacklist --shallow scamlink.lonk/\n"
                                    "bot>user:Successfully added your links",
                                    description="Added the link scamlink.lonk/ to the blacklist for a shallow search",
                                ).parse(),
                                CommandExample(
                                    None,
                                    "Add a link to the blacklist for a deep search",
                                    chat="user:!add link blacklist --deep scamlink.lonk/\n"
                                    "bot>user:Successfully added your links",
                                    description="Added the link scamlink.lonk/ to the blacklist for a deep search",
                                ).parse(),
                            ],
                        ),
                        "whitelist": Command.raw_command(
                            self.add_link_whitelist,
                            command="add link whitelist",
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Whitelist a link",
                            examples=[
                                CommandExample(
                                    None,
                                    "Add a link to the whitelist",
                                    chat="user:!add link whitelink safelink.lonk/\n"
                                    "bot>user:Successfully added your links",
                                    description="Added the link safelink.lonk/ to the whitelist",
                                ).parse()
                            ],
                        ),
                    },
                )
            },
        )

        self.commands["remove"] = Command.multiaction_command(
            level=100,
            delay_all=0,
            delay_user=0,
            default=None,
            command="remove",
            commands={
                "link": Command.multiaction_command(
                    command="remove link",
                    level=500,
                    delay_all=0,
                    delay_user=0,
                    default=None,
                    commands={
                        "blacklist": Command.raw_command(
                            self.remove_link_blacklist,
                            command="remove link blacklist",
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Remove a link from the blacklist.",
                            examples=[
                                CommandExample(
                                    None,
                                    "Remove a link from the blacklist.",
                                    chat="user:!remove link blacklist 20\n"
                                    "bot>user:Successfully removed blacklisted link with id 20",
                                    description="Remove a link from the blacklist with an ID",
                                ).parse()
                            ],
                        ),
                        "whitelist": Command.raw_command(
                            self.remove_link_whitelist,
                            command="remove link whitelist",
                            level=500,
                            delay_all=0,
                            delay_user=0,
                            description="Remove a link from the whitelist.",
                            examples=[
                                CommandExample(
                                    None,
                                    "Remove a link from the whitelist.",
                                    chat="user:!remove link whitelist 12\n"
                                    "bot>user:Successfully removed blacklisted link with id 12",
                                    description="Remove a link from the whitelist with an ID",
                                ).parse()
                            ],
                        ),
                    },
                )
            },
        )

    def add_link_blacklist(self, bot, source, message, **rest):
        options, new_links = self.parse_link_blacklist_arguments(message)

        if new_links:
            parts = new_links.split(" ")
            try:
                for link in parts:
                    if len(link) > 1:
                        self.blacklist_url(link, **options)
                        AdminLogManager.post(
                            "Blacklist link added", source.discord_id, link
                        )
                bot.whisper(source, "Successfully added your links")
                return True
            except:
                log.exception("Unhandled exception in add_link_blacklist")
                bot.whisper(source, "Some error occurred while adding your links")
                return False
        else:
            bot.whisper(source, "Usage: !add link blacklist LINK")
            return False

    def add_link_whitelist(self, bot, source, message, **rest):
        parts = message.split(" ")
        try:
            for link in parts:
                self.whitelist_url(link)
                AdminLogManager.post("Whitelist link added", source.discord_id, link)
        except:
            log.exception("Unhandled exception in add_link")
            bot.whisper(source, "Some error occurred white adding your links")
            return False

        bot.whisper(source, "Successfully added your links")

    def remove_link_blacklist(self, bot, source, message, **rest):
        if not message:
            bot.whisper(source, "Usage: !remove link blacklist ID")
            return False

        id = None
        try:
            id = int(message)
        except ValueError:
            pass

        link = self.db_session.query(BlacklistedLink).filter_by(id=id).one_or_none()

        if link:
            self.blacklisted_links.remove(link)
            self.db_session.delete(link)
            self.db_session.commit()
        else:
            bot.whisper(source, "No link with the given id found")
            return False

        AdminLogManager.post("Blacklist link removed", source.discord_id, link.domain)
        bot.whisper(source, f"Successfully removed blacklisted link with id {link.id}")

    def remove_link_whitelist(self, bot, source, message, **rest):
        if not message:
            bot.whisper(source, "Usage: !remove link whitelist ID")
            return False

        id = None
        try:
            id = int(message)
        except ValueError:
            pass

        link = self.db_session.query(WhitelistedLink).filter_by(id=id).one_or_none()

        if link:
            self.whitelisted_links.remove(link)
            self.db_session.delete(link)
            self.db_session.commit()
        else:
            bot.whisper(source, "No link with the given id found")
            return False

        AdminLogManager.post("Whitelist link removed", source.discord_id, link.domain)
        bot.whisper(source, f"Successfully removed whitelisted link with id {link.id}")

    @staticmethod
    def parse_link_blacklist_arguments(message):
        parser = argparse.ArgumentParser()
        parser.add_argument("--deep", dest="level", action="store_true")
        parser.add_argument("--shallow", dest="level", action="store_false")
        parser.set_defaults(level=False)

        try:
            args, unknown = parser.parse_known_args(message.split())
        except SystemExit:
            return False, False
        except:
            log.exception("Unhandled exception in add_link_blacklist")
            return False, False

        # Strip options of any values that are set as None
        options = {k: v for k, v in vars(args).items() if v is not None}
        response = " ".join(unknown)

        if "level" in options:
            options["level"] = int(options["level"])

        return options, response
예제 #4
0
class RoleToLevel(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "RoleToLevel"
    DESCRIPTION = "Gives level based on roles in discord"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="level",
            label="Level required to manage roles",
            type="number",
            placeholder=1500,
            default=1500,
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.redis = RedisManager.get()
        if self.bot:
            self.bot.roles = {}

    async def add_role_level(self, bot, author, channel, message, args):
        command_args = message.split(" ") if message else []
        role = self.bot.filters.get_role([command_args[0]], None, {})[0]
        if not role:
            await self.bot.say(channel, f"Invalid role id {command_args[0]}")
            return

        try:
            self.bot.roles = json.loads(
                self.redis.get(f"{self.bot.bot_name}:role-level")
            )
        except:
            self.redis.set(f"{self.bot.bot_name}:role-level", json.dumps({}))
            self.bot.roles = {}

        if str(role.id) in self.bot.roles:
            await self.bot.say(
                channel,
                f"Level, {self.bot.roles.get(str(role.id))} has already been assigned to a {role.mention}",
            )
            return
        level = int(command_args[1])
        if level >= args["user_level"]:
            await self.bot.say(
                channel, f"You cant set a level higher then your current level"
            )
            return
        self.bot.roles[str(role.id)] = level
        self.redis.set(f"{self.bot.bot_name}:role-level", json.dumps(self.bot.roles))
        await self.bot.say(
            channel, f"Level, {command_args[1]} assigned to role, {role.mention}"
        )

    async def remove_role_level(self, bot, author, channel, message, args):
        command_args = message.split(" ") if message else []
        if command_args[0] in self.bot.roles:
            del self.bot.roles[str(command_args[0])]
            self.redis.set(
                f"{self.bot.bot_name}:role-level", json.dumps(self.bot.roles)
            )
            await self.bot.say(channel, f"Removed role with id {command_args[0]}")
            return
        role = self.bot.filters.get_role([command_args[0]], None, {})[0]
        if not role:
            await self.bot.say(channel, f"Invalid role id {command_args[0]}")
            return

        try:
            self.bot.roles = json.loads(
                self.redis.get(f"{self.bot.bot_name}:role-level")
            )
        except:
            self.redis.set(f"{self.bot.bot_name}:role-level", json.dumps({}))
            self.bot.roles = {}

        if str(role.id) not in self.bot.roles:
            await self.bot.say(channel, f"{role.mention} doesnt have a level assigned")
            return

        del self.bot.roles[str(role.id)]

        self.redis.set(f"{self.bot.bot_name}:role-level", json.dumps(self.bot.roles))
        await self.bot.say(channel, f"{role.mention} no longer has a level")

    def load_commands(self, **options):
        self.commands["addrolelevel"] = Command.raw_command(
            self.add_role_level,
            command="addrolelevel",
            delay_all=0,
            delay_user=0,
            level=int(self.settings["level"]),
            can_execute_with_whisper=True,
            description="Adds Level to role",
        )
        self.commands["removerolelevel"] = Command.raw_command(
            self.remove_role_level,
            command="removerolelevel",
            delay_all=0,
            delay_user=0,
            level=int(self.settings["level"]),
            can_execute_with_whisper=True,
            description="Removes Level from role",
        )

    def enable(self, bot):
        if not bot:
            return
        try:
            self.bot.roles = json.loads(
                self.redis.get(f"{self.bot.bot_name}:role-level")
            )
        except:
            self.redis.set(f"{self.bot.bot_name}:role-level", json.dumps({}))
            self.bot.roles = {}

    def disable(self, bot):
        if not bot:
            return
        self.bot.roles = {}
예제 #5
0
class ActivityTracker(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "RemindMe"
    DESCRIPTION = "Allows users to create reminders"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="max_reminders_per_user",
            label="Maximum reminders per user",
            type="int",
            placeholder="",
            default="3",
        ),
        ModuleSetting(
            key="cost",
            label="Points required to add a reminder",
            type="number",
            placeholder="",
            default="0",
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.redis = RedisManager.get()
        self.reminder_tasks = {}

    def create_reminder(self, bot, author, channel, message, args):
        pass

    def myreminders(self, bot, author, channel, message, args):
        pass

    def forgetme(self, bot, author, channel, message, args):
        pass

    def load_commands(self, **options):
        self.commands["remindme"] = Command.raw_command(
            self.create_reminder,
            delay_all=0,
            delay_user=0,
            cost=self.settings["cost"],
            can_execute_with_whisper=False,
            description="Creates a reminder",
        )
        self.commands["myreminders"] = Command.raw_command(
            self.myreminders,
            delay_all=0,
            delay_user=0,
            cost=self.settings["cost"],
            description="Creates a reminder",
        )
        self.commands["forgetme"] = Command.raw_command(
            self.forgetme,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=False,
            description="Creates a reminder",
        )

    def execute_reminder(self, salt, reminder):
        self.reminder_tasks.pop(salt)

    def enable(self, bot):
        if not bot:
            return
        try:
            reminders_list = json.loads(self.redis.get("remind-me-reminders"))
            """
            { 
                user_id: {
                    "message_id": message_id,
                    "message": message,
                    "date_of_reminder": date_of_reminder,
                },
            }
            """
        except:
            self.redis.set("remind-me-reminders", json.dumps({}))
            reminders_list = {}
        new_reminders_list = {}
        for user in reminders_list:
            user_reminders = reminders_list[user]
            new_user_reminders = []
            for reminder in user_reminders:
                salt = random_string()
                date_of_reminder = reminder["date_of_reminder"]
                if ":" in date_of_reminder[-5:]:
                    date_of_reminder = f"{date_of_reminder[:-5]}{date_of_reminder[-5:-3]}{date_of_reminder[-2:]}"
                date_of_reminder = datetime.strptime(date_of_reminder, "%Y-%m-%d %H:%M:%S.%f%z")
                if date_of_reminder < utils.now():
                    continue
                new_user_reminders.append(reminder)
                self.reminder_tasks[salt] = ScheduleManager.execute_delayed((date_of_reminder-utils.now()).seconds, self.execute_reminder, args=[salt, reminder])

    def disable(self, bot):
        if not bot:
            return
예제 #6
0
class ActivityTracker(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "ActivityTracker"
    DESCRIPTION = "Gives points for being active"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="sub_role_id",
            label="ID of the sub role",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="regular_role_id",
            label="ID of the regular role",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="channels_to_listen_in",
            label="Channel IDs to listen in seperated by a ' '",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="hourly_credit",
            label=
            "If he wrote enough messages this hour, the hour will be credited with this many points",
            type="number",
            placeholder="",
            default="8",
        ),
        ModuleSetting(
            key="daily_max_msgs",
            label="Maximum hours(msgs) that can be credited per day",
            type="number",
            placeholder="",
            default="12",
        ),
        ModuleSetting(
            key="daily_limit",
            label="If user reached daily max msgs he will get this many points",
            type="number",
            placeholder="",
            default="10",
        ),
        ModuleSetting(
            key="min_msgs_per_week",
            label=
            "If user can't keep up this many credited hours per week he loses the role",
            type="number",
            placeholder="",
            default="10",
        ),
        ModuleSetting(
            key="min_regular_points",
            label="Points required for the regular role",
            type="number",
            placeholder="",
            default="10",
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.process_messages_job = None

    async def process_messages(self):
        with DBManager.create_session_scope() as db_session:
            regular_role = self.bot.filters.get_role(
                [self.settings["regular_role_id"]], None, {})[0]
            sub_role = self.bot.filters.get_role(
                [self.settings["sub_role_id"]], None, {})[0]
            counts_by_week = Message._get_week_count_by_user(db_session)
            for member in regular_role.members:
                count = counts_by_week.get(str(member.id), 0)
                if (count < self.settings["min_msgs_per_week"]
                        or sub_role not in member.roles):
                    await self.bot.remove_role(
                        member, regular_role,
                        "They failed to meet the requirements to keep the role"
                    )
            channels_to_listen_in = (
                self.settings["channels_to_listen_in"].split(" ")
                if len(self.settings["channels_to_listen_in"]) != 0 else None)
            messages = Message._get_last_hour(db_session,
                                              channels_to_listen_in)
            counts_by_day = Message._get_day_count_by_user(db_session)
            for message in messages:
                count = counts_by_day.get(message.user_id, 0)
                if message.user_id != str(self.bot.bot_id):
                    if count < self.settings["daily_max_msgs"] - 1:
                        message.user.points += self.settings["hourly_credit"]
                    elif count == self.settings["daily_max_msgs"] - 1:
                        message.user.points += self.settings["daily_limit"]
                message.credited = True
                counts_by_day[message.user_id] = count + 1

            for user in User._get_users_with_points(
                    db_session, self.settings["min_regular_points"]):
                member = self.bot.filters.get_member([user.discord_id], None,
                                                     {})[0]
                if not member:
                    continue

                count = counts_by_week.get(str(member.id), 0)
                if (sub_role not in member.roles
                        or (regular_role in member.roles
                            or count < self.settings["min_msgs_per_week"])):
                    continue
                await self.bot.add_role(
                    member, regular_role,
                    "They met the requirements to get the role")

    def enable(self, bot):
        if not bot:
            return
        self.process_messages_job = ScheduleManager.execute_every(
            3600, self.process_messages)  # Checks every hour

    def disable(self, bot):
        if not bot:
            return
        self.process_messages_job.remove()
        self.process_messages_job = None
예제 #7
0
class Twitter(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Twitter"
    DESCRIPTION = "Fetches Tweets from users and displays them"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="users",
            label="Users to follow",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="output_format",
            label="Format for output, where {username} is the username and {tweet_url} is the url of the tweet",
            type="text",
            placeholder="@here {username} just tweeted {tweet_url}",
            default="@here {username} just tweeted {tweet_url}",
        ),
        ModuleSetting(
            key="output_channel",
            label="Channel ID to output to",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="send_replies",
            label="Log Replies to Tweets",
            type="boolean",
            placeholder="",
            default=True,
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.redis = RedisManager.get()
        self.stream = None
        self.process = None
        if not self.bot:
            return

    async def on_status(self, tweet):
        try:
            if tweet.in_reply_to_status_id is not None:
                if not self.settings["send_replies"]:
                    return
            username = tweet.author.screen_name
            tweet_url = f"https://twitter.com/{username}/status/{tweet.id}"
            out_channel = self.bot.filters.get_channel([int(self.settings["output_channel"])], None, {})[0]
            message = self.settings["output_format"].format(
                username=username, tweet_url=tweet_url
            )
            await self.bot.say(channel=out_channel, message=message, ignore_escape=True)
        except Exception as e:
            log.error(e)


    def load_commands(self, **options):
        if not self.bot:
            return

        ScheduleManager.execute_now(self.update_manager)

    async def update_manager(self):
        await HandlerManager.trigger("twitter_follows", usernames=self.settings["users"].split(" ") if self.settings["users"] else [])

    def get_users_to_follow(self, usernames):
        return [
            str(self.bot.twitter_manager.api.get_user(username).id)
            for username in usernames
        ]

    def enable(self, bot):
        if not bot:
            return
        HandlerManager.add_handler("twitter_on_status", self.on_status)

    def disable(self, bot):
        if not bot:
            return
        HandlerManager.remove_handler("twitter_on_status", self.on_status)
예제 #8
0
파일: memes.py 프로젝트: EMorf/greenbot
class Memes(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Memes"
    DESCRIPTION = "Fun Module"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="mod_role_id",
            label="Mod Role ID",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="cost_mod_pride",
            label="Points required to execute the modpride command",
            type="number",
            placeholder="",
            default=0,
        ),
        ModuleSetting(
            key="dank_role_id",
            label="Dank Role ID",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="cost_dank",
            label="Points required to execute the dank command",
            type="number",
            placeholder="",
            default=0,
        ),
        ModuleSetting(
            key="dank_cooldown",
            label="Cooldown for the dank command",
            type="number",
            placeholder="",
            default=0,
        ),
        ModuleSetting(
            key="emote_for_vroom",
            label="Emote used for vroom",
            type="text",
            placeholder=":wheelchair:",
            default=":wheelchair:",
        ),
        ModuleSetting(
            key="max_vroom_races",
            label="Max vroom races",
            type="number",
            placeholder="",
            default=3,
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.mod_pride_running = False
        self.vroom_races = []

    async def modpride(self, bot, author, channel, message, args):
        if self.mod_pride_running:
            return False
        self.mod_pride_running = True
        role = self.bot.discord_bot.guild.get_role(
            int(self.settings["mod_role_id"]))
        r, g, b = role.color.to_rgb()
        for c in rainbowcolors:
            dcol = discord.Colour(c)
            await asyncio.sleep(0.2)
            await role.edit(colour=dcol)
        await role.edit(colour=discord.Colour.from_rgb(r, g, b))
        self.mod_pride_running = False
        return True

    async def vroom(self, bot, author, channel, message, args):
        if len(self.vroom_races) < self.settings["max_vroom_races"]:
            start_time = utils.now()
            m = await self.bot.say(channel=channel,
                                   message="﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏")
            self.vroom_races.append(m)
            await asyncio.sleep(0.5)
            await m.edit(
                content=f"{self.settings['emote_for_vroom']}﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏﹏"
            )
            for _ in range(19):
                newtick = m.content[:-1]
                newtick = "﹏" + newtick
                await asyncio.sleep(random.randint(5, 30) / 10)
                await m.edit(content=newtick)
            elapsed_time = utils.now() - start_time
            await m.edit(
                content=
                f"{author.mention} finished in {round(elapsed_time.total_seconds(), 2)}s"
            )
            self.vroom_races.remove(m)
        else:
            await self.bot.say(
                channel=channel,
                message=
                f"{author.mention} there can only be up to {self.settings['max_vroom_races']} races at the same time. Try later...",
                ignore_escape=True,
            )

    async def dank(self, bot, author, channel, message, args):
        role = self.bot.discord_bot.guild.get_role(
            int(self.settings["dank_role_id"]))
        dcol = discord.Colour.from_rgb(random.randint(0, 255),
                                       random.randint(0, 255),
                                       random.randint(0, 255))
        await role.edit(colour=dcol)
        return True if role not in (self.bot.filters.get_member(
            [author.id], None, {})[0].roles) else "return currency"

    def load_commands(self, **options):
        if self.settings["mod_role_id"]:
            self.commands["modpride"] = Command.raw_command(
                self.modpride,
                command="modpride",
                delay_all=0,
                delay_user=0,
                cost=self.settings["cost_mod_pride"],
                can_execute_with_whisper=False,
                description="KappaPride Mods",
            )
        if self.settings["dank_role_id"]:
            self.commands["dank"] = Command.raw_command(
                self.dank,
                command="dank",
                delay_all=self.settings["dank_cooldown"],
                delay_user=self.settings["dank_cooldown"],
                cost=self.settings["cost_dank"],
                can_execute_with_whisper=False,
                description="Messes with the dank roles color",
            )
        self.commands["vroom"] = Command.raw_command(
            self.vroom,
            command="vroom",
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=False,
            description="VROOOOOOOOOOM",
        )

    def enable(self, bot):
        if not bot:
            return

    def disable(self, bot):
        if not bot:
            return
예제 #9
0
파일: giveaway.py 프로젝트: EMorf/greenbot
class GiveawayModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Giveaway"
    DESCRIPTION = "Create Giveaways"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="level",
            label="Level required to start and stop giveaways",
            type="number",
            placeholder="500",
            default=500,
        ),
        ModuleSetting(
            key="regular_role_id",
            label="Role ID for regular role",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="regular_role_tickets",
            label="Regular role tickets",
            type="number",
            placeholder="",
            default=5,
        ),
        ModuleSetting(
            key="tier1_sub_role_id",
            label="Role ID for tier 1 sub role",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="tier1_sub_role_tickets",
            label="Tier 1 sub role tickets",
            type="number",
            placeholder="",
            default=1,
        ),
        ModuleSetting(
            key="tier2_sub_role_id",
            label="Role ID for tier 2 sub role",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="tier2_sub_role_tickets",
            label="Tier 2 sub role tickets",
            type="number",
            placeholder="",
            default=2,
        ),
        ModuleSetting(
            key="tier3_sub_role_id",
            label="Role ID for tier 3 sub role",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="tier3_sub_role_tickets",
            label="Tier 3 sub role tickets",
            type="number",
            placeholder="",
            default=4,
        ),
        ModuleSetting(
            key="valid_channels",
            label="Valid channels for typing commands",
            type="text",
            placeholder="",
            default="",
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot

    async def giveaway_info(self, bot, author, channel, message, args):
        embed = discord.Embed(description=("Giveaway Info"),
                              colour=discord.Colour.dark_gold())
        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            embed.add_field(
                name=("Current Giveaway"),
                value=
                f"**{current_giveaway.giveaway_item}** ending **{current_giveaway.giveaway_deadline}**"
                if current_giveaway else "No giveaway is running right now",
                inline=False,
            )
        role_dict = {
            "regular_role_id": self.settings["regular_role_tickets"],
            "tier1_sub_role_id": self.settings["tier1_sub_role_tickets"],
            "tier2_sub_role_id": self.settings["tier2_sub_role_tickets"],
            "tier3_sub_role_id": self.settings["tier3_sub_role_tickets"],
        }
        role_dict = dict(sorted(role_dict.items(), key=operator.itemgetter(1)))
        chances_value = "@everyone 1 entry\n"
        for role_name in role_dict:
            role_id = self.settings[role_name]
            if not role_id:
                continue

            role = self.bot.filters.get_role([role_id], None,
                                             {})[0] if role_id else None
            if not role:
                continue
            entries = role_dict[role_name]
            chances_value += f"{role.mention} {entries} entr{'ies' if entries > 1 else 'y'}\n"
        embed.add_field(
            name=("Chances to win!"),
            value=chances_value[:-1],
            inline=False,
        )

        await self.bot.say(channel=channel, embed=embed)

    async def giveaway_join(self, bot, author, channel, message, args):
        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            if not current_giveaway:
                await self.bot.say(
                    channel=channel,
                    message=
                    f"{author.mention}, there is no giveaway running right now.",
                    ignore_escape=True)
                return False

            if current_giveaway.locked:
                await self.bot.say(
                    channel=channel,
                    message=
                    f"{author.mention}, the current giveaway is locked.",
                    ignore_escape=True)
                return False

            registered = GiveawayEntry.is_entered(db_session, str(author.id),
                                                  current_giveaway.id)
            if registered:
                await self.bot.say(
                    channel=channel,
                    message=
                    f"{author.mention}, you already joined the giveaway.",
                    ignore_escape=True)
                return False

            tickets = self.get_highest_ticket_count(author)
            giveaway_entry = GiveawayEntry._create(db_session, str(author.id),
                                                   current_giveaway.id,
                                                   tickets)
            if giveaway_entry:
                await self.bot.say(
                    channel=channel,
                    message=
                    f"{author.mention}, you joined the giveaway for **{current_giveaway.giveaway_item}** with **{tickets}** entr{'y' if tickets == 1 else 'ies' }! The giveaway will end **{current_giveaway.giveaway_deadline}** and you will be notified if you win. Good Luck! :wink:",
                    ignore_escape=True)
                return True

            await self.bot.say(
                channel=channel,
                message=
                f"{author.mention} failed to add you to the giveaway dm a mod for help :smile:",
                ignore_escape=True)
            return False

    def get_highest_ticket_count(self, member):
        role_dict = {
            "regular_role_id": self.settings["regular_role_tickets"],
            "tier1_sub_role_id": self.settings["tier1_sub_role_tickets"],
            "tier2_sub_role_id": self.settings["tier2_sub_role_tickets"],
            "tier3_sub_role_id": self.settings["tier3_sub_role_tickets"],
        }
        role_dict = dict(sorted(role_dict.items(), key=operator.itemgetter(1)))

        tickets = 1
        for role_name in role_dict:
            role_id = self.settings[role_name]
            if not role_id:
                continue

            role = self.bot.filters.get_role([role_id], None,
                                             {})[0] if role_id else None
            if not role:
                continue

            tickets = max(tickets, role_dict[role_name]
                          ) if role and role in member.roles else tickets

        return tickets

    async def giveaway_start(self, bot, author, channel, message, args):
        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            if current_giveaway:
                await self.bot.say(
                    channel=channel,
                    message=
                    "There is already a giveaway running. Please use !wipegiveaway before you start a new one. (Don't forget to chose a winner before you end!)",
                    ignore_escape=True)
                return False

            desc_array = re.findall(r'"([^"]*)"', message)
            if not len(desc_array) == 2:
                await self.bot.say(
                    channel=channel,
                    message=
                    'Please set 2 arguments between quotation marks. `!startgiveaway "<item>" "<deadline>"`\nExample: `!startgiveaway "a new GTX2900" "10 days"`',
                    ignore_escape=True)
                return False

            if Giveaway._create(db_session, str(author.id), desc_array[0],
                                desc_array[1]):
                await self.bot.say(
                    channel=channel,
                    message=
                    "New giveaway was started! Use !giveawaywinner to chose a winner when the time has passed.",
                    ignore_escape=True)
                return True

            await self.bot.say(
                channel=channel,
                message=
                "An unknown error has occurred, please contact a moderator",
                ignore_escape=True)
            return False

    async def giveaway_wipe(self, bot, author, channel, message, args):
        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            if not current_giveaway:
                await self.bot.say(channel=channel,
                                   message="There is no giveaway running.")
                return False
            current_giveaway._disable(db_session)
        await self.bot.say(channel=channel,
                           message="The current giveaway has been wiped")
        return True

    async def giveaway_winner(self, bot, author, channel, message, args):
        args_split = message.split(" ")
        try:
            count = int(args_split[0])
        except:
            count = 1

        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            if not current_giveaway:
                await self.bot.say(channel=channel,
                                   message="There is no giveaway running.")
                return False

            if not current_giveaway.locked:
                current_giveaway._lock_state(db_session, True)
                db_session.commit()
            pool = []
            for entry in current_giveaway.entries:
                member = self.bot.filters.get_member([int(entry.user_id)],
                                                     None, {})[0]
                for _ in range(
                        max(entry.tickets,
                            self.get_highest_ticket_count(member))):
                    pool.append(entry)

            winning_users = []
            while len(winning_users) < count:
                if len(pool) == 0:
                    break
                winning_entry = random.choice(pool)
                winning_user = self.bot.filters.get_member(
                    [int(winning_entry.user_id)], None, {})[0]
                if winning_user and winning_user not in winning_users:
                    winning_users.append(winning_user)
                    winning_entry._remove(db_session)
                    pool = list(filter(lambda x: x != winning_entry, pool))

            if len(winning_users) == 0:
                await self.bot.say(
                    channel=channel,
                    message="The giveaway ended but nobody entered!",
                    ignore_escape=True)
                return True

            await self.bot.say(channel=channel,
                               message="Shuffling giveaway list...",
                               ignore_escape=True)
            await asyncio.sleep(5)
            await self.bot.say(channel=channel,
                               message="*Shuffling intensifies...*",
                               ignore_escape=True)
            await asyncio.sleep(5)
            await self.bot.say(
                channel=channel,
                message=
                f"**And the winner{'s are' if count > 1 else ' is'}...**",
                ignore_escape=True)
            await asyncio.sleep(5)
            for winning_user in winning_users:
                await self.bot.say(
                    channel=channel,
                    message=
                    f"Congratulations {winning_user.mention} you won **{current_giveaway.giveaway_item}**!!!",
                    ignore_escape=True)

        return True

    async def giveaway_lock(self, bot, author, channel, message, args):
        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            if not current_giveaway:
                await self.bot.say(channel=channel,
                                   message="There is no giveaway running.")
                return False

            if current_giveaway.locked:
                await self.bot.say(
                    channel=channel,
                    message="The current giveaway has already been locked")
                return False

            current_giveaway.locked = True
        await self.bot.say(channel=channel,
                           message="The current giveaway has been locked")
        return True

    async def giveaway_unlock(self, bot, author, channel, message, args):
        with DBManager.create_session_scope() as db_session:
            current_giveaway = Giveaway._get_current_giveaway(db_session)
            if not current_giveaway:
                await self.bot.say(channel=channel,
                                   message="There is no giveaway running.")
                return False

            if not current_giveaway.locked:
                await self.bot.say(
                    channel=channel,
                    message="The current giveaway is not locked")
                return False

            current_giveaway.locked = False
        await self.bot.say(channel=channel,
                           message="The current giveaway has been unlocked")
        return True

    def load_commands(self, **options):
        self.commands["giveaway"] = Command.multiaction_command(
            delay_all=0,
            delay_user=0,
            default="join",
            can_execute_with_whisper=False,
            command="giveaway",
            commands={
                "join":
                Command.raw_command(
                    self.giveaway_join,
                    command="giveaway join",
                    delay_all=0,
                    delay_user=0,
                    channels=json.dumps(
                        self.settings["valid_channels"].split(" ")),
                    can_execute_with_whisper=False,
                    description="Joins the current giveaway",
                ),
                "info":
                Command.raw_command(
                    self.giveaway_info,
                    command="giveaway info",
                    delay_all=0,
                    delay_user=0,
                    channels=json.dumps(
                        self.settings["valid_channels"].split(" ")),
                    can_execute_with_whisper=False,
                    description="Info about the current giveaway",
                )
            },
        )
        self.commands["startgiveaway"] = Command.raw_command(
            self.giveaway_start,
            command="startgiveaway",
            delay_all=0,
            delay_user=0,
            level=self.settings["level"],
            channels=json.dumps(self.settings["valid_channels"].split(" ")),
            can_execute_with_whisper=False,
            description="Start a giveaway",
        )
        self.commands["wipegiveaway"] = Command.raw_command(
            self.giveaway_wipe,
            command="wipegiveaway",
            delay_all=0,
            delay_user=0,
            level=self.settings["level"],
            channels=json.dumps(self.settings["valid_channels"].split(" ")),
            can_execute_with_whisper=False,
            description="Clears the current giveaway",
        )
        self.commands["giveawaywinner"] = Command.raw_command(
            self.giveaway_winner,
            command="giveawaywinner",
            delay_all=0,
            delay_user=0,
            level=self.settings["level"],
            channels=json.dumps(self.settings["valid_channels"].split(" ")),
            can_execute_with_whisper=False,
            description="Chooses a winner(s) for the current giveaway",
        )
        self.commands["lockgiveaway"] = Command.raw_command(
            self.giveaway_lock,
            command="lockgiveaway",
            delay_all=0,
            delay_user=0,
            level=self.settings["level"],
            channels=json.dumps(self.settings["valid_channels"].split(" ")),
            can_execute_with_whisper=False,
            description="Locks the current giveaway",
        )
        self.commands["unlockgiveaway"] = Command.raw_command(
            self.giveaway_unlock,
            command="unlockgiveaway",
            delay_all=0,
            delay_user=0,
            level=self.settings["level"],
            channels=json.dumps(self.settings["valid_channels"].split(" ")),
            can_execute_with_whisper=False,
            description="Unlocks the current giveaway",
        )

    def enable(self, bot):
        if not bot:
            return

    def disable(self, bot):
        if not bot:
            return
예제 #10
0
class MovieNight(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "MovieNight"
    DESCRIPTION = "Fun Module"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="cdn_server_address",
            label="Server address for cdn",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="cdn_stream_key",
            label="Stream key for cdn",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="cdn_username",
            label="Username for cdn",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="cdn_password",
            label="Password for cdn",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="player_domain",
            label="Playser Hosting Domain",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="logo_url",
            label="Discord Bot Logo URL",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="alert_channel_id",
            label="Alert Channel ID",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="level",
            label="Level required to execute mod commands",
            type="number",
            placeholder="500",
            default=500,
        ),
        ModuleSetting(
            key="valid_channels",
            label="Channel IDs commands can be executed in",
            type="text",
            placeholder="",
            default="",
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot

    async def movienight(self, bot, author, channel, message, args):
        # query api, returns is_online, is_ull, key 
        is_online, is_ull, key = self.bot.movienight_api.is_online()
        if not is_online:
            await self.bot.say(channel=channel, message="Movienight is offline right now :(")
            return False

        if is_ull:
            embed = discord.Embed(
                title="Movienight is online!",
                color=0x0600FF,
                description="Try the normal player if you are having problems with the low latency one.",
            )
            if self.settings["logo_url"]:
                embed.set_thumbnail(url=self.settings["logo_url"])
            embed.add_field(
                name="Web (low latency):",
                value=f"https://{self.settings['player_domain']}/ull_player.html?key={key}",
                inline=False,
            )
            embed.add_field(
                name="Web:",
                value=f"https://{self.settings['player_domain']}/ull_player-hls-forced.html?key={key}",
                inline=True,
            )
        else:
            embed = discord.Embed(
                title="Movienight is online!",
                color=0x0600FF,
                description="Use the following link to watch: ",
            )
            if self.settings["logo_url"]:
                embed.set_thumbnail(url=self.settings["logo_url"])
            embed.add_field(
                name="Web:",
                value=f"https://{self.settings['player_domain']}/cdn_player.html?key={key}",
                inline=True,
            )
        await self.bot.say(channel=channel, embed=embed)
        return True

    async def moviestart_ull(self, bot, author, channel, message, args):
        await self.bot.private_message(user=author, message="Starting movienight ull, this usually takes less than a minute...")
        server, stream_key = await self.bot.movienight_api.create_ull_target()

        embed = discord.Embed(
            title="A new Wowza ULL target has been created! Use the following OBS settings:",
            color=0x008000,
        )
        embed.set_author(name="Movienight (ULL)")
        embed.add_field(name="Server:", value=server, inline=False)
        embed.add_field(name="Stream Key:", value=stream_key, inline=False)
        embed.add_field(name="Authentication:", value="Disabled", inline=False)

        await self.bot.private_message(user=author, embed=embed)
        return True

    async def moviestart_cdn(self, bot, author, channel, message, args):
        await self.bot.private_message(user=author, message="Starting transcoder, this usually takes less than a minute...")

        start_transcoder = await self.bot.movienight_api.start_cdn_target()
        if start_transcoder:
            embed = discord.Embed(
                title="Wowza CDN target ready. Here are the OBS settings:",
                color=0x008000,
            )
            embed.set_author(name="Movienight (CDN)")
            embed.add_field(
                name="Server:", value=self.settings["cdn_server_address"] if self.settings["cdn_server_address"] else "Not Specified", inline=False #TODO
            )
            embed.add_field(name="Stream Key:", value=self.settings["cdn_stream_key"] if self.settings["cdn_stream_key"] else "Not Specified", inline=False)
            embed.add_field(name="Authentication:", value="Enabled", inline=False)
            embed.add_field(name="Username:"******"cdn_username"] if self.settings["cdn_username"] else "Not Specified", inline=False)
            embed.add_field(name="Password:"******"cdn_password"] if self.settings["cdn_password"] else "Not Specified", inline=False)

            await self.bot.private_message(user=author, message="Target Ready!", embed=embed)
            return True

        await self.bot.private_message(user=author, message="Was unable to start the transcoder, please check logs")
        return False

    async def movie_night_started_event(self):
        if not self.bot.movienight_api.active:
            log.error("API is not running!")
            return

        is_online, is_ull, key = self.bot.movienight_api.is_online()
        if not is_online:
            return

        out_chnanel = self.bot.filters.get_channel([self.settings["alert_channel_id"]], None, {})[0]
        if is_ull:
            embed = discord.Embed(
                title="Movienight is online!",
                color=0x0600FF,
                description="Use the normal player if you are having problems with the low latency one.",
            )
            embed.add_field(
                name="Web (low latency):",
                value=f"https://movie.admiralbulldog.live/ull_player.html?key={key}",
                inline=False,
            )
            embed.add_field(
                name="Web:",
                value=f"https://movie.admiralbulldog.live/ull_player-hls-forced.html?key={key}",
                inline=True,
            )
        else:
            embed = discord.Embed(
                title="Movienight is about to start!",
                color=0x0600FF,
                description="Use the following link to watch: ",
            )
            embed.add_field(
                name="Web:",
                value=f"https://movie.admiralbulldog.live/cdn_player.html?key={key}",
                inline=True,
            )
        if self.settings["logo_url"]:
            embed.set_thumbnail(url=self.settings["logo_url"])
        await self.bot.say(channel=out_chnanel, embed=embed)

    def load_commands(self, **options):
        self.commands["movienight"] = Command.raw_command(
            self.movienight,
            delay_all=0,
            delay_user=0,
            can_execute_with_whisper=False,
            channels=json.dumps(self.settings["valid_channels"].split(" ")),
            description="Shows thge movienight links",
        )
        self.commands["moviestart"] = Command.multiaction_command(
            delay_all=0,
            delay_user=0,
            default=None,
            can_execute_with_whisper=False,
            level=self.settings["level"],
            command="moviestart",
            commands={
                "ull": Command.raw_command(
                    self.moviestart_ull,
                    command="moviestart ull",
                    delay_all=0,
                    delay_user=0,
                    level=self.settings["level"],
                    can_execute_with_whisper=False,
                    channels=json.dumps(self.settings["valid_channels"].split(" ")),
                    description="Creates an ultra-low-latency target (lowest latency, source quality only)",
                ),
                "cdn": Command.raw_command(
                    self.moviestart_cdn,
                    command="moviestart cdn",
                    delay_all=0,
                    delay_user=0,
                    level=self.settings["level"],
                    can_execute_with_whisper=False,
                    channels=json.dumps(self.settings["valid_channels"].split(" ")),
                    description="Starts a normal cdn target with transcoder (higher latency, adaptive bitrate)",
                ),
            },
        ) 
        if not self.bot:
            return

        if not self.bot.movienight_api.active:
            log.error("API is not running!")
            return

    def enable(self, bot):
        if not bot:
            return
        if not self.bot.movienight_api.active:
            log.error("API is not running!")
            return
        
        HandlerManager.add_handler("movie_night_started", self.movie_night_started_event)

    def disable(self, bot):
        if not bot:
            return

        HandlerManager.remove_handler("movie_night_started", self.movie_night_started_event)
예제 #11
0
class TimeoutModule(BaseModule):
    ID = __name__.split(".")[-1]
    NAME = "Timeout"
    DESCRIPTION = "Allows moderators to timeout users"
    CATEGORY = "Feature"

    SETTINGS = [
        ModuleSetting(
            key="punished_role_id",
            label="Role ID given when a user is timedout",
            type="text",
            placeholder="",
            default="",
        ),
        ModuleSetting(
            key="level_for_command",
            label="Level required to timeout user",
            type="number",
            placeholder="",
            default=500,
        ),
        ModuleSetting(
            key="log_timeout",
            label="Log timeout Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_untimeout",
            label="Log untimeout Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
        ModuleSetting(
            key="log_timeout_update",
            label="Log timeout_update Event",
            type="boolean",
            placeholder="",
            default=True,
        ),
    ]

    def __init__(self, bot):
        super().__init__(bot)
        self.bot = bot
        self.redis = RedisManager.get()
        self.reminder_tasks = {}

    async def timeout_user(self, bot, author, channel, message,
                           args):  # !timeout <here> @username
        command_args = message.split(" ") if message else []

        if len(command_args) == 0:
            await self.bot.say(
                channel=channel,
                message=
                f"!timeout (here) <User mention> <duration> (reason...)",
            )
            return False

        member = self.bot.filters.get_member([command_args[0]], None, {})[0]
        if not member:
            await self.bot.say(channel=channel,
                               message=f"Cant find member, {command_args[0]}")
            return False

        with DBManager.create_session_scope() as db_session:
            user_level = self.bot.psudo_level_member(db_session, member)

            if user_level >= args["user_level"]:
                await self.bot.say(
                    channel=channel,
                    message=
                    f"You cannot timeout a member with a with a level the same or higher than you!",
                )
                return False

            timedelta = (utils.parse_timedelta(command_args[1])
                         if len(command_args) > 1 else None)
            ban_reason = (" ".join(command_args[1:])
                          if not timedelta else " ".join(command_args[2:]))
            success, resp = await self.bot.timeout_manager.timeout_user(
                db_session, member, author,
                (utils.now() + timedelta) if timedelta else None, ban_reason)
            duration = f"timedout for {utils.seconds_to_resp(timedelta.total_seconds())}" if timedelta else "permanently muted"
            if success:
                await self.bot.say(
                    channel=channel,
                    message=f"Member {member.mention} has been {duration}")
                return True

            await self.bot.say(channel=channel, message=resp)
            return False

    async def untimeout_user(self, bot, author, channel, message, args):
        command_args = message.split(" ") if message else []
        if len(command_args) == 0:
            await self.bot.say(
                channel=channel,
                message=f"!untimeout (here) <User mention> (reason...)")
            return False

        member = self.bot.filters.get_member([command_args[0]], None, {})[0]
        if not member:
            await self.bot.say(channel=channel,
                               message=f"Cant find member, {command_args[0]}")
            return False

        unban_reason = " ".join(command_args[1:])

        with DBManager.create_session_scope() as db_session:
            success, resp = await self.bot.timeout_manager.untimeout_user(
                db_session, member, author, unban_reason)
            if success:
                await self.bot.say(
                    channel=channel,
                    message=f"Member {member.mention} has been untimedout")
                return True

            self.bot.say(channel=channel, message=resp)
            return False

    async def query_timeouts(self, bot, author, channel, message, args):
        command_args = message.split(" ") if message else []
        member = self.bot.filters.get_member([command_args[0]], None, {})[0]
        if not member:
            await self.bot.say(channel=channel,
                               message=f"Cant find member, {command_args[0]}")
            return False
        with DBManager.create_session_scope() as db_session:
            timeouts = Timeout._by_user_id(db_session, str(member.id))

            if not timeouts:
                await self.bot.say(
                    channel=channel,
                    message=f"The user {member} has no timeouts")
                return True

            for timeout in timeouts:
                embed = discord.Embed(
                    description=f"Timeout #{timeout.id}",
                    timestamp=timeout.created_at,
                    colour=member.colour,
                )
                embed.set_author(
                    name=f"{member} ({member.id})",
                    icon_url=str(member.avatar_url),
                )
                embed.add_field(
                    name="Banned on",
                    value=str(
                        timeout.created_at.strftime("%b %d %Y %H:%M:%S %Z")),
                    inline=False,
                )
                embed.add_field(
                    name="Banned till" if timeout.active else "Unbanned on",
                    value=str(timeout.until.strftime("%b %d %Y %H:%M:%S %Z"))
                    if timeout.until else "Permanently",
                    inline=False,
                )
                if timeout.active:
                    embed.add_field(
                        name="Timeleft",
                        value=str(utils.seconds_to_resp(timeout.time_left)),
                        inline=False,
                    )
                if timeout.issued_by_id:
                    issued_by = self.bot.filters.get_member(
                        [int(timeout.issued_by_id)], None, {})[0]
                    embed.add_field(
                        name="Banned by",
                        value=issued_by.mention
                        if issued_by else f"{timeout.issued_by_id}",
                        inline=False,
                    )
                if timeout.ban_reason:
                    embed.add_field(name="Ban Reason",
                                    value=str(timeout.ban_reason),
                                    inline=False)
                if timeout.active and timeout.unban_reason:
                    embed.add_field(
                        name="Unban Reason",
                        value=str(timeout.unban_reason),
                        inline=False,
                    )
                if timeout.active and timeout.unbanned_by_id:
                    unbanned_by = self.bot.filters.get_member(
                        [int(timeout.unbanned_by_id)], None, {})[0]
                    embed.add_field(
                        name="Unban By",
                        value=unbanned_by.mention
                        if unbanned_by else f"{timeout.issued_by_id}",
                        inline=False,
                    )
                await self.bot.say(channel=channel, embed=embed)

    async def is_timedout(self, bot, author, channel, message, args):
        command_args = message.split(" ") if message else []
        member = self.bot.filters.get_member([command_args[0]], None, {})[0]
        if not member:
            await self.bot.say(channel=channel,
                               message=f"Cant find member, {command_args[0]}")
            return False
        with DBManager.create_session_scope() as db_session:
            timeout = Timeout._is_timedout(db_session, str(member.id))

            if not timeout:
                await self.bot.say(
                    channel=channel,
                    message=f"The user {member} has not currently timedout",
                )
                return True

            embed = discord.Embed(
                description=f"Timeout #{timeout.id}",
                timestamp=timeout.created_at,
                colour=member.colour,
            )
            embed.add_field(
                name="Banned on",
                value=str(timeout.created_at.strftime("%b %d %Y %H:%M:%S %Z")),
                inline=False,
            )
            embed.add_field(
                name="Banned till"
                if timeout.time_left != 0 else "Unbanned on",
                value=str(timeout.until.strftime("%b %d %Y %H:%M:%S %Z"))
                if timeout.until else "Permanently",
                inline=False,
            )
            if timeout.time_left != 0:
                embed.add_field(
                    name="Timeleft",
                    value=str(utils.seconds_to_resp(timeout.time_left)),
                    inline=False,
                )
            if timeout.issued_by:
                embed.add_field(name="Banned by",
                                value=str(timeout.issued_by),
                                inline=False)
            if timeout.ban_reason:
                embed.add_field(name="Ban Reason",
                                value=str(timeout.ban_reason),
                                inline=False)
            if timeout.time_left != 0 and timeout.unban_reason:
                embed.add_field(name="Unban Reason",
                                value=str(timeout.unban_reason),
                                inline=False)
            await self.bot.say(channel=channel, embed=embed)

    def load_commands(self, **options):
        self.commands["timeout"] = Command.raw_command(
            self.timeout_user,
            command="timeout",
            delay_all=0,
            delay_user=0,
            level=int(self.settings["level_for_command"]),
            can_execute_with_whisper=False,
            description="Adds a timeout to a user",
        )
        self.commands["untimeout"] = Command.raw_command(
            self.untimeout_user,
            command="untimeout",
            delay_all=0,
            delay_user=0,
            level=int(self.settings["level_for_command"]),
            can_execute_with_whisper=False,
            description="Removes a timeout on a user",
        )
        self.commands["timeouts"] = Command.raw_command(
            self.query_timeouts,
            command="timeouts",
            delay_all=0,
            delay_user=0,
            level=int(self.settings["level_for_command"]),
            can_execute_with_whisper=False,
            description="Queries timeouts of a user",
        )
        self.commands["istimedout"] = Command.raw_command(
            self.is_timedout,
            command="istimedout",
            delay_all=0,
            delay_user=0,
            level=int(self.settings["level_for_command"]),
            can_execute_with_whisper=False,
            description="Checks if the user is currently timedout",
        )

    def enable(self, bot):
        if not bot:
            return

        self.bot.timeout_manager.enable({
            "enabled":
            True,
            "log_timeout":
            self.settings["log_timeout"],
            "log_untimeout":
            self.settings["log_untimeout"],
            "log_timeout_update":
            self.settings["log_timeout_update"],
            "punished_role_id":
            self.settings["punished_role_id"],
        })

    def disable(self, bot):
        if not bot:
            return

        self.bot.timeout_manager.disable()