async def ping(self, ctx): """Shows bot ping.""" start = time.perf_counter() message = await ctx.send(embed=info("Pong!", ctx.me)) end = time.perf_counter() duration = (end - start) * 1000 await message.edit(embed=info(f":ping_pong: {duration:.2f}ms", ctx.me, "Pong!"))
async def deadline(self, ctx): """Shows how much time until Code Jam is over.""" try: time_until_string = get_utc_time_until(year=2020, month=11, day=17, hour=23, minute=59, second=59) await ctx.send(embed=info(time_until_string, ctx.me, title="Code Jam ends in:")) except ValueError: await ctx.send(embed=info("Code Jam is over!", member=ctx.me, title="Finished"))
async def slap(self, ctx, member: discord.Member): """Slaps a member.""" if ctx.author == member: embed = info(f"{member.mention} slapped him/her self LOL", ctx.me, "Slap!") img_url = "https://media.giphy.com/media/j1zuL4htGTFQY/giphy.gif" else: embed = info(f"{member.mention} got slapped in the face by: {ctx.author.mention}!", ctx.me, "Slap!") img_url = "https://66.media.tumblr.com/05212c10d8ccfc5ab190926912431344/tumblr_mt7zwazvyi1rqfhi2o1_400.gif" embed.set_image(url=img_url) await ctx.send(embed=embed)
async def on_member_join(self, member: Member): inviter = await self.tracker.track_invite() if inviter: await self.log_channel.send(embed=embed_handler.info( f"New member {member} was invited by {inviter}", member=member, title="" )) else: await self.log_channel.send(embed=embed_handler.info( f"New member {member} joined through discord Discovery", member=member, title="" ))
async def _mass_ban_timestamp_helper(self, ctx, timestamp_start: datetime, timestamp_end: datetime, reason: str): members_to_ban = [] for member in self.tortoise_guild.members: if member.joined_at is None: continue if timestamp_start < member.joined_at < timestamp_end: members_to_ban.append(member) if not members_to_ban: return await ctx.send(embed=failure("Could not find any members, aborting..")) members_to_ban.sort(key=lambda m: m.joined_at) reaction_msg = await ctx.send( embed=warning( f"This will ban {len(members_to_ban)} members, " f"first one being {members_to_ban[0]} and last one being {members_to_ban[-1]}.\n" f"Are you sure you want to continue?" ) ) confirmation = await ConfirmationMessage.create_instance(self.bot, reaction_msg, ctx.author) if confirmation: one_tenth = len(members_to_ban) // 10 notify_interval = one_tenth if one_tenth > 50 else 50 await ctx.send( embed=info( f"Starting the ban process, please be patient.\n" f"You will be notified for each {notify_interval} banned members.", ctx.author ) ) logger.info(f"{ctx.author} is timestamp banning: {', '.join(str(member.id) for member in members_to_ban)}") for count, member in enumerate(members_to_ban): if count != 0 and count % notify_interval == 0: await ctx.send(embed=info(f"Banned {count} members..", ctx.author)) await ctx.guild.ban(member, reason=reason) message = f"Successfully mass banned {len(members_to_ban)} members!" await ctx.send(embed=success(message)) await self.deterrence_log_channel.send(embed=authored(message, author=ctx.author)) else: await ctx.send(embed=info("Aborting mass ban.", ctx.me))
async def deal_with_long_code(self, message: Message) -> bool: """ When someone sends long message containing code, bot will delete it and upload message content to our pastebin and reply with it's link. Guessing is quite CPU intensive so be sure to check it only for long messages (not for each). :param message: message to check :return: bool, was the passed message deleted or not? """ if len(message.content) <= constants.max_message_length: return False await message.channel.trigger_typing() language = await self.bot.loop.run_in_executor( None, functools.partial(self.guess_language.language_name, source_code=message.content)) if not language or language == "Markdown": # Markdown can be too similar to just regular Discord message so just ignore it. # Also ignore if language could not be detected. return False pastebin_link = await self.create_pastebin_link( message.content.encode()) await message.delete() msg = ( f"Hey {message.author}, I've uploaded your long **{language}** code to our pastebin: {pastebin_link}" ) await message.channel.send(embed=info(msg, message.guild.me, "")) return True
async def shoot(self, ctx, member: discord.Member): """Shoots a member.""" embed = info( f"{member.mention} shot by {ctx.author.mention} :gun: :boom:", ctx.me, "Boom!") embed.set_image(url="https://i.gifer.com/XdhK.gif") await ctx.send(embed=embed)
async def promote(self, ctx, member: discord.Member, role: discord.Role): """Promote member to role.""" if role >= ctx.author.top_role: await ctx.send( embed=failure("Role needs to be below you in hierarchy.")) return elif role in member.roles: await ctx.send(embed=failure( f"{member.mention} already has role {role.mention}!")) return await member.add_roles(role) await ctx.send(embed=success( f"{member.mention} is promoted to {role.mention}", ctx.me), delete_after=5) dm_embed = info(( f"You are now promoted to role **{role.name}** in our community.\n" f"`'With great power comes great responsibility'`\n" f"Be active and keep the community safe."), ctx.me, "Congratulations!") dm_embed.set_footer(text="Tortoise community") await member.send(embed=dm_embed)
async def change_volume(self, ctx, *, volume: float): """Change the player volume. Parameters ------------ volume: float or int [Required] The volume to set the player to in percentage. This must be between 1 and 100. """ vc = ctx.voice_client if not vc or not vc.is_connected(): return await ctx.send( embed=failure("I am not currently connected to voice!")) if not 0 < volume < 101: return await ctx.send( embed=failure("Please enter a value between 1 and 100.")) player = self.get_player(ctx) if vc.source: vc.source.volume = volume / 100 player.volume = volume / 100 await ctx.send( embed=info(f"**`{ctx.author}`** set the volume to **{volume}%**", ctx.me, title="Volume update"))
async def create_source(cls, ctx, search: str, *, loop, download=False): loop = loop or asyncio.get_event_loop() to_run = partial(ytdl.extract_info, url=search, download=download) data = await loop.run_in_executor(None, to_run) if "entries" in data: # take first item from a playlist data = data["entries"][0] await ctx.send(embed=info( f"```ini\n[Added {data['title']} to the queue.]```", ctx.me, "")) if download: source = ytdl.prepare_filename(data) else: return { "webpage_url": data["webpage_url"], "requester": ctx.author, "title": data["title"] } return cls(discord.FFmpegPCMAudio(source, **ffmpeg_options), data=data, requester=ctx.author)
async def _wait_for(self, container: set, user: discord.User) -> Union[discord.Message, None]: """ Simple custom wait_for that waits for user reply for 5 minutes and has ability to cancel the wait, deal with errors and deal with containers (which mark users that are currently doing something aka event submission/bug report etc). :param container: set, container holding active user sessions by having their IDs in it. :param user: Discord user to wait reply from :return: Union[Message, None] message representing user reply, can be none representing invalid reply. """ def check(msg): return msg.guild is None and msg.author == user container.add(user.id) await user.send(embed=info( "Reply with single message, link to paste service or uploading utf-8 `.txt` file.\n" "You have 5m, type `cancel` to cancel right away.", user)) try: user_reply = await self.bot.wait_for("message", check=check, timeout=300) except TimeoutError: container.remove(user.id) await user.send(embed=failure("You took too long to reply.")) return if user_reply.content.lower() == "cancel": container.remove(user.id) await user.send(embed=success("Successfully canceled.")) return return user_reply
async def choice(self, ctx, *, args): """Returns a randomly chosen string from given arguments""" choices = args.split(",") if len(choices): choice = random.choice(choices) await ctx.send(embed=info( f"🎰 | Random choice | **{choice.strip()}**", ctx.me, title=""))
async def verify_member(self, member_id: str): """ Adds verified role to the member and also sends success messages. :param member_id: str member id to verify """ try: member_id = int(member_id) except ValueError: raise EndpointBadArguments() none_checks = (self.tortoise_guild, self.verified_role, self.new_member_role, self.successful_verifications_channel, self.welcome_channel) for check_none in none_checks: if check_none is None: logger.warning( f"One of necessary IDs was not found {none_checks}") raise DiscordIDNotFound() # Attempt to fix bug with verification where sometimes member is not found in cache even if they are in guild tortoise_guild = self.bot.get_guild(constants.tortoise_guild_id) member = tortoise_guild.get_member(member_id) if member is None: logger.critical( f"Can't verify, member is not found in guild {member} {member_id}" ) raise DiscordIDNotFound() await member.add_roles(self.verified_role, self.new_member_role, reason="Completed Oauth2 Verification") await self.successful_verifications_channel.send( embed=info(f"{member} is now verified.", member.guild.me, title="") ) msg = (f"You are now verified {self.verified_emoji}\n\n" f"Make sure to read {self.welcome_channel.mention}") await self.general_channel.send( member.mention, embed=info(f"Say hi to our newest member {member.mention}", member.guild.me, title=""), delete_after=100) await member.send(embed=success(msg))
async def invite(self, ctx): """Shows invite to our Tortoise Advent of Code leaderboard.""" invite_embed = info( f"Use this code to join Tortoise AoC leaderboard: **{self.TORTOISE_LEADERBOARD_INVITE}**\n\n" "To join you can go to the AoC website: https://adventofcode.com/2020/leaderboard", title="Tortoise AoC", member=ctx.guild.me) await ctx.send(embed=invite_embed)
async def update_message(self): await self._message.edit( embed=info( self.get_message_content(), self._get_bot_member_from_destination(self._message.channel), title=self._embed_title ) )
async def create_message(self, destination) -> None: self._message = await destination.send( embed=info( self.get_message_content(), self._get_bot_member_from_destination(destination), title=self._embed_title ) )
async def is_verified(self, ctx, member: DatabaseMember): try: response = await self.bot.api_client.is_verified(member) except ResponseCodeError as e: msg = f"Something went wrong, got response status {e.status}.\nDoes the member exist?" await ctx.send(embed=failure(msg)) else: await ctx.send(embed=info(f"Verified: {response}", ctx.me, title=f"{member}"))
async def dice(self, ctx, times: int = 1): """Rolls a dice""" if times == 1: dice_roll = random.randint(1, 6) await ctx.send(embed=info( f"🎲 | Dice Roll | **{dice_roll}**", ctx.me, title="")) elif times <= 25: dice_roll = ", ".join( str(random.randint(1, 6)) for _ in range(times)) await ctx.send( embed=info(f"🎲 | Dice Rolled {times} times | **{dice_roll}**", ctx.me, title="")) else: await ctx.send(embed=info( "Oops! You can't roll that many times. Try a number less than 25", ctx.me, title=""))
async def shuffle(self, ctx, *, args): """Returns a shuffled sequence of given arguments""" choices = [word.strip() for word in args.split(",")] if len(choices): random.shuffle(choices) await ctx.send( embed=info(f"📃 | Random shuffle | **{', '.join(choices)}**", ctx.me, title=""))
async def create_new_suggestion_message(self) -> int: suggestions_channel = self.bot.get_channel( constants.suggestions_channel_id) suggestion_embed = info(self.SUGGESTION_MESSAGE_CONTENT, suggestions_channel.guild.me, "New suggestion") msg = await suggestions_channel.send(embed=suggestion_embed) suggestion_emoji = self.bot.get_emoji(constants.suggestions_emoji_id) await msg.add_reaction(suggestion_emoji) return msg.id
async def show_data(self, ctx, member: DatabaseMember): try: data = await self.bot.api_client.get_member_data(member) except ResponseCodeError as e: msg = f"Something went wrong, got response status {e.status}.\nDoes the member exist?" await ctx.send(embed=failure(msg)) else: pretty = "\n".join(f"{key}:{value}\n" for key, value in data.items()) await ctx.send(embed=info(pretty, ctx.me, "Member data"))
async def ask(self, ctx): content = ( "Don't ask to ask, just ask.\n\n" " • You will have much higher chances of getting an answer\n" " • We can skip the whole process of actually getting the question out of you thus you will get " "answer faster\n\n" "For more info visit https://dontasktoask.com/") embed = info(content, ctx.me, "") message = await ctx.send(embed=embed) await RemovableMessage.create_instance(self.bot, message, ctx.author)
async def on_message_delete(self, message): if message.content == "": return # if it had only attachment for example msg = (f"**Message deleted in** {message.channel.mention}\n\n" f"**Message: **{message.content}") embed = info(msg, message.guild.me, "") embed.set_footer(text=f"Author: {message.author}", icon_url=message.author.avatar_url) await self.log_channel.send(embed=embed)
async def coin(self, ctx, times: int = 1): """Tosses a coin""" sample_space = ("Head", "Tail") if times == 1: coin_toss = sample_space[random.randint(0, 1)] await ctx.send(embed=info( f":coin: | Coin Toss | **{coin_toss}**", ctx.me, title="")) elif times <= 25: coin_toss = ", ".join(sample_space[random.randint(0, 1)] for _ in range(times)) await ctx.send(embed=info( f":coin: | Coin tossed {times} times | **{coin_toss}**", ctx.me, title="")) else: await ctx.send(embed=info( "Oops! You can't toss that many times. Try a number less than 25", ctx.me, title=""))
async def submit(self, ctx): """Initializes process of submitting code for event.""" fake_payload = SimpleNamespace() fake_payload.user_id = ctx.author.id fake_payload.emoji = self.bot.get_emoji(constants.event_emoji_id) await self.bot.get_cog("TortoiseDM").on_raw_reaction_add_helper( fake_payload) await ctx.send(embed=info( "Check your DMs.\n" "Note: if you already have active DM option nothing will happen.", ctx.me))
async def rule(self, ctx, alias: Union[int, str]): """Shows rule based on number order or alias.""" if isinstance(alias, int): rule_dict = self._get_rule_by_value(alias) else: rule_dict = self._get_rule_by_alias(alias) if rule_dict is None: await ctx.send(embed=failure("No such rule."), delete_after=5) else: await ctx.send(embed=info(rule_dict["statement"], ctx.guild.me, f"Rule: {rule_dict['name']}"))
async def _deal_with_vulgar_words(self, message: Message): for category, banned_words in self.banned_words.loaded.items(): for banned_word in banned_words: if banned_word in message.content.lower(): embed = info( f"Curse word **{banned_word}** detected from the category **{category}**", message.guild.me, "" ) embed.set_footer(text=f"Author: {message.author}", icon_url=message.author.avatar_url) await self.log_channel.send(embed=embed)
async def pfp(self, ctx, member: discord.Member = None): """Displays the profile picture of a member.""" if member is None: message, url = "Your avatar", ctx.author.avatar_url elif member == ctx.me: message, url = "My avatar", member.avatar_url else: message, url = f"{member} avatar", member.avatar_url embed = info(message, ctx.me) embed.set_image(url=url) await ctx.send(embed=embed)
async def on_first_ready(self): self.load_extensions() await self.change_presence(activity=discord.Game(name="DM me!")) await self.reload_tortoise_meta_cache() try: version = subprocess.check_output(["git", "describe", "--always" ]).strip().decode("utf-8") bot_log_channel = self.get_channel(bot_log_channel_id) await bot_log_channel.send(embed=info( f"Bot restarted. Image version `{version}`", self.user, "")) except Exception as e: logger.info("Git image version not found", e)
async def deal_with_attachments(self, message: Message) -> bool: """ Will delete message if it has attachment that we don't allow. If it's a whitelisted extension it will upload it's content to our pastebin instead and reply with link to it. :param message: message to check for attachments :return: bool, was the passed message deleted or not? """ reply = None delete_message_flag = False for attachment in message.attachments: try: extension = attachment.filename.rsplit('.', 1)[1] except IndexError: extension = "" # file has no extension extension = extension.lower() if extension in extension_to_pastebin: # Maximum file size to upload to Pastebin is 4MB if attachment.size > 4 * 1024 * 1024: delete_message_flag = True reply = ( f"Hey {message.author} , your {extension} file is over 4MB so I will be deleting it.\n\n" f"If you have a question please have a minimum reproducible code example." ) else: file_content = await attachment.read() url = await self.create_pastebin_link(file_content) reply = ( f"Hey {message.author} , I've uploaded your file to our pastebin for easier viewing: " f"[**{attachment.filename}** {url}]") elif extension not in allowed_file_extensions: reply = ( f"Hey {message.author}, {extension} file extension is not allowed here.\n " "If you believe this is a mistake please contact admins.") if reply: delete_message_flag = True msg = await message.channel.send(f"{message.author.mention}!", embed=info( reply, message.guild.me)) self.bot.loop.create_task( RemovableMessage.create_instance(self.bot, msg, message.author)) if delete_message_flag: await message.delete() # for return handler to know if og msg got deleted, so it doesn't run additional checks return delete_message_flag