Exemple #1
0
    async def close_silent(self, ctx: MyContext, *, reason: str = None):
        """
        Close the opened DM channel. Will not send a message, since it wasn't a support request.
        """
        await self.is_in_forwarding_channels(ctx)

        if reason is None:
            reason = "Closed silently."

        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()

        async with ctx.typing():
            await ctx.send(
                content="🚮 Deleting channel... Don't send messages anymore!")
            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."
            )
Exemple #2
0
    async def vote(self, ctx: MyContext):
        """
        Sends the link you can use to vote for DuckHunt on bot lists
        """
        _ = await ctx.get_translate_function()

        m = await ctx.reply(
            _("Please wait while I check where you can vote..."))

        async with ctx.typing():
            votable_lists, maybe_lists, nope_lists = await self.get_votable_lists(
                ctx.author)

            embed = discord.Embed()

            if votable_lists:
                text = votable_lists[0].vote_url
                embed.title = _("You can vote !")
                embed.description = _(
                    "Thanks for supporting the bot by voting !")
                embed.url = votable_lists[0].vote_url
                embed.colour = discord.Colour.green()
            elif maybe_lists:
                text = maybe_lists[0].vote_url
                embed.title = _("You might be able to vote !")
                embed.description = _(
                    "Thanks for supporting the bot by voting as much as you can. It makes a difference !"
                )
                embed.url = maybe_lists[0].vote_url
                embed.colour = discord.Colour.orange()
            else:
                text = _(
                    "Oh no! No bot list is currently available for you to vote."
                )
                embed.title = _(
                    "There is nowhere for you to vote at the time !")
                embed.description = _(
                    "Thanks for supporting the bot. It makes a difference! \n"
                    "Unfortunately, you voted everywhere you could for now, but you can check back in a few hours."
                )
                embed.colour = discord.Colour.red()

            click_me_to_vote = _("Click me to vote")
            for bot_list in votable_lists:
                embed.add_field(
                    name=_("You can vote on {bl_name}", bl_name=bot_list.name),
                    value=f"[{click_me_to_vote}]({bot_list.vote_url})",
                    inline=False)

            for bot_list in maybe_lists:
                embed.add_field(
                    name=_("You might be able to vote on {bl_name}",
                           bl_name=bot_list.name),
                    value=f"[{click_me_to_vote}]({bot_list.vote_url})",
                    inline=True)

            await m.edit(embed=embed, content=text)
Exemple #3
0
    async def remove_all_scores_stats(self,
                                      ctx: MyContext,
                                      channel_id_to_delete: int = None):
        """
        Delete scores for all users on this channel or on the specified channel ID.

        Data will not be recoverable, and there is no confirmation dialog. Type with caution.
        This command will execute on the *current channel* if no ID is provided.
        If you pass an ID, the bot will check whether the channel still exist. If it does, it'll refuse to delete to
        prevent mistakes. You'll have to run the command in the correct channel. If it doesn't, but the channel was in
        the same guild/server, scores will be deleted.

        If you need a backup of the channel data, you can use the DuckHunt API to download the scores and statistics for
        everyone who ever played.

        Note that the channel itself wont be deleted, only the scores are.
        """
        _ = await ctx.get_translate_function()

        async with ctx.typing():
            if channel_id_to_delete:
                maybe_channel = ctx.guild.get_channel(channel_id_to_delete)
                if maybe_channel:
                    await ctx.send(
                        _(
                            "❌ The channel {channel.mention} still exists on the server. "
                            "Please run the command from there.",
                            channel=maybe_channel))
                    return
                else:
                    maybe_db_channel = await DiscordChannel.all()\
                        .prefetch_related('guild')\
                        .get_or_none(discord_id=channel_id_to_delete)

                    if maybe_db_channel:
                        if maybe_db_channel.guild.discord_id != ctx.guild.id:
                            await ctx.send(
                                _("❌ The channel used to exist but on a different server. I can't confirm you have the "
                                  "correct rights on that server. Please use this command in the right server, or contact "
                                  "support : <https://duckhunt.me/support>."))
                            return
                    else:
                        await ctx.send(
                            _("❌ I can't find a channel with that ID. Please check the given ID, or contact "
                              "support : <https://duckhunt.me/support>."))
                        return

                db_channel = maybe_db_channel
            else:
                db_channel = await get_from_db(ctx.channel)

            await Player.filter(channel=db_channel).delete()

            await ctx.send(
                _("Scores and hunters data were removed from the game, but the game wasn't stopped... "
                  "You can use `{ctx.prefix}settings enabled False` to stop the game."
                  ))
Exemple #4
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."
            )
Exemple #5
0
    async def random_duck(self, ctx: MyContext, with_background=True):
        """
        Shows a random duck image, courtesy of Globloxmen assets.
        This is mostly used to debug the random duck functions,
        and uses the same pipeline as for the random duck avatars.

        You can specify if you want a background or not for your random duck, and it defaults to yes.
        """
        async with ctx.typing():
            _ = await ctx.get_translate_function()
            file = await get_random_duck_file(self.bot, with_background)

            await ctx.send(file=file)
