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." )
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)
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." ))
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." )
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)
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)
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."))
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.")
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 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." )
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." )
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)