Beispiel #1
0
    async def language_change_interaction(self, ctx, db_user, user,
                                          language_code, suggested_locale):
        def check(payload: RawReactionActionEvent):
            return payload.user_id == user.id and str(
                payload.emoji) == '✅' and payload.guild_id is None

        try:
            payload = await self.bot.wait_for('raw_reaction_add',
                                              timeout=86400,
                                              check=check)
        except asyncio.TimeoutError:
            _ = get_translate_function(ctx, db_user.language)
            await user.send(_("Your language preference wasn't changed."))
            await ctx.message.add_reaction('❌')
            await ctx.reply("Language change timed out.")
        else:
            async with user.dm_channel.typing():
                db_user = await get_from_db(user, as_user=True)
                db_user.language = language_code
                await db_user.save()
                _ = get_translate_function(ctx, language_code)

            await user.send(
                _("Your preferred language is now {new_language}.",
                  new_language=suggested_locale.get_display_name()))

            await ctx.message.add_reaction('✅')
            await ctx.reply("Language change accepted.")
Beispiel #2
0
    async def get_translate_function(self, user_language=False):
        language_code = await self.get_language_code(
            user_language=user_language)

        return get_translate_function(self,
                                      language_code,
                                      additional_kwargs={'ctx': self})
Beispiel #3
0
    async def handle_auto_responses(self, message: discord.Message):
        forwarding_channel = await self.get_or_create_channel(message.author)
        content = message.content

        user = message.author
        db_user = await get_from_db(message.author, as_user=True)
        language = db_user.language
        _ = get_translate_function(self.bot, language)

        if self.invites_regex.search(content):
            dm_invite_embed = discord.Embed(
                color=discord.Color.purple(),
                title=_("This is not how you invite DuckHunt."))
            dm_invite_embed.description = \
                _("DuckHunt, like other discord bots, can't join servers by using an invite link.\n"
                  "You instead have to be a server Administrator and to invite the bot by following "
                  "[this guide](https://duckhunt.me/docs/bot-administration/admin-quickstart). If you need more help, "
                  "you can ask here and we'll get back to you.")

            dm_invite_embed.set_footer(text=_("This is an automatic message."))

            await self.send_mirrored_message(forwarding_channel,
                                             user,
                                             db_user=db_user,
                                             embed=dm_invite_embed)
                async def confirm(self, button: discord.ui.Button,
                                  interaction: discord.Interaction):
                    await interaction.response.send_message('Closing...',
                                                            ephemeral=True)
                    db_user = await get_from_db(message.author, as_user=True)

                    ticket = await db_user.get_or_create_support_ticket()
                    ticket.close(
                        await get_from_db(interaction.user, as_user=True),
                        "Asked closed")

                    await ticket.save()

                    language = db_user.language

                    _ = get_translate_function(outer.bot, language)

                    close_embed = discord.Embed(
                        color=discord.Color.red(),
                        title=_("DM Closed"),
                        description=
                        _("Your support ticket was closed following your request and the history deleted. "
                          "Thanks for using DuckHunt DM support. "
                          "Keep in mind, sending another message here will open a new ticket!\n"
                          "In the meantime, here's a nice duck picture for you to look at !"
                          ),
                    )

                    close_embed.add_field(
                        name=_("Support server"),
                        value=
                        _("For all your questions, there is a support server. "
                          "Click [here](https://duckhunt.me/support) to join."
                          ))

                    file = await get_random_duck_file(outer.bot)
                    close_embed.set_image(url="attachment://random_duck.png")

                    async with forwarding_channel.typing():
                        await forwarding_channel.send(
                            content=
                            f"🚮 Deleting channel following a click by {interaction.user.name}#{interaction.user.discriminator}... Don't send messages anymore!"
                        )

                        try:
                            await message.author.send(file=file,
                                                      embed=close_embed)
                        except:
                            pass

                        await asyncio.sleep(5)  # To let people stop writing

                        await outer.clear_caches(forwarding_channel)

                        await forwarding_channel.delete(
                            reason=
                            f"{interaction.user.name}#{interaction.user.discriminator} ({interaction.user.id}) closed the DM."
                        )
                    self.stop()