Exemple #6
0
    async def confirm(self, ctx: MyContext):
        """Execute the prestige process. Almost all of your hunting data **WILL** be deleted."""
        old_db_hunter: Player = await get_player(ctx.author, ctx.channel)

        _ = await ctx.get_translate_function()
        higher_level = get_higher_level()
        needed_exp = higher_level["expMin"]
        current_exp = old_db_hunter.experience
        missing_exp = needed_exp - current_exp
        progression = round(current_exp / needed_exp * 100)
        kept_exp = round(-missing_exp / 10)

        if current_exp < needed_exp:
            await ctx.send(
                _("❌ You haven't unlocked prestige yet. See `{ctx.prefix}prestige info` to learn more."
                  ))
            return False

        async with ctx.typing():
            old_prestige = old_db_hunter.prestige
            new_prestige = old_db_hunter.prestige + 1

            e = discord.Embed(
                title=_("Prestige {old_prestige} -> {new_prestige}",
                        old_prestige=old_prestige,
                        new_prestige=new_prestige))

            e.color = discord.Color.green()

            e.description = _(
                "You used prestige after reaching {pct}% of the required threshold.",
                pct=progression)
            e.add_field(
                name=_("✨ New run"),
                value=_("You'll restart the game with {kept_exp} experience.",
                        kept_exp=kept_exp))

            await old_db_hunter.delete()
            new_db_hunter: Player = await get_player(ctx.author, ctx.channel)
            new_db_hunter.experience = kept_exp
            new_db_hunter.prestige = new_prestige
            new_db_hunter.stored_achievements = old_db_hunter.stored_achievements

            level_info = new_db_hunter.level_info()
            new_db_hunter.magazines = level_info["magazines"]
            new_db_hunter.bullets = level_info["bullets"]

            await new_db_hunter.save()

        await ctx.send(embed=e)
Exemple #7
0
    async def predicate(ctx: MyContext):

        if not ctx.guild:
            raise commands.NoPrivateMessage()
        else:
            db_user = await get_from_db(ctx.author)

            access = db_user.get_access_level()
            if access >= required_access:
                return True
            elif access >= AccessLevel.BANNED and required_access <= AccessLevel.ADMIN:
                if ctx.author_permissions().administrator:
                    return True  # Override permissions for administrators
                raise AccessTooLow(current_access=access, required_access=required_access)
            else:
                raise AccessTooLow(current_access=access, required_access=required_access)
Exemple #8
0
    async def inv_compress(self, ctx: MyContext):
        """
        Compress and sort your inventory, to fuse items together. You shouldn't have to use this.
        """
        _ = await ctx.get_translate_function(user_language=True)
        async with ctx.typing():
            db_user = await get_from_db(ctx.author, as_user=True)

            # The following creates a new list, there is no need to copy.
            inv_items = sorted(db_user.inventory, key=lambda i: i["name"])
            db_user.inventory = []
            for item in inv_items:
                db_user.add_to_inventory(item)

            await db_user.save()
        await ctx.reply(_("🎒 Your inventory has been sorted."))
Exemple #9
0
    async def give_trophy(self, ctx: MyContext, trophy_key: str, user: discord.User, value: bool = True):
        """
        Congratulate an user giving him a trophy.
        """

        async with ctx.typing():
            db_user = await get_from_db(user, as_user=True)

            if value:
                db_user.trophys[trophy_key] = True
            else:
                del db_user.trophys[trophy_key]

            await db_user.save()

        await ctx.reply(f"User {user.name}#{user.discriminator} (`{user.id}`) updated.")
Exemple #10
0
    async def random_duck(self, ctx: MyContext, with_background=True):
        """
        Shows a random duck image, courtesy of Globloxmen assets.
        This is mostly used to debug the random duck functions,
        and uses the same pipeline as for the random duck avatars.

        You can specify if you want a background or not for your random duck, and it defaults to yes.
        """
        async with ctx.typing():
            _ = await ctx.get_translate_function()
            fn = partial(self.get_random_duck_bytes, with_background)

            buffer = await self.bot.loop.run_in_executor(None, fn)
            file = discord.File(filename="random_duck.png", fp=buffer)

            await ctx.send(file=file)
    async def send_exp(self, ctx: MyContext, target: discord.Member,
                       amount: int):
        """
        Send some of your experience to another player.
        """
        _, db_channel, db_sender, db_reciver = await asyncio.gather(
            ctx.get_translate_function(), get_from_db(ctx.channel),
            get_player(ctx.author, ctx.channel),
            get_player(target, ctx.channel))

        if target.id == ctx.author.id:
            await ctx.reply(_("❌ You cant send experience to yourself."))
            return False

        elif target.bot:
            await ctx.reply(
                _("❌ I'm not sure that {target.mention} can play DuckHunt.",
                  target=target))
            return False

        elif amount < 10:
            await ctx.reply(_("❌ You need to send more experience than this."))
            return False

        elif db_sender.experience < amount:
            await ctx.reply(_("❌ You don't have enough experience 🤣."))
            return False

        if amount >= 30 and db_reciver.is_powerup_active('confiscated'):
            db_sender.stored_achievements['gun_insurer'] = True

        tax = int(amount * (db_channel.tax_on_user_send / 100) + 0.5)

        await db_sender.edit_experience_with_levelups(ctx, -amount)
        await db_reciver.edit_experience_with_levelups(ctx, amount - tax)

        await asyncio.gather(db_sender.save(), db_reciver.save())

        await ctx.reply(
            _(
                "🏦 You sent {recived} experience to {reciver.mention}. "
                "A tax of {db_channel.tax_on_user_send}% was applied to this transaction (taking {tax} exp out of the total sent).",
                amount=amount,
                recived=amount - tax,
                tax=tax,
                reciver=target,
                db_channel=db_channel))
