Example #1
0
async def ask(bot, channel, author, text, options, timeout=60, show_embed=False, delete_after=False):
    embed = Embed(color=0x68a910, description='\n'.join(f"{Emoji.get_chat_emoji(option.emoji)} {option.text}" for option in options))
    message = await channel.send(text, embed=embed if show_embed else None)
    handlers = dict()
    for option in options:
        emoji = Emoji.get_emoji(option.emoji)
        await message.add_reaction(emoji)
        handlers[str(emoji)] = {'handler': option.handler, 'args': option.args}

    def check(reaction: Reaction, user):
        return user == author and str(reaction.emoji) in handlers.keys() and reaction.message.id == message.id

    try:
        reaction, user = await bot.wait_for('reaction_add', timeout=timeout, check=check)
    except asyncio.TimeoutError as ex:
        if delete_after:
            await message.delete()
        await channel.send(
            Lang.get_string("questions/error_reaction_timeout",
                            error_emoji=Emoji.get_emoji("WARNING"),
                            timeout=timeout_format(timeout)),
            delete_after=10 if delete_after else None)
        raise ex
    else:
        if delete_after:
            await message.delete()
        h = handlers[str(reaction.emoji)]['handler']
        a = handlers[str(reaction.emoji)]['args']
        if h is None:
            return
        if inspect.iscoroutinefunction(h):
            await h(*a) if a is not None else await h()
        else:
            h(*a) if a is not None else h()
Example #2
0
    async def on_message(self, message: discord.Message):
        try:
            if message.author.bot\
                    or not message.attachments\
                    or not hasattr(message.channel, "guild")\
                    or message.channel.guild is None\
                    or message.channel.id != self.channels[message.channel.guild.id][self.listen_tag]:
                return
        except KeyError as ex:
            return

        ctx = await self.bot.get_context(message)
        tags = []

        for tag in self.channels[ctx.guild.id].keys():
            tags.append(f"\\b{re.escape(tag)}\\b")
        tag_pattern = '|'.join(tags)
        tag_matcher = re.compile(tag_pattern, re.IGNORECASE)
        tags = tag_matcher.findall(message.content)

        if tags:
            for tag in tags:
                channel = self.bot.get_channel(
                    self.channels[ctx.guild.id][tag])
                for attachment in message.attachments:
                    embed = discord.Embed(timestamp=message.created_at,
                                          color=0x663399)
                    embed.add_field(name="Author",
                                    value=message.author.mention)
                    embed.add_field(name="Tag", value=f"#{tag}")
                    embed.add_field(
                        name="Jump Link",
                        value=f"[Go to message]({message.jump_url})")
                    embed.add_field(name="URL",
                                    value=f"[Download]({attachment.url})")
                    if message.content:
                        embed.add_field(name="Message Content",
                                        value=message.content)
                    embed.set_image(url=attachment.url)
                    sent = await channel.send(embed=embed)
                    await sent.add_reaction(Emoji.get_emoji("YES"))
                    await sent.add_reaction(Emoji.get_emoji("NO"))
        else:
            channel = self.bot.get_channel(
                self.channels[ctx.guild.id][self.main_tag])
            for attachment in message.attachments:
                embed = discord.Embed(timestamp=message.created_at,
                                      color=0x663399)
                embed.add_field(name="Author", value=message.author.mention)
                embed.add_field(name="Jump Link",
                                value=f"[Go to message]({message.jump_url})")
                embed.add_field(name="URL",
                                value=f"[Download]({attachment.url})")
                if message.content:
                    embed.add_field(name="Message Content",
                                    value=message.content)
                embed.set_image(url=attachment.url)
                sent = await channel.send(embed=embed)
                await sent.add_reaction(Emoji.get_emoji("YES"))
                await sent.add_reaction(Emoji.get_emoji("NO"))
Example #3
0
    async def on_raw_reaction_add(self, event):
        try:
            channel = self.bot.get_channel(event.channel_id)
            message = await channel.fetch_message(event.message_id)

            if event.channel_id not in self.collection_channels[
                    message.channel.guild.id]:
                return

            member = message.channel.guild.get_member(event.user_id)
            user_is_bot = event.user_id == self.bot.user.id
            has_permission = member.guild_permissions.mute_members  # TODO: change to role-based?
            if user_is_bot or not has_permission:
                return

            await message.clear_reactions()
            if str(event.emoji) == str(Emoji.get_emoji("NO")):
                # delete message
                await message.delete()
                return

        except (NotFound, KeyError, AttributeError) as e:
            # couldn't find channel, message, member, or action
            return
        except Exception as e:
            await Utils.handle_exception("art collector generic exception",
                                         self, e)
            return
