async def quote_alias(self, ctx: utils.Context, quote_id: commands.clean_content, alias: commands.clean_content): """ Adds an alias to a quote. """ # Grab data from db async with self.bot.database() as db: rows = await db( "SELECT * FROM user_quotes WHERE quote_id=$1 AND guild_id=$1", quote_id.lower(), ctx.guild.id) if not rows: return await ctx.send( f"There's no quote with the ID `{quote_id.upper()}`.") # Insert alias into db async with self.bot.database() as db: rows = await db("SELECT * FROM quote_aliases WHERE alias=$1", alias) if rows: return await ctx.send( f"The alias `{alias}` is already being used.") await db( "INSERT INTO quote_aliases (quote_id, alias) VALUES ($1, $2)", quote_id.lower(), alias.lower()) await ctx.send( f"Added the alias `{alias.upper()}` to quote ID `{quote_id.upper()}`." )
async def create(self, ctx: commands.Context, name: str, *, value: commands.clean_content) -> None: """Create tags for your server. Example: tag create hello Hi! I am the bot responding! Complex usage: https://github.com/fourjr/rainbot/wiki/Tags """ if value.startswith('http'): if value.startswith('https://hasteb.in') and 'raw' not in value: value = 'https://hasteb.in/raw/' + value[18:] async with self.bot.session.get(value) as resp: value = await resp.text() if name in [i.qualified_name for i in self.bot.commands]: await ctx.send('Name is already a pre-existing bot command') else: await self.bot.db.update_guild_config( ctx.guild.id, {'$push': { 'tags': { 'name': name, 'value': value } }}) await ctx.send(self.bot.accept)
async def _remove(self, ctx, channel: discord.TextChannel = None, *, target: commands.clean_content): """ Remove a competition from a live-scores channel Either perform ls remove <league name> in the channel you wish to delete it from, or use ls remove #channel <league name> to delete it from another channel.""" # Verify we have a valid livescores channel target. channel = ctx.channel if channel is None else channel if channel.id not in {i[1] for i in self.cache}: return await ctx.send(f'{channel.mention} is not set as a scores channel.') # Verify which league the user wishes to remove. target = target.strip("'\",") # Remove quotes, idiot proofing. leagues = self.cache[(ctx.guild.id, channel.id)] matches = [i for i in leagues if target.lower() in i.lower()] index = await embed_utils.page_selector(ctx, matches) if index is None: return # rip. target = matches[index] connection = await self.bot.db.acquire() async with connection.transaction(): await connection.execute(""" DELETE FROM scores_leagues WHERE (league,channel_id) = ($1,$2)""", target, channel.id) leagues.remove(target) leagues = ", ".join(leagues) await ctx.send(f"✅ **{target}** was deleted from the tracked leagues for {channel.mention}," f" the new tracked leagues list is: ```yaml\n{leagues}```\n") await self.bot.db.release(connection) await self.update_cache()
async def create(self, ctx, tag_name: commands.clean_content, *, tag_value: commands.clean_content): tag_name = str(tag_name) if tag_name.lower() in ['remove', 'create']: return await ctx.send("Cannot make tag that is a tag command.") if len(tag_name.strip() ) > 100: # tbh idk if this check is really a good one return await ctx.send("Tag name too long (100 or less characters)") if len(str(tag_value)) >= 1800: return await ctx.send("Tag value too long (1800 or less characters" ) g = mongo_setup.mod_and_logging_config.find_one( {'_id': ctx.guild.id} ) # if theres tags for ctx.guild, then the dict should be formatted like: {'tag name': {'author': user id, 'value': 'tag value'}} if not g: mongo_setup.tags.insert_one({'_id': ctx.guild.id }) # if theres no tags for ctx.guild g = mongo_setup.tags.find_one({'_id': ctx.guild.id}) t = g.get(tag_name, None) # checking if the tag is already in the dict if not t: d = {'value': tag_value, 'author': ctx.author.id} mongo_setup.tags.update_one({'_id': ctx.guild.id}, {'$set': { tag_name: d }}) return await ctx.send(f"Tag {tag_name} created!") await ctx.send(f"Tag {tag_name} already exists")
async def repeat(self, ctx, num: int, *, text: commands.clean_content): if "spam" not in ctx.channel.name: raise commands.BadArgument("This command is only " "available in spam channels.") ssplit = text.split("\\") suffix = ssplit[-1] if len(ssplit) > 1 else "" text = ssplit[0] num = min(num, 50) fmt = (text.replace("%count%", "{0}").replace("%countBackwards%", "{1}").replace( "%enumerate%", "{2}")) for i in range(1, num + 1): if i % 10 == 1 and i != 11: enumend = "st" elif i % 10 == 2 and i != 12: enumend = "nd" elif i % 10 == 3 and i != 13: enumend = "rd" else: enumend = "th" text = fmt.format(i, num - i + 1, f"{i}{enumend}") await ctx.send(text) await asyncio.sleep(2) if suffix: await ctx.send(suffix)
async def two_cats(self, ctx, *, text: commands.clean_content): """Write some text on two signs to write on both signs split the text with || or | if no separator is passed the text will be halved and written on both signs.""" image = ImageDrawText(self.bot, "images/two_cats.jpg") text = ctx.emote_unescape(text) try: text, text_two = text.split("|", 1) or text.split("||", 1) except ValueError: index = len(text) // 2 text_two = " ".join([text[index:]]) text = " ".join([text[:index]]) if not text: text = text_two # this is an invisible character text_two = "\u200b" text = self.string_splice(text, 41) text_two = self.string_splice(text_two, 41) image.font_setter(self.arial_unicode, 18, (6, 0, 15)) text = image.text_wrap(text, 14) text_two = image.text_wrap(text_two, 14) rotate = 358 await image.draw_text_on_image_rotated(text, [67, 217], rotate, True) await image.draw_text_on_image_rotated(text_two, [268, 232], rotate, True) file = discord.File(filename="sign.png", fp=image.save()) await ctx.send(file=file)
async def binTotxt(self, ctx, *, content: commands.clean_content): """``binTotxt [hex code from .bin file]`` Converts .bin hex code into assembly.""" if await self.checklol(ctx): return content = content.replace("```\n", "").replace("```", "") out = (f"```c\nAddress\t\t\t Memory Content\n{'-'*49}\n" + "\n ".join( [(f'{int(j[0], 16)}\t{" ".join([str(bin(int(j[1], 16))).lstrip("0b").zfill(32)[m:m + 4] for m in range(0, 32, 4)])}' if len(j) > 1 else "") for j in [i.split() for i in content.split("\n")]]) + "```") await ctx.send(out)
async def clapup( self, ctx, *, text: commands. clean_content = 'you forgot to supply text :bigbrain10000:'): """clap 👏 up 👏 some 👏 text""" text = str(text) text = text.replace('.', '\u200B.') return await ctx.send( str(' \N{CLAPPING HANDS SIGN} '.join(text.split()))[:2000])
async def b(self, ctx, *, message: commands.clean_content): """This is a bad idea.""" if 'b' in message.lower(): return await ctx.send( message.replace('b', ':b:').replace('B', ':b:')) consonants = set( [x for x in message if x.lower() in "bcdfghjklmnpqrstvwxyz"]) await ctx.send( message.replace( random.choice(tuple(consonants) if consonants else message), ':b:'))
async def cardexp(self, ctx: commands.Context, *, arg: commands.clean_content = ''): assert isinstance(arg, str) exp = self.bot.assets[Server.JP].card_exp_master def comma_number(n): return '{:,}'.format(n) def format_exp(e): return comma_number(e.total_exp).rjust(9) if not arg: embed = discord.Embed(title='Card Exp', description='```' + '\n'.join(f'Lvl {n}: {format_exp(exp[n])}' for n in range(10, 90, 10)) + '```') await ctx.send(embed=embed) else: try: if arg.isnumeric(): level = int(arg) level_total = exp[level].total_exp desc = ( f'```\n' f'Total: {comma_number(level_total)}\n' f'Change: {comma_number(level_total - exp[level - 1].total_exp) if level > 1 else "N/A"}\n' f'```') await ctx.send(embed=discord.Embed( title=f'Card Exp Lvl {level}', description=desc)) else: start, end = arg.split('-') start = int(start) end = int(end) if start > end: await ctx.send('End exp is greater than start exp.') return start_exp = exp[start] end_exp = exp[end] change_amount = end_exp.total_exp - start_exp.total_exp embed = discord.Embed( title=f'Card Exp Lvl {start}-{end}', description=f'```\n' f'Lvl {str(start).rjust(2)}: {format_exp(start_exp)}\n' f'Lvl {str(end).rjust(2)}: {format_exp(end_exp)}\n' f'Change: {comma_number(change_amount).rjust(9)}\n' f'```') await ctx.send(embed=embed) except Exception: await ctx.send(f'Invalid card exp {arg}')
async def match(self, ctx, *, content: commands.clean_content): """<person1>, <person2>|||Returns a match percentage between two people <3""" match = str(random.randint(0, 100)) content = content.replace(", ", ",").replace(" ,", ",") if "," not in content: raise commands.UserInputError() content = content.split(",") left, right = content await self.Hamood.quick_embed( ctx, description= f"**{left}** and **{right}** are **{match}%** compatible.")
async def poll(self, ctx, *, options: commands.clean_content = ""): '''Create a poll for members in the channel to vote on. Separate options using the " | " character.''' options = options or await ctx.ask("Give a list of options for the poll, separated by the `|` character:") letters = ["🇦", "🇧", "🇨", "🇩", "🇪", "🇫", "🇬", "🇭", "🇮", "🇯"] options = dict(zip(letters, options.split("|"))) self.ona.assert_(len(options) > 1, error="Only one option provided. Separate options with the `|` character.") embed = self.ona.embed("\n\n".join(f"{letter} {option}" for letter, option in options.items()), title=f"{ctx.author.display_name}'s Poll") poll = await ctx.send(f"{ctx.author.mention} React with ⏹ when you'd like to end the poll.", embed=embed) for letter in options: await poll.add_reaction(letter) await poll.add_reaction("⏹") def check(r, u): return u == ctx.author and r.message.id == poll.id and r.emoji == "⏹" await self.ona.wait_for("reaction_add", check=check) # Continue only after author reacts with the stop emote votes = defaultdict(list) for reaction in (await ctx.channel.fetch_message(poll.id)).reactions: if reaction.custom_emoji or reaction.emoji not in options: # Ignore miscellaneous reacts continue async for member in reaction.users().filter(lambda u: not u.bot): if not any(member == existing_member for emoji in votes for existing_member in votes[emoji]): votes[reaction.emoji].append(member) # Ignore duplicate votes if ctx.channel.permissions_for(ctx.me).manage_messages: await poll.clear_reactions() await ctx.table({options[letter]: len(voters) for letter, voters in votes.items()}, title="final results", label="vote")
async def get(self, ctx:utils.Context, identifier:commands.clean_content): """Gets a quote from the database""" # Get quote from database async with self.bot.database() as db: quote_rows = await db( """SELECT user_quotes.quote_id as quote_id, user_id, channel_id, message_id FROM user_quotes LEFT JOIN quote_aliases ON user_quotes.quote_id=quote_aliases.quote_id WHERE user_quotes.quote_id=$1 OR quote_aliases.alias=$1""", identifier.lower(), ) if not quote_rows: return await ctx.send(f"There's no quote with the identifier `{identifier.upper()}`.") # Get the message data = quote_rows[0] if data['channel_id'] is None: return await ctx.send("There's no quote channel set for that quote.") channel = self.bot.get_channel(data['channel_id']) if channel is None: return await ctx.send("I wasn't able to get your quote channel.") try: message = await channel.fetch_message(data['message_id']) assert message is not None except (AssertionError, discord.HTTPException): return await ctx.send("I wasn't able to get your quote message.") # Output to user return await ctx.send(embed=message.embeds[0])
async def choose(self, ctx, *, choices: commands.clean_content): '''choose! use , in between''' choices = choices.split(',') if len(choices) < 2: return await ctx.send('Not enough choices to pick from.') choices[0] = ' ' + choices[0] await ctx.send(str(random.choice(choices))[1:])
async def spaceout(self, ctx, *, content: commands.clean_content): """``spaceout [binary machine code]`` spaces out your binary code""" if await self.checklol(ctx): return c = content.replace(" ", "").replace("```", "") x = " ".join([c[i:i + 4] for i in range(0, len(c), 4)]) await ctx.send(f"```java\n{x}```")
async def alias_remove(self, ctx:utils.Context, alias:commands.clean_content): """Deletes an alias from a quote""" # Grab data from db async with self.bot.database() as db: await db("DELETE FROM quote_aliases WHERE alias=$1", alias.lower()) return await ctx.send(f"Deleted alias `{alias.upper()}`.")
async def remind(self, ctx, *, argument: commands.clean_content): """remind yourself after given time in the channel the command was invoked in or privately with a reason or without""" args = argument.split("\n") time = args.pop(0) if args: reason = "\n".join(args).strip()[:self.char_limit] else: reason = "No Reason" kwargs = {'locales': ["de-BE"], 'settings': self.set} expected_date = dateparser.parse(time, **kwargs) if expected_date is None: msg = "No valid time format" await ctx.send(msg) return current_date = datetime.now() difference = (expected_date - current_date).total_seconds() embed = discord.Embed(colour=discord.Color.green()) embed.description = "**Reminder registered:**" represent = expected_date.strftime(self.preset) embed.set_footer(text=represent) if difference < 0: msg = "The timestamp has already passed" await ctx.send(msg) return current_stamp = current_date.timestamp() expected_stamp = expected_date.timestamp() arguments = [ctx.author.id, ctx.channel.id, current_stamp, expected_stamp, reason] reminder = Timer(self.bot, arguments) if difference < 60: await ctx.send(embed=embed) await asyncio.sleep(difference) await reminder.send() else: query = 'INSERT INTO reminder ' \ '(author_id, channel_id, creation, expiration, reason)' \ ' VALUES ($1, $2, $3, $4, $5)' cursor = await self.bot.db.execute(query, arguments) reminder.id = cursor.lastrowid await self.bot.db.commit() if not self.current_reminder: self.current_reminder = reminder self._lock.set() else: if reminder.expiration < self.current_reminder.expiration: self.restart(reminder) logger.debug(f"reminder {reminder.id}: registered") embed.description = f"{embed.description[:-3]} (ID {reminder.id}):**" await ctx.send(embed=embed)
async def roll(self, ctx: Context, *, message: clean_content): display_name = get_name_string(ctx.message) if len(message) == 0: message = "1d6" message = message.strip() try: expression = DiceParser.parse_all.parse(message).or_die() except ParseError as e: await ctx.send( FAILURE_OUT.format(ping=display_name, body=f"```{e}```")) return except ExcessiveDiceRollsError: await ctx.send( WARNING_OUT.format( ping=display_name, body="_You requested an excessive number of dice rolls._", )) return value = expression.value try: out = SUCCESS_OUT.format( ping=display_name, body= f"`{repr(expression)}` | **{value}** ⟵ {clean_brackets(str(expression))}", ) await ctx.send(out) except OutputTooLargeError: await ctx.send( WARNING_OUT.format(ping=display_name, body="_Your output was too large!_"))
async def memegen(self, ctx, *, text: commands.clean_content = 'Вот такие пироги'): """Генератор мемов. *Сооруди свой топовый мем!* [!] Команда может быть выполнена лишь раз в 8 секунд. Аргументы: `:text` - текст (% - перенос вниз) __ __ Например: ``` n!memegen Вот такие пироги ``` """ string_list = text.split('%') templates = [f'templates/{x}' for x in os.listdir('templates/')] if len(string_list) == 1: make_meme(topString=string_list[0], bottomString='', outputFilename=ctx.guild.id, filename=choice(templates)) elif len(string_list) >= 2: make_meme(topString=string_list[0], bottomString=string_list[1], outputFilename=ctx.guild.id, filename=choice(templates)) await ctx.send(file=discord.File(fp=f'{ctx.guild.id}.png')) await asyncio.sleep(5) os.remove(f'{ctx.guild.id}.png')
async def cryptoprice(self, ctx, cryptocurrency: commands.clean_content = 'bitcoin', \ currency: commands.clean_content = 'rub'): """Стоимость криптовалют. Аргументы: `:cryptocurrency` - имя криптовалюты `:currency` - имя валюты __ __ Например: ``` n!cryptoprice bitcoin rub ``` """ request = requests.get(f'https://api.coinmarketcap.com/v1/ticker/{cryptocurrency}/?convert={currency}') resp = request.json() if type(resp) is dict: return await ctx.send(f'Что-то пошло не так.\n*Может быть, {ctx.author.mention} ввел несуществующую валюту?') price = 'price_' + currency.lower() try: resp[0][price] except KeyError: await ctx.send(f'Что-то пошло не так.\n*Может быть, {ctx.author.mention} ввел несуществующую валюту?') else: embed = discord.Embed(color=0xF4F624, title=f'Стоимость криптовалюты {cryptocurrency}.', description=f'USD: `{resp[0]["price_usd"]}`\n{currency.upper()}: `{resp[0][price]}`') embed.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) embed.set_footer(text=f'{ctx.prefix}{ctx.command}') await ctx.send(embed=embed)
async def encode_ascii85(self, ctx, *, input: commands.clean_content = None): if not input: input = await self.detect_file(ctx) await self.encryptout( ctx, "Text -> ASCII85", base64.a85encode(input.encode('UTF-8')) )
async def encode_hex(self, ctx, *, input: commands.clean_content = None): if not input: input = await self.detect_file(ctx) await self.encryptout( ctx, "Text -> hex", binascii.hexlify(input.encode('UTF-8')) )
async def say(self, ctx, *, message: commands.clean_content): message_components = message.split() if "@everyone" in message_components or "@here" in message_components: await ctx.send("Nice try noob") return await ctx.send(message)
async def choose(self, ctx, *, choices: commands.clean_content): '''Choose between multiple choices. Use `,` to seperate choices.''' choices = choices.split(',') if len(choices) < 2: return await ctx.send('Not enough choices to pick from.') choices[0] = ' ' + choices[0] await ctx.send(str(random.choice(choices))[1:])
async def formatbin(self, ctx, *, content: commands.clean_content): """``formatbin [binary machine code]`` tries to find format of machine code""" if await self.checklol(ctx): return content = content.replace("```", "") output = self.format(content, True) await ctx.send(output)
async def choose(self, ctx, *, choices: commands.clean_content): """choose! use , in between Parameters • choices - the choices to choose from separated using ,""" choices = choices.split(",") choices[0] = " " + choices[0] await ctx.send(str(random.choice(choices))[1:])
async def encode_base32(self, ctx, *, input: commands.clean_content = None): if not input: input = await self.detect_file(ctx) await self.encryptout( ctx, "Text -> base32", base64.b32encode(input.encode('UTF-8')) )
async def _remove(self, ctx, channels: commands.Greedy[discord.TextChannel], *, target: commands.clean_content): """ Remove a competition from an existing live-scores channel """ # Verify we have a valid livescores channel target. channels = await self._pick_channels(ctx, channels) if not channels: return # rip all_leagues = set() target = target.strip("'\",") # Remove quotes, idiot proofing. for c in channels: # Fetch All partial matches leagues = self.cache[(ctx.guild.id, c.id)] all_leagues |= set( [i for i in leagues if target.lower() in i.lower()]) # Verify which league the user wishes to remove. all_leagues = list(all_leagues) index = await embed_utils.page_selector(ctx, all_leagues) if index is None: return # rip. target = all_leagues[index] for c in channels: if c.id not in {i[1] for i in self.cache}: await ctx.reply(f'{c.mention} is not set as a scores channel.', mention_author=True) continue connection = await self.bot.db.acquire() async with connection.transaction(): await connection.execute( """ DELETE FROM scores_leagues WHERE (league,channel_id) = ($1,$2)""", target, c.id) await self.bot.db.release(connection) leagues = self.cache[(ctx.guild.id, c.id)].copy() leagues.remove(target) await ctx.reply( f"✅ **{target}** deleted from the tracked leagues for {c.mention}", mention_author=False) await self.update_channel(c.guild.id, c.id) await send_leagues(ctx, c, leagues) await self.update_cache()
async def get_color_image(self, ctx, *, color_list: clean_content): """ Post a picture of a color or multiple colors when specifying multiple colors make sure colors that have a space in them like light blue are written using quotes like this `red "light blue" green` By default colors are concatenated horizontally. For vertical stacking use a newline like this ``` {prefix}{name} red green blue yellow ``` Which would have red and green on top and blue and yellow on bottom Color can be a \u200b \u200b \u200b• hex value (`#000000` or `0x000000`) \u200b \u200b \u200b• RGB tuple `(0,0,0)` \u200b \u200b \u200b• Color name. All compatible names are listed [here](https://en.wikipedia.org/wiki/List_of_colors_(compact)) \u200b \u200b \u200b• Any of `invisible`, `none`, `transparent` for transparent spot """ color_list = [shlex.split(c) for c in color_list.split('\n')] lengths = map(len, color_list) if sum(lengths) > 100: raise BadArgument('Maximum amount of colors is 100') images = [] hex_colors = [] size = (50, 50) def do_the_thing(): for colors in color_list: ims = [] for color in colors: color = self.color_from_str(color) try: im = Image.new('RGBA', size, color) ims.append(im) if isinstance(color, tuple): color = self.rgb2hex(*color) hex_colors.append(color) except (TypeError, ValueError): raise BadArgument( f'Failed to create image using color {color}') images.append(self.concatenate_colors(ims)) if len(images) > 1: concat = self.stack_colors(images) else: concat = images[0] data = BytesIO() concat.save(data, 'PNG') data.seek(0) return data data = await self.bot.loop.run_in_executor(self.bot.threadpool, do_the_thing) await ctx.send(' '.join(hex_colors), file=discord.File(data, 'colors.png'))
async def decode_ascii85(self, ctx, *, input: commands.clean_content = None): if not input: input = await self.detect_file(ctx) try: await self.encryptout(ctx, "ASCII85 -> Text", base64.a85decode(input.encode('UTF-8'))) except Exception: await ctx.send("Invalid ASCII85...")