Exemple #12
0
    async def close_silent(self, ctx: MyContext):
        """
        Close the opened DM channel. Will not send a message, since it wasn't a support request.
        """
        await self.is_in_forwarding_channels(ctx)

        async with ctx.typing():
            await ctx.send(
                content="🚮 Deleting channel... Don't send messages anymore!")
            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."
            )
Exemple #13
0
    async def give_exp(self, ctx: MyContext, target: discord.Member, amount: int):
        """
        Give some experience to another player. This is a cheat.
        """
        _, db_receiver = await asyncio.gather(ctx.get_translate_function(), get_player(target, ctx.channel))

        if target.bot:
            await ctx.reply(_("❌ I'm not sure that {target.mention} can play DuckHunt.", target=target))
            return False

        await db_receiver.edit_experience_with_levelups(ctx, amount)

        await db_receiver.save()

        await ctx.reply(_("💰️ You gave {amount} experience to {reciver.mention}. ",
                          amount=amount,
                          reciver=target, ))
Exemple #14
0
    async def close(self, ctx: MyContext):
        """
        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)

        close_embed = discord.Embed(
            color=discord.Color.red(),
            title="DM Closed",
            description=
            "Your support ticket was closed and the history deleted. Thanks for using COVID-19 Bot DM "
            "support. If you need anything else, feel free to open a new ticket by sending a message here."
        )

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

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

            try:
                await user.send(embed=close_embed)
            except discord.DiscordException:
                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."
            )
Exemple #15
0
    async def carve(self,
                    ctx: MyContext,
                    who: Optional[discord.User] = None,
                    width_pct: int = 50,
                    height_pct: int = 50,
                    image_format: str = "jpeg"):
        """
        Content-aware carving of an image/avatar, resizing it to reduce the width and height,
        loosing as few details as we can.

        With seam carving algorithm, the image could be intelligently resized while keeping the important contents
        undistorted. The carving process could be further guided, so that an object could be removed from the image
        without apparent artifacts.

        This function only handle normal resizing, without masks.

        The command arguments work as follow :
        - The first argument is optional and can be a mention/user ID of someone to use their avatar.
          If it's not supplied, the bot will look for an attached image.
        - The next two arguments are the width and the height percentages to keep. They must both be > 0, but can also
          go higher than 100 if you want to upscale the image

        - The image format argument can be any of
          • jpeg, for a still image
          • gif, for an animated resizing. Limited quality
          • png for an APNG (Animated PNG - discord doesn't support them well and will only show the first frame,
            open in browser)
          • webp for an animated WebP, a new-ish format that discord doesn't support at all. Try opening those in your
            browser
        """
        if width_pct <= 0 or height_pct <= 0:
            await ctx.send(
                "❌ Please use positive integers for width and height.")
            return
        if image_format not in ALLOWED_FORMATS:
            await ctx.send(f"❌ Please use a format in {ALLOWED_FORMATS}.")
            return

        status_message = await ctx.send(
            "<a:typing:597589448607399949> Downloading image...")
        async with ctx.typing():
            start = time.perf_counter()

            for attachment in ctx.message.attachments:
                if attachment.content_type.startswith('image/'):
                    image_bytes = await attachment.read()
                    break
            else:
                if who:
                    image_bytes = await who.avatar.replace(format="jpg",
                                                           size=512).read()
                else:
                    image_bytes = await ctx.author.avatar.replace(
                        format="jpg", size=512).read()

            end_dl = time.perf_counter()
            dl_time = round(end_dl - start, 1)

            await status_message.edit(
                content=f"✅ Downloading image... {dl_time}s\n"
                f"<a:typing:597589448607399949> Processing image...")

            if image_format in {"gif", "webp", "png"}:
                fn = partial(resize_image_gif, image_bytes, width_pct,
                             height_pct, image_format)
            else:
                fn = partial(resize_image, image_bytes, width_pct, height_pct)

            final_buffer, src_h, src_w, dst_h, dst_w = await self.bot.loop.run_in_executor(
                None, fn)

            end_processing = time.perf_counter()
            processing_time = round(end_processing - end_dl, 1)

            await status_message.edit(
                content=f"✅ Downloading image... {dl_time}s\n"
                f"✅ Processing image... {processing_time}s "
                f"({src_w} x {src_h} -> {dst_w} x {dst_h})\n", )

            # prepare the file
            file = discord.File(filename="seam_carving." + image_format,
                                fp=final_buffer)

            # send it
            await ctx.reply(file=file)