Example #4
0
async def ask(bot, channel, author, text, options, timeout=60, show_embed=False, delete_after=False, locale="en_US"):
    description = '\n'.join(f"{Emoji.get_chat_emoji(option.emoji)} {option.text or ''}" for option in options)
    embed = Embed(color=0x68a910, description=description)
    message = await channel.send(text, embed=embed if show_embed else None)
    handlers = dict()
    for option in options:
        emoji = Emoji.get_emoji(option.emoji)
        add_attempts = 10
        # try reaction 10x in case it fails to add
        while add_attempts > 0:
            try:
                await message.add_reaction(emoji)
                break
            except Exception as ex:
                add_attempts = add_attempts - 1
        handlers[str(emoji)] = {'handler': option.handler, 'args': option.args}

    def check(reaction: Reaction, user):
        return user == author and str(reaction.emoji) in handlers.keys() and reaction.message.id == message.id

    try:
        reaction, user = await bot.wait_for('reaction_add', timeout=timeout, check=check)
    except asyncio.TimeoutError as ex:
        try:
            if delete_after:
                await message.delete()
            await channel.send(
                Lang.get_locale_string("questions/error_reaction_timeout", locale,
                                       error_emoji=Emoji.get_emoji("WARNING"),
                                       timeout=timeout_format(timeout)),
                delete_after=10 if delete_after else None)
        except Exception as e:
            # ignore all failures at this point
            pass
        raise ex
    else:
        if delete_after:
            await message.delete()
        h = handlers[str(reaction.emoji)]['handler']
        a = handlers[str(reaction.emoji)]['args']
        if h is None:
            return
        if inspect.iscoroutinefunction(h):
            await h(*a) if a is not None else await h()
        else:
            h(*a) if a is not None else h()
Example #5
0
 async def do_collect(my_message, my_tag):
     content_shown = False
     my_channel = self.bot.get_channel(self.channels[ctx.guild.id][my_message.channel.id][my_tag.lower()])
     for attachment in my_message.attachments:
         embed = discord.Embed(
             timestamp=my_message.created_at,
             color=0x663399)
         embed.add_field(name="Author", value=my_message.author.mention)
         if my_tag is not self.no_tag:
             embed.add_field(name="Tag", value=f"#{my_tag}")
         embed.add_field(name="Jump Link", value=f"[Go to message]({my_message.jump_url})")
         embed.add_field(name="URL", value=f"[Download]({attachment.url})")
         if my_message.content and not content_shown:
             # Add message content to the first of multiples, when many attachments to a single my_message.
             embed.add_field(name="Message Content", value=my_message.content, inline=False)
             content_shown = True
         embed.set_image(url=attachment.url)
         sent = await my_channel.send(embed=embed)
         await sent.add_reaction(Emoji.get_emoji("YES"))
         await sent.add_reaction(Emoji.get_emoji("NO"))
Example #6
0
    async def add_mod_action(self, trigger, matched, message, response_channel,
                             formatted_response):
        """
        :param message: Trigger message
        :param response_channel: Channel to respond in
        :param formatted_response: prepared auto-response
        :return: None
        """
        embed = discord.Embed(
            title=f"Trigger: {matched or get_trigger_description(trigger)}",
            timestamp=message.created_at,
            color=0xFF0940)
        embed.add_field(name='Message Author',
                        value=message.author.mention,
                        inline=True)
        embed.add_field(name='Channel',
                        value=message.channel.mention,
                        inline=True)
        embed.add_field(name='Jump link',
                        value=f"[Go to message]({message.jump_url})",
                        inline=True)
        embed.add_field(name='Offending Mesasge',
                        value=f"```{message.content}```",
                        inline=False)
        embed.add_field(name='Moderator Actions',
                        value=f"""
            Pass: {Emoji.get_emoji("YES")}
            Intervene: {Emoji.get_emoji("CANDLE")}
            Auto-Respond: {Emoji.get_emoji("WARNING")}
            DESTROY: {Emoji.get_emoji("NO")}
        """)

        # message add reactions
        sent_response = await response_channel.send(embed=embed)
        await sent_response.add_reaction(Emoji.get_emoji("YES"))
        await sent_response.add_reaction(Emoji.get_emoji("CANDLE"))
        await sent_response.add_reaction(Emoji.get_emoji("WARNING"))
        await sent_response.add_reaction(Emoji.get_emoji("NO"))

        action = mod_action(message.channel.id, message.id, formatted_response)
        self.mod_actions[sent_response.id] = action