Beispiel #5
0
    async def suggest_language(self, ctx: MyContext, *, language_code):
        """
        Suggest a new language to the user. This will show them a prompt asking them if they want to switch to this new
        language.
        """
        await self.is_in_forwarding_channels(ctx)

        user = await self.get_user(ctx.channel.name)
        db_user = await get_from_db(user, as_user=True)

        if db_user.language.casefold() == language_code.casefold():
            await ctx.reply(
                f"❌ The user language is already set to {db_user.language}")
            return

        try:
            suggested_locale = Locale.parse(language_code)
        except (babel.UnknownLocaleError, ValueError):
            await ctx.reply(
                "❌ Unknown locale. You need to provide a language code here, like `fr`, `es`, `en`, ..."
            )
            return

        current_locale = Locale.parse(db_user.language)

        _ = get_translate_function(ctx, language_code)

        embed = discord.Embed(colour=discord.Colour.blurple(),
                              title=_("Language change offer"))

        embed.description = _(
            "DuckHunt support suggests you change your personal language "
            "from {current_language} to {suggested_language}. This will translate "
            "all the messages you get in private message from DuckHunt to {suggested_language}.",
            current_language=current_locale.get_display_name(language_code),
            suggested_language=suggested_locale.get_display_name(
                language_code),
        )

        embed.set_author(name=f"{ctx.author.name}#{ctx.author.discriminator}",
                         icon_url=str(ctx.author.avatar_url))

        embed.set_footer(text=_(
            "Press ✅ to accept the change, or do nothing to reject. "
            "Use the [dh!settings my_lang language_code] command in a game channel to edit later."
        ))

        message = await user.send(embed=embed)
        await message.add_reaction("✅")

        # Do it, but don't block it
        asyncio.ensure_future(
            self.language_change_interaction(ctx, db_user, user, language_code,
                                             suggested_locale))
        await ctx.message.add_reaction("<a:typing:597589448607399949>")
Beispiel #6
0
    async def close(self, ctx: MyContext, *, reason: str = None):
        """
        Close the opened DM channel. Will send a message telling the user that the DM was closed.
        """
        await self.is_in_forwarding_channels(ctx)

        user = await self.get_user(ctx.channel.name)
        db_user = await get_from_db(user, as_user=True)

        ticket = await db_user.get_or_create_support_ticket()
        ticket.close(await get_from_db(ctx.author, as_user=True), reason)

        await ticket.save()

        language = db_user.language

        _ = get_translate_function(self.bot, language)

        close_embed = discord.Embed(
            color=discord.Color.red(),
            title=_("DM Closed"),
            description=_(
                "Your support ticket was closed and the history deleted. "
                "Thanks for using DuckHunt DM support. "
                "Keep in mind, sending another message here will open a new ticket!\n"
                "In the meantime, here's a nice duck picture for you to look at !",
                ctx=ctx),
        )

        close_embed.add_field(
            name=_("Support server"),
            value=_("For all your questions, there is a support server. "
                    "Click [here](https://duckhunt.me/support) to join."))

        file = await get_random_duck_file(self.bot)
        close_embed.set_image(url="attachment://random_duck.png")

        async with ctx.typing():
            await ctx.send(
                content="🚮 Deleting channel... Don't send messages anymore!")

            try:
                await user.send(file=file, embed=close_embed)
            except:
                pass

            await asyncio.sleep(5)  # To let people stop writing

            await self.clear_caches(ctx.channel)

            await ctx.channel.delete(
                reason=
                f"{ctx.author.name}#{ctx.author.discriminator} ({ctx.author.id}) closed the DM."
            )
Beispiel #7
0
    async def handle_support_message(self, message: discord.Message):
        user = await self.get_user(message.channel.name)
        db_user = await get_from_db(user, as_user=True)
        language = db_user.language

        self.bot.logger.info(
            f"[SUPPORT] answering {user.name}#{user.discriminator} with a message from {message.author.name}#{message.author.discriminator}"
        )

        _ = get_translate_function(self.bot, language)

        support_embed = discord.Embed(color=discord.Color.blurple(),
                                      title="Support response")
        support_embed.set_author(
            name=f"{message.author.name}#{message.author.discriminator}",
            icon_url=str(message.author.avatar_url))
        support_embed.description = message.content

        if len(message.attachments) == 1:
            url = str(message.attachments[0].url)
            if not message.channel.nsfw \
                    and (url.endswith(".webp") or url.endswith(".png") or url.endswith(".jpg")):
                support_embed.set_image(url=url)
            else:
                support_embed.add_field(name="Attached", value=url)

        elif len(message.attachments) > 1:
            for attach in message.attachments:
                support_embed.add_field(name="Attached", value=attach.url)

            support_embed.add_field(
                name=_("Attachments persistence"),
                value=_(
                    "Images and other attached data to the message will get deleted "
                    "once your ticket is closed. "
                    "Make sure to save them beforehand if you wish."))

        try:
            await user.send(embed=support_embed)
        except Exception as e:
            await message.channel.send(
                f"❌: {e}\nYou can use `dh!private_support close` to close the channel."
            )