Example #7
0
    async def send_bug_info(self, key):
        channel = self.bot.get_channel(Configuration.get_var("channels")[key])
        bug_info_id = Configuration.get_persistent_var(f"{key}_message")
        if bug_info_id is not None:
            try:
                message = await channel.fetch_message(bug_info_id)
            except NotFound:
                pass
            else:
                await message.delete()
                if message.id in self.bug_messages:
                    self.bug_messages.remove(message.id)

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

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

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

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

                    bugemoji = Emoji.get_emoji('BUG')
                    message = await channel.send(Lang.get_locale_string("bugs/bug_info", ctx, bug_emoji=bugemoji))
                    self.bug_messages.add(message.id)
                    await message.add_reaction(bugemoji)
                    Configuration.set_persistent_var(f"{channel.guild.id}_{channel_id}_bug_message", message.id)
                    Logging.info(f"Bug report message sent in channel #{channel.name} ({channel.id})")
                    await last_message.delete()
                except Exception as e:
                    await self.bot.guild_log(channel.guild.id, f'Having trouble sending bug message in {channel.mention}')
                    await Utils.handle_exception(
                        f"Bug report message failed to send in channel #{channel.name} ({channel.id})", self.bot, e)
                    await asyncio.sleep(0.5)
Example #9
0
    async def on_raw_reaction_add(self, event):
        """
        reaction listener for art collection channels.
        Clears reactions and on "no" reaction, removes post from collection
        """
        try:
            m_id = event.message_id
            u_id = event.user_id
            g_id = event.guild_id
            c_id = event.channel_id
            my_emoji = event.emoji
            my_member = event.member

            my_channel = self.bot.get_channel(c_id)
            my_guild = self.bot.get_guild(g_id)

            if c_id not in self.collection_channels[my_guild.id]:
                return

            user_is_bot = u_id == self.bot.user.id
            has_permission = my_member.guild_permissions.mute_members  # TODO: change to role-based?
            if user_is_bot or not has_permission:
                return

            message = await my_channel.fetch_message(m_id)
            if str(my_emoji) == str(Emoji.get_emoji("NO")):
                # delete message
                await message.delete()
                return
            else:
                await message.clear_reactions()  # any reaction will remove the bot reacts

        except (NotFound, HTTPException, KeyError, AttributeError) as e:
            # couldn't find channel, message, member, or action
            return
        except Exception as e:
            await Utils.handle_exception("art collector generic exception", self.bot, e)
            return
Example #10
0
    async def do_mod_action(self, action, member, message, emoji):
        """
        :param action: namedtuple mod_action to execute
        :param member: member performing the action
        :param message: message action is performed on
        :param emoji: the emoji that was added
        :return: None
        """

        try:
            trigger_channel = self.bot.get_channel(action.channel_id)
            trigger_message = await trigger_channel.fetch_message(
                action.message_id)
        except (NotFound, AttributeError) as e:
            trigger_message = None

        m = self.bot.metrics

        if str(emoji) == str(Emoji.get_emoji("YES")):
            # delete mod action message, leave the triggering message
            await message.delete()
            m.auto_responder_mod_pass.inc()
            return

        async def update_embed(my_message, mod):
            # replace mod action list with acting mod name and datetime
            my_embed = my_message.embeds[0]
            start = message.created_at
            react_time = datetime.utcnow()
            time_d = Utils.to_pretty_time((react_time - start).seconds)
            nonlocal trigger_message
            my_embed.set_field_at(-1,
                                  name="Handled by",
                                  value=mod.mention,
                                  inline=True)
            if trigger_message is None:
                my_embed.add_field(
                    name="Deleted",
                    value="Member removed message before action was taken.")
            my_embed.add_field(name="Action Used", value=emoji, inline=True)
            my_embed.add_field(name="Reaction Time", value=time_d, inline=True)
            await (my_message.edit(embed=my_embed))

        await update_embed(message, member)
        await message.clear_reactions()
        await asyncio.sleep(1)

        if str(emoji) == str(Emoji.get_emoji("CANDLE")):
            # do nothing
            m.auto_responder_mod_manual.inc()
            pass
        if str(emoji) == str(Emoji.get_emoji("WARNING")):
            # send auto-response in the triggering channel
            m.auto_responder_mod_auto.inc()
            if trigger_message is not None:
                await trigger_message.channel.send(action.response)
        if str(emoji) == str(Emoji.get_emoji("NO")):
            # delete the triggering message
            m.auto_responder_mod_delete_trigger.inc()
            if trigger_message is not None:
                await trigger_message.delete()
Example #11
0
async def ask_text(
        bot,
        channel,
        user,
        text,
        validator=None,
        timeout=Configuration.get_var("question_timeout_seconds"),
        confirm=False,
        escape=True,
        delete_after=False,
        locale="en_US"):

    def check(msg):
        return user == msg.author and msg.channel == channel

    ask_again = True

    def confirmed():
        nonlocal ask_again
        ask_again = False

    def clean_text(txt):
        """Remove multiple spaces and multiple newlines from input txt."""
        txt = re.sub(r' +', ' ', txt)
        txt = re.sub(r'\n\s*\n', '\n\n', txt)
        return txt

    my_messages = []

    async def clean_dialog():
        nonlocal delete_after
        nonlocal my_messages
        if delete_after:
            for msg in my_messages:
                try:
                    await msg.delete()
                except Exception as e:
                    pass

    while ask_again:
        message_cleaned = ""
        my_messages.append(await channel.send(text))
        try:
            while True:
                message = await bot.wait_for('message', timeout=timeout, check=check)
                my_messages.append(message)
                if message.content is None or message.content == "":
                    result = Lang.get_locale_string("questions/text_only", locale)
                else:
                    message_cleaned = clean_text(message.content)
                    result = validator(message_cleaned) if validator is not None else True
                if result is True:
                    break
                else:
                    my_messages.append(await channel.send(result))
        except asyncio.TimeoutError as ex:
            await clean_dialog()
            await channel.send(
                # TODO: remove "bug" from lang string. send report cancel language from Bugs.py exception handler
                Lang.get_locale_string("questions/error_reaction_timeout", locale,
                                       error_emoji=Emoji.get_emoji("WARNING"),
                                       timeout=timeout_format(timeout))
            )
            raise ex
        else:
            content = Utils.escape_markdown(message_cleaned) if escape else message_cleaned
            if confirm:
                backticks = "``" if len(message_cleaned.splitlines()) == 1 else "```"
                message = Lang.get_locale_string('questions/confirm_prompt',
                                                 locale,
                                                 backticks=backticks,
                                                 message=message_cleaned)
                await ask(bot, channel, user, message, [
                    Option("YES", handler=confirmed),
                    Option("NO")
                ], delete_after=delete_after)
            else:
                confirmed()

            await clean_dialog()
            return content
Example #12
0
async def ask_attachements(
        bot,
        channel,
        user,
        timeout=Configuration.get_var("question_timeout_seconds"),
        max_files=Configuration.get_var('max_attachments'),
        locale="en_US"):

    def check(message):
        return user == message.author and message.channel == channel

    done = False

    def ready():
        nonlocal done
        done = True

    async def restart_attachments():
        nonlocal final_attachments
        final_attachments = []
        await ask(bot, channel, user, Lang.get_locale_string("questions/attachments_restart", locale), [
            Option("YES", Lang.get_locale_string('questions/restart_attachments_yes', locale)),
            Option("NO", Lang.get_locale_string('questions/restart_attachments_no', locale), handler=ready)
        ], show_embed=True)

    while not done:
        ask_again = True
        final_attachments = []
        count = 0

        def confirmed():
            nonlocal ask_again
            ask_again = False

        while ask_again:
            if not final_attachments:
                await channel.send(Lang.get_locale_string("questions/attachment_prompt",
                                                          locale,
                                                          max=max_files))
            elif len(final_attachments) < max_files - 1:
                await channel.send(
                    Lang.get_locale_string("questions/attachment_prompt_continued",
                                           locale,
                                           max=max_files - len(final_attachments)))
            elif len(final_attachments):
                await channel.send(Lang.get_locale_string("questions/attachment_prompt_final", locale))

            done = False

            try:
                while True:
                    message = await bot.wait_for('message', timeout=timeout, check=check)
                    links = Utils.URL_MATCHER.findall(message.content)
                    attachment_links = [str(a.url) for a in message.attachments]
                    if len(links) != 0 or len(message.attachments) != 0:
                        if (len(links) + len(message.attachments)) > max_files:
                            await channel.send(Lang.get_locale_string("questions/attachments_overflow",
                                                                      locale,
                                                                      max=max_files))
                        else:
                            final_attachments += links + attachment_links
                            count += len(links) + len(attachment_links)
                            break
                    else:
                        await channel.send(Lang.get_locale_string("questions/attachment_not_found", locale))
            except asyncio.TimeoutError as ex:
                await channel.send(
                    Lang.get_locale_string("questions/error_reaction_timeout", locale,
                                           error_emoji=Emoji.get_emoji("WARNING"),
                                           timeout=timeout_format(timeout))
                )
                raise ex
            else:
                if count < max_files:
                    await ask(bot, channel, user, Lang.get_locale_string('questions/another_attachment', locale),
                              [Option("YES"), Option("NO", handler=confirmed)])
                else:
                    ask_again = False

        prompt_yes = Lang.get_locale_string("questions/approve_attachments", locale)
        if len(final_attachments) == 1:
            prompt_no = Lang.get_locale_string('questions/restart_attachment_singular', locale)
        else:
            prompt_no = Lang.get_locale_string('questions/restart_attachment_plural', locale)
        await ask(bot, channel, user, Lang.get_locale_string('questions/confirm_attachments', locale), [
            Option("YES", prompt_yes, handler=ready),
            Option("NO", prompt_no, handler=restart_attachments)
        ], show_embed=True)

    return final_attachments