Beispiel #8
0
    async def send_mirrored_message(self,
                                    channel: discord.TextChannel,
                                    user: discord.User,
                                    db_user=None,
                                    **send_kwargs):
        self.bot.logger.info(
            f"[SUPPORT] Sending mirror message to {user.name}#{user.discriminator}"
        )

        db_user = db_user or await get_from_db(user, as_user=True)
        language = db_user.language
        _ = get_translate_function(self.bot, language)

        channel_message = await channel.send(**send_kwargs)

        try:
            await user.send(**send_kwargs)
        except discord.Forbidden:
            await channel_message.add_reaction(emoji="❌")
            await channel.send(
                content=
                "❌ Couldn't send the above message, `dh!ps close` to close the channel."
            )
Beispiel #9
0
    async def background_loop(self):
        """
        Check for age of the last message sent in the channel.
        If it's too old, consider the channel inactive and close the ticket.
        """
        category = await self.get_forwarding_category()
        now = datetime.datetime.now()
        one_day_ago = now - datetime.timedelta(days=1)
        for ticket_channel in category.text_channels:
            last_message_id = ticket_channel.last_message_id
            if last_message_id:
                # We have the ID of the last message, there is no need to fetch the API, since we can just extract the
                # datetime from it.
                last_message_time = snowflake_time(last_message_id)
            else:
                # For some reason, we couldn't get the last message, so we'll have to go the expensive route.
                # In my testing, I didn't see it happen, but better safe than sorry.
                try:
                    last_message = (await
                                    ticket_channel.history(limit=1
                                                           ).flatten())[0]
                except IndexError:
                    # No messages at all.
                    last_message_time = now
                else:
                    last_message_time = last_message.created_at

            inactive_for = last_message_time - now
            inactive_for_str = format_timedelta(inactive_for,
                                                granularity='minute',
                                                locale='en',
                                                threshold=1.1)
            self.bot.logger.debug(
                f"[SUPPORT GARBAGE COLLECTOR] "
                f"#{ticket_channel.name} has been inactive for {inactive_for_str}."
            )

            if last_message_time <= one_day_ago:
                self.bot.logger.debug(f"[SUPPORT GARBAGE COLLECTOR] "
                                      f"Deleting #{ticket_channel.name}...")
                # The last message was sent a day ago, or more.
                # It's time to close the channel.
                async with ticket_channel.typing():
                    user = await self.get_user(ticket_channel.name)
                    db_user = await get_from_db(user, as_user=True)

                    ticket = await db_user.get_or_create_support_ticket()
                    ticket.close(
                        await get_from_db(self.bot.user, as_user=True),
                        "Automatic closing for inactivity.")

                    await ticket.save()

                    language = db_user.language

                    _ = get_translate_function(self.bot, language)

                    inactivity_embed = discord.Embed(
                        color=discord.Color.orange(),
                        title=_("DM Timed Out"),
                        description=
                        _("It seems like nothing has been said here for a long time, "
                          "so I've gone ahead and closed your ticket, deleting its history. "
                          "Thanks for using DuckHunt DM support. "
                          "If you need anything else, feel free to open a new ticket by sending a message "
                          "here."),
                    )

                    inactivity_embed.add_field(
                        name=_("Support server"),
                        value=
                        _("For all your questions, there is a support server. "
                          "Click [here](https://duckhunt.me/support) to join."
                          ))

                    try:
                        await user.send(embed=inactivity_embed)
                    except:
                        pass

                    await self.clear_caches(ticket_channel)

                    await ticket_channel.delete(
                        reason=f"Automatic deletion for inactivity.")
Beispiel #10
0
    async def handle_ticket_opening(self, channel: discord.TextChannel,
                                    user: discord.User):
        db_user: DiscordUser = await get_from_db(user, as_user=True)
        ticket = await db_user.get_or_create_support_ticket()
        await ticket.save()

        await channel.send(
            content=
            f"Opening a DM channel with {user.name}#{user.discriminator} ({user.mention}).\n"
            f"Every message in here will get sent back to them if it's not a bot message, "
            f"DuckHunt command, and if it doesn't start with the > character.\n"
            f"You can use many commands in the DM channels, detailed in "
            f"`dh!help private_support`\n"
            f"• `dh!ps close` will close the channel, sending a DM to the user.\n"
            f"• `dh!ps tag tag_name` will send a tag to the user *and* in the channel. "
            f"The two are linked, so changing pages in this channel "
            f"will change the page in the DM too.\n"
            f"• `dh!ps block` will block the user from opening further channels.\n"
            f"• `dh!ps huh` should be used if the message is not a support request, "
            f"and will silently close the channel.\n"
            f"Attachments are supported in messages.\n\n"
            f"Thanks for helping with the bot DM support ! <3")

        players_data = await Player.all().filter(
            member__user=db_user
        ).order_by("-last_giveback").select_related("channel").limit(5)

        info_embed = discord.Embed(color=discord.Color.blurple(),
                                   title="Support information")

        info_embed.description = "Information in this box isn't meant to be shared outside of this channel, and is " \
                                 "provided for support purposes only. \n" \
                                 "Nothing was sent to the user about this."

        info_embed.set_author(name=f"{user.name}#{user.discriminator}",
                              icon_url=str(user.avatar_url))
        info_embed.set_footer(text="Private statistics")

        ticket_count = await db_user.support_ticket_count()
        info_embed.add_field(name="User language",
                             value=str(db_user.language),
                             inline=True)

        if db_user.access_level_override != AccessLevel.DEFAULT:
            info_embed.add_field(name="Access Level",
                                 value=str(db_user.access_level_override.name),
                                 inline=True)

        fs_td = format_timedelta(db_user.first_seen - timezone.now(),
                                 granularity="minute",
                                 add_direction=True,
                                 format="short",
                                 locale="en")

        info_embed.add_field(name="First seen", value=str(fs_td), inline=True)
        info_embed.add_field(name="Tickets created",
                             value=str(ticket_count),
                             inline=True)

        if ticket_count > 1:
            last_ticket = await SupportTicket.filter(
                user=db_user, closed=True).order_by(
                    '-opened_at').select_related('closed_by').first()

            ftd = format_timedelta(last_ticket.closed_at - timezone.now(),
                                   granularity="minute",
                                   add_direction=True,
                                   format="short",
                                   locale="en")
            value = f"Closed {ftd} by {last_ticket.closed_by.name}."

            if last_ticket.close_reason:
                value += f"\n{last_ticket.close_reason}"

            info_embed.add_field(name="Previous ticket",
                                 value=value,
                                 inline=False)

        for player_data in players_data:
            if player_data.channel.enabled:
                info_embed.add_field(
                    name=
                    f"#{player_data.channel} - {player_data.experience} exp",
                    value=
                    f"[Statistics](https://duckhunt.me/data/channels/{player_data.channel.discord_id}/{user.id})"
                )
            else:
                info_embed.add_field(
                    name=f"#{player_data.channel} [DISABLED]",
                    value=
                    f"[Statistics](https://duckhunt.me/data/channels/{player_data.channel.discord_id}/{user.id}) - {player_data.experience} exp"
                )

        await channel.send(embed=info_embed)

        _ = get_translate_function(self.bot, db_user.language)

        welcome_embed = discord.Embed(color=discord.Color.green(),
                                      title="Support ticket opened")
        welcome_embed.description = \
            _("Welcome to DuckHunt private messages support.\n"
              "Messages here are relayed to a select group of volunteers and bot moderators to help you use the bot. "
              "For general questions, we also have a support server "
              "[here](https://duckhunt.me/support).\n"
              "If you opened the ticket by mistake, just say `close` and we'll close it for you, otherwise, we'll get "
              "back to you in a few minutes.")
        welcome_embed.set_footer(
            text=_("Support tickets are deleted after 24 hours of inactivity"))

        try:
            await user.send(embed=welcome_embed)
        except discord.Forbidden:
            await channel.send(
                content=
                "❌ It seems I can't send messages to the user, you might want to close the DM. "
                "`dh!ps close`.")