class Module(ModuleBase): usage_doc = '{prefix}{aliases} <quesrion>' short_doc = 'Ask a question (answer is not guaranteed)' long_doc = ('Became possible with the use of https://nekos.life') name = 'why' aliases = (name, ) category = 'Services' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **options): question = args[1:] if question.endswith('?'): question = question[:-1] if len(question) > 251: question = question[:248] + '...' question = question[:1].lower() + question[1:] if question else '' e = Embed(colour=Colour.gold()) if question: e.title = f'Why {question}?' response = await neko_api_request('why') if not response: return '{error} Problem with api response. Please, try again later' e.description = response['why'] e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) await ctx.send(embed=e)
class Module(ModuleBase): short_doc = 'Get list of guilds I\'m in' name = 'guilds' aliases = (name, 'servers') category = 'Bot' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): guilds = sorted(self.bot.guilds, reverse=True, key=lambda g: (g.member_count, g.name)) lines = [f'{g.id:<19}| {g.name}' for g in guilds] lines_per_chunk = 30 chunks = [ f'```{"id":<19}| name\n{"-" * 53}\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk) ] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed(title=f'{len(self.bot.guilds)} guilds', colour=Colour.gold(), description=chunk) e.set_footer(text=f'Page {i + 1} / {len(chunks)}') p.add_page(embed=e) await p.run(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <colour>' short_doc = 'Display information about given colour' name = 'colourinfo' aliases = (name, 'colorinfo') category = 'Utils' bot_perms = (PermissionEmbedLinks(), ) min_args = 1 async def on_call(self, ctx, args, **flags): try: rgb = ImageColor.getrgb(args[1:]) colour = Colour.from_rgb(*rgb) except ValueError as e: return '{warning} Not a colour' bytes_img = BytesIO() img = Image.new('RGB', (100, 100), rgb) img.save(bytes_img, format='JPEG') bytes_img.seek(0) file = File(bytes_img, filename='img.jpg') e = Embed(colour=colour, title=str(colour)) e.add_field(name='Decimal value', value=colour.value) e.set_image(url='attachment://img.jpg') await ctx.send(embed=e, file=file)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [user]' short_doc = 'Get user avatar' name = 'avatar' aliases = (name, 'pfp') category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): if len(args) == 1: user = ctx.author else: user = await find_user(args[1:], ctx.message) if user is None: return '{warning} User not found' formats = ['png', 'webp', 'jpg'] if user.is_avatar_animated(): formats.insert(0, 'gif') e = Embed(colour=Colour.gold(), description=' | '.join( f'[{f}]({user.avatar_url_as(format=f)})' for f in formats)) e.set_author(name=user) e.set_image(url=user.avatar_url_as(static_format='png')) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <role>' short_doc = 'Get information about given role' name = 'role' aliases = (name, 'roleinfo') category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) min_args = 1 guild_only = True async def on_call(self, ctx, args, **flags): role = await find_role(args[1:], ctx.guild) if role is None: return '{warning} Role not found' e = Embed(colour=role.colour, title=role.name) e.add_field(name='Created', value=f'`{role.created_at.replace(microsecond=0)}`') e.add_field( name='Position', value= f'{len(ctx.guild.roles) - role.position}/{len(ctx.guild.roles)}') e.add_field(name='Members', value=len(role.members)) e.add_field(name='Permission bitfield', value=role.permissions.value) e.add_field(name='Colour', value=f'#{role.colour.value:06x}') e.add_field(name='Mentionable', value=role.mentionable) e.set_footer(text=role.id) await ctx.send(embed=e)
class Module(ModuleBase): short_doc = 'Get bot invite link' name = 'invite' aliases = (name, ) category = 'Bot' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): e = Embed(title='My invite links', colour=Colour.gold(), description=' | '.join( ('[admin](' + DISCORD_AUTH_URL.format(id=self.bot.user.id, perms=8) + ')', '[no admin](' + DISCORD_AUTH_URL.format( id=self.bot.user.id, perms=2146958583) + ')'))) owner = self.bot.get_user(BOT_OWNER_ID) if owner is None: try: owner = await self.bot.get_user_info(BOT_OWNER_ID) except NotFound: pass e.add_field( name='Contact developar', value= (f'Feel free to contact owner: **{owner}** [**{BOT_OWNER_ID}**]\n' f'[development server]({DEV_GUILD_INVITE})')) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <user>' short_doc = 'Get matching users list' name = 'users' aliases = (name, 'userlist') category = 'Discord' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): users = await find_user(args[1:], ctx.message, max_count=-1) if not users: return '{warning} Users not found' lines = [f'{u.id:<19}| {u}' for u in users] lines_per_chunk = 30 chunks = [f'```{"id":<19}| name\n{"-" * 53}\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk)] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed( title=f'Matching users ({len(lines)})', colour=Colour.gold(), description=chunk ) e.set_footer(text=f'Page {i + 1} / {len(chunks)}') p.add_page(embed=e) await p.run(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [user]' short_doc = 'Get list of common guilds with user' name = 'seenon' aliases = (name, ) category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): if len(args) == 1: user = ctx.author else: user = await find_user(args[1:], ctx.message) if not user: return '{warning} User not found' guilds = sorted( [g for g in self.bot.guilds if g.get_member(user.id) is not None], key=lambda g: (g.member_count, g.name), reverse=True) if not guilds: return '{warning} No common guilds' lines = [f'{g.id:<19}| {g.name}' for g in guilds] lines_per_chunk = 30 chunks = [ f'```{"id":<19}| name\n{"-" * 53}\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk) ] def make_embed(chunk, page=None): e = Embed(title=f'{len(guilds)} common guilds', colour=Colour.gold(), description=chunk) e.set_author(name=user, icon_url=user.avatar_url) if page is not None: e.set_footer(text=f'Page {i + 1} / {len(chunks)}') return e p = Paginator(self.bot) for i, chunk in enumerate(chunks): p.add_page(embed=make_embed(chunk, page=i)) await p.run(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [discriminator]' short_doc = 'Get list of users with given discriminator' name = 'discriminator' aliases = (name, 'discrim') category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): if len(args) == 1: discrim = ctx.author.discriminator else: if args[1].isdigit() and len(args[1]) == 4: discrim = args[1] else: return '{warning} Invalid discriminator given' matched = [] for user in self.bot.users: if user.discriminator == discrim and not user.bot and user != ctx.author: matched.append(user) if not matched: return '{warning} Users with discriminator **%s** not found' % discrim lines = sorted([str(u) for u in matched], key=lambda s: (len(s), s)) users_per_chunk = 30 chunks = [ lines[i:i + users_per_chunk] for i in range(0, len(lines), users_per_chunk) ] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed(description=f'```\n' + '\n'.join(chunk) + '```', colour=Colour.gold()) e.set_footer( text=f'Page {i + 1}/{len(chunks)} ({len(lines)}) results') p.add_page(embed=e) await p.run(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [user]' short_doc = 'Show list of roles for guild or user' name = 'roles' aliases = (name, 'listroles') category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) guild_only = True async def on_call(self, ctx, args, **flags): user = None if len(args) > 1: user = await find_user(args[1:], ctx.message, global_search=False) if user is None: return '{warning} User not found' roles = user.roles else: roles = ctx.guild.roles lines = [f'{r.id:<19}| {r.name}' for r in roles[::-1]] lines_per_chunk = 30 chunks = [ f'```{"id":<19}| name\n{"-" * 53}\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk) ] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed( title= f'''{'Guild' if user is None else str(user) + "'s"} roles ({len(lines)})''', colour=Colour.gold(), description=chunk) e.set_footer(text=f'Page {i + 1} / {len(chunks)}') p.add_page(embed=e) await p.run(ctx)
class Module(ModuleBase): short_doc = 'Very inspirational' name = 'inspire' aliases = (name, 'inspirebot') category = 'Services' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **options): p = UpdatingPaginator(self.bot) await p.run(ctx, self.paginator_update_func) async def paginator_update_func(self, p): async with self.bot.sess.get(API_URL) as r: if r.status == 200: e = Embed(colour=Colour.gold()) e.set_image(url=await r.text()) e.set_footer(text='Powered by https://inspirobot.me') return {'embed': e}
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <text>' short_doc = 'Make text kawaii' long_doc = ('Became possible with the use of https://nekos.life') name = 'owoify' aliases = (name, ) category = 'Services' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) async def on_not_enough_arguments(self, ctx): return '{warning} Pwease, gib me text to make it kawaii' async def on_call(self, ctx, args, **options): text = args[1:] if len(text) > 1000: return '{error} oopsie whoopsie, seems like youw text is longew thany 1000 symbols~~' chunks = [text[i:i + 200] for i in range(0, len(text), 200)] owo = '' for c in chunks: data = {'text': c} response = await neko_api_request('owoify', **data) if response is None: break owo += response['owo'] if not owo: return '{error} Problem with api response. Please, try again later' title = await neko_api_request('cat') e = Embed(colour=Colour.gold(), title=title.get('cat', None) or 'OwO') e.add_field(name=f'{ctx.author.display_name} just said...', value=owo) e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <text>' short_doc = 'Translate text' long_doc = ( 'Subcommands:\n' '\tlist: get list of languages\n\n' 'Command flags:\n' '\t[--in|-i] <language>: input language\n' '\t[--out|-o] <language>: output language' ) name = 'goodtranslator2' aliases = (name, 'gt2') category = 'Actions' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) flags = { 'in': { 'alias': 'i', 'bool': False }, 'out': { 'alias': 'o', 'bool': False } } async def on_load(self, from_reload): self.api_key = self.bot.config.get('yandex_api_key') if self.api_key: params = { 'key': self.api_key, 'ui': 'en' } async with self.bot.sess.get(API_URL + 'getLangs', params=params) as r: if r.status == 200: self.langs = (await r.json())['langs'] return raise Exception('No yandex api key in config or key is invalid') async def on_call(self, ctx, args, **flags): if args[1:].lower() == 'list': return '\n'.join(f'`{k}`: {v}' for k, v in self.langs.items()) in_lang = flags.get('in', None) if in_lang and in_lang.lower() not in self.langs: return '{warning} Invalid input language. Try using list subcommand' out_lang = flags.get('out', 'en').lower() if out_lang not in self.langs: return '{warning} Invalid out language. Try using list subcommand' params = { 'key': self.api_key, 'text': args[1:], 'lang': out_lang if in_lang is None else f'{in_lang}-{out_lang}' } try: async with self.bot.sess.post(API_URL + 'translate', params=params) as r: if r.status != 200: return '{error} Failed to translate. Please, try again later' r_json = await r.json() text = r_json['text'][0] source, destination = r_json['lang'].split('-') except Exception: return '{error} Failed to translate. Please, try again later' e = Embed(colour=Colour.gold(), title='GoodTranslator 2') e.description = text[:2048] e.add_field( name='Translated', value=f'{self.langs.get(source, source)} -> {self.langs[destination]}' ) e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <tag>' short_doc = 'Nekos' long_doc = ('Powered by https://nekos.life api\n\n' 'Subcommands:\n' '\tlist: shows list of available tags\n' '\tsfw: sends random sfw tag\n' '\tnsfw: sends random nsfw tag\n') name = 'nekos' aliases = (name, 'neko') category = 'Services' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **options): result = '' subcommand = args[1].lower() if subcommand == 'list': result = f'{"SFW": >11} | NSFW\n{"-" * 25}\n' result += '\n'.join(f'{x: >11} | {y}' for x, y in zip_longest( sorted(SFW_IMG_TAGS), sorted(NSFW_IMG_TAGS), fillvalue='')) return f'```\n{result}```' tag = None nsfw = False if subcommand in SFW_IMG_TAGS: tag = subcommand elif subcommand in NSFW_IMG_TAGS: tag = subcommand nsfw = True elif subcommand == 'sfw': tag = subcommand elif subcommand == 'nsfw': tag = subcommand nsfw = True else: return await self.on_doc_request(ctx) if getattr(ctx.channel, 'is_nsfw', lambda: isinstance(ctx.channel, DMChannel))() < nsfw: return await self.on_nsfw_permission_denied(ctx) p = UpdatingPaginator(self.bot) await p.run(ctx, self.paginator_update_func, update_args=(tag, )) async def paginator_update_func(self, p, tag): if tag == 'sfw': tag = random.choice(SFW_IMG_TAGS) elif tag == 'nsfw': tag = random.choice(NSFW_IMG_TAGS) response = await neko_api_image_request( TAG_NAME_REPLACEMENTS.get(tag, tag)) if response is not None: e = Embed(colour=Colour.gold(), title=f'Tag: {tag}', url=response['url']) e.set_image(url=response['url']) e.set_footer(text='Click on 🆕 reaction to get new image') return {'embed': e} else: return { 'content': 'Problem with api response. Please try again later' }
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <text>' short_doc = 'Make me say something (Google engine)' long_doc = ('Command flags:\n' '\t[--file|-f]: respond with audio file\n' '\t[--no-voice|-n]: don\'t use voice channel\n' '\t[--volume|-v] <value>: set volume in % from 0 to 200\n' '\t[--slow|-s]: use slow mode if added\n' '\t[--language|-l] <language>: select prefered language\n\n' 'Subcommands:\n' '\t{prefix}{aliases} list: show list of languages') name = 'gtts' aliases = (name, 'gspeak') category = 'Actions' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) flags = { 'language': { 'alias': 'l', 'bool': False }, 'slow': { 'alias': 's', 'bool': True }, 'file': { 'alias': 'f', 'bool': True }, 'no-voice': { 'alias': 'n', 'bool': True }, 'volume': { 'alias': 'v', 'bool': False } } async def on_load(self, from_reload): self.langs = gtts.lang.tts_langs() async def on_call(self, ctx, args, **flags): if args[1:].lower() == 'list': lines = [f'{k:<10}| {v}' for k, v in self.langs.items()] lines_per_chunk = 30 chunks = [ f'```{"code":<10}| name\n{"-" * 35}\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk) ] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed(title=f'Supported languages ({len(lines)})', colour=Colour.gold(), description=chunk) e.set_footer(text=f'Page {i + 1} / {len(chunks)}') p.add_page(embed=e) return await p.run(ctx) voice_flag = not flags.get('no-voice', isinstance(ctx.channel, DMChannel)) if voice_flag: if not ctx.author.voice: return '{warning} Please, join voice channel first' if not ctx.author.voice.channel.permissions_for(ctx.author).speak: return '{error} You\'re muted!' if not ctx.author.voice.channel.permissions_for( ctx.guild.me).connect: return '{error} I don\'t have permission to connect to the voice channel' if ctx.guild.voice_client is None: # not connected to voice channel try: vc = await ctx.author.voice.channel.connect() except Exception: return '{warning} Failed to connect to voice channel' elif ctx.author not in ctx.guild.voice_client.channel.members: # connected to a different voice channel await ctx.guild.voice_client.move_to(ctx.author.voice.channel) vc = ctx.guild.voice_client else: # already connected and is in the right place vc = ctx.guild.voice_client try: volume = float(flags.get('volume', 100)) / 100 except ValueError: return '{error} Invalid volume value' language_flag = flags.get('language') if language_flag: if language_flag not in self.langs: return '{warning} language not found. Use `list` subcommand to get list of voices' tts = gtts.gTTS(args[1:], lang=language_flag or 'en', slow=flags.get('slow', False), lang_check=False) with TemporaryFile() as tts_file: partial_tts = partial(tts.write_to_fp, tts_file) try: await self.bot.loop.run_in_executor(None, partial_tts) except Exception: return '{error} Problem with api response. Please, try again later' tts_file.seek(0) audio = PCMVolumeTransformer( FFmpegPCMAudio(tts_file, **ffmpeg_options), volume) if voice_flag: if vc.is_playing(): vc.stop() vc.play(audio) await ctx.react('✅') if flags.get('file', not voice_flag): try: tts_file.seek(0) await ctx.send( file=File(tts_file.read(), filename='tts.mp3')) except Exception: await ctx.send('Failed to send file')
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <subject> <choice 1> <choice 2> [choices 3-9]' short_doc = 'Begin poll' long_doc = ( 'Subcommands:\n' '\t{prefix}{aliases} cancel - cancels poll\n\n' 'Command flags:\n' '\t[--timeout|-t] <time>: set custom timeout, default is 60\n\n' 'Time formatting examples:\n' '\t1hour or 1h or 60m or 3600s or 3600 will result in 1 hour') name = 'poll' aliases = (name, ) category = 'Actions' bot_perms = (PermissionEmbedLinks(), PermissionAddReactions(), PermissionReadMessageHistory()) min_args = 1 flags = {'timeout': {'alias': 't', 'bool': False}} guild_only = True async def on_load(self, from_reload): self.polls = {} for key in await self.bot.redis.keys('poll:*'): value = await self.bot.redis.get(key) channel_id = int(key[5:]) author_id, poll_id, expires_at = [ int(i) for i in value.split(':')[:3] ] channel = self.bot.get_channel(channel_id) author = self.bot.get_user(author_id) try: poll = await channel.get_message(poll_id) except NotFound: poll = None if None in (channel, author, poll): await self.bot.redis.delete(key) await self.bot.redis.delete(f'poll_choices:{poll.channel.id}') return self.polls[poll.channel.id] = self.bot.loop.create_task( self.end_poll(expires_at, author, poll)) async def on_unload(self): for task in self.polls.values(): task.cancel() async def on_call(self, ctx, args, **flags): if args[1].lower() == 'cancel': task = self.polls.get(ctx.channel.id) if not task: return '{warning} No active poll in channel found' value = await self.bot.redis.get(f'poll:{ctx.channel.id}') author_id, poll_id = [int(i) for i in value.split(':')[:2]] if ctx.author.id != author_id: manage_messages_perm = PermissionManageMessages() if not manage_messages_perm.check(ctx.channel, ctx.author): raise manage_messages_perm task.cancel() await self.bot.redis.delete(f'poll_choices:{ctx.channel.id}') await self.bot.redis.delete(f'poll:{ctx.channel.id}') del self.polls[ctx.channel.id] try: poll = await ctx.channel.get_message(poll_id) except NotFound: pass else: await self.bot.edit_message(poll, content='[CANCELLED]') return await ctx.send(f'**{ctx.author}** cancelled poll.') elif len(args) < 4: return await self.on_not_enough_arguments(ctx) if ctx.channel.id in self.polls: return '{warning} Channel already has active poll' try: wait_until = timedelta_from_string(flags.get('timeout', '60')) except: return '{error} Failed to convert time' expires_at = wait_until.replace(tzinfo=timezone.utc).timestamp() + 1 if not 10 <= expires_at - time.time() <= 3600 * 24 * 7 + 60: return '{error} Timeout should be between **10** seconds and **1** week' if len(args) > 11: return '{error} Can\'t start poll with more than 9 items' subject = f'Poll: {args[1]}' if len(subject) > 256: return '{error} Subject name can\'t be longer than 250 characters' choices = args.args[2:] emojis = [EMOJI_NUMBER_BASE.format(i + 1) for i in range(len(choices))] e = Embed(colour=Colour.gold(), title=subject) e.description = '\n'.join(f'{emojis[i]}: {c}' for i, c in enumerate(choices)) e.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) e.set_footer(text=f'React with {emojis[0]} - {emojis[-1]} to vote') e.timestamp = wait_until try: poll = await ctx.send(embed=e, register=False) for e in emojis: await poll.add_reaction(e) except NotFound: return await self.bot.delete_message(ctx.message) await self.bot.redis.set( f'poll:{poll.channel.id}', f'{ctx.author.id}:{poll.id}:{int(expires_at)}:{subject}') await self.bot.redis.rpush(f'poll_choices:{poll.channel.id}', *choices) self.polls[poll.channel.id] = self.bot.loop.create_task( self.end_poll(expires_at, ctx.author, poll)) async def end_poll(self, expires_at, author, poll): await asyncio.sleep(expires_at - time.time()) value = await self.bot.redis.get(f'poll:{poll.channel.id}') author_id, poll_id, expires_at, subject = value.split(':', 3) choices = await self.bot.redis.lrange( f'poll_choices:{poll.channel.id}', 0, 8) await self.bot.redis.delete(f'poll_choices:{poll.channel.id}') await self.bot.redis.delete(f'poll:{poll.channel.id}') del self.polls[poll.channel.id] try: poll = await poll.channel.get_message(poll.id) await poll.edit(content='[FINISHED]') except NotFound: return scores = [0] * len(choices) emojis = [EMOJI_NUMBER_BASE.format(i + 1) for i in range(len(choices))] for r in poll.reactions: try: index = emojis.index(str(r.emoji)) except ValueError: continue else: scores[index] = r.count - 1 max_score = max(scores) author = self.bot.get_user(int(author_id)) e = Embed(colour=Colour.gold(), title=subject) e.set_author(name=author, icon_url=author.avatar_url) e.description = 'Results\n' for s, c in sorted(zip(scores, choices), key=lambda x: (-x[0], x[1])): e.description += f'{c}: {s}' e.description += ' (WINNER)\n' if s == max_score else '\n' await self.bot.send_message(poll.channel, embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <query>' short_doc = 'Web search using duckduckgo' name = 'ddg' aliases = (name, 'duckduckgo') category = 'Services' bot_perms = (PermissionEmbedLinks(), ) min_args = 1 async def on_call(self, ctx, args, **flags): params = {'q': args[1:], 'o': 'json', 'no_html': 1} async with self.bot.sess.get(API_URL, params=params) as r: if r.status != 200: return '{error} request failed: ' + str(r.status) try: r_json = await r.json(content_type='application/x-javascript') except aiohttp.ContentTypeError: # (text/html; charset=utf-8) with query "osu!", ??? return '{error} Failed to read response' def make_embed(page): e = Embed(colour=Colour.gold(), title='DuckDuckGo') e.set_footer(text=f'Page {page}', icon_url='https://duckduckgo.com/favicon.png') return e abstract_text = r_json['AbstractText'] related = r_json['RelatedTopics'] p = Paginator(self.bot) if abstract_text: e = make_embed(1) e.description = abstract_text e.set_image(url=r_json['Image']) p.add_page(embed=e) elif not related: return '{warning} Nothing found' topics = [] for topic in related: if topic.get('Name'): for subtopic in topic['Topics']: subtopic['Text'] = f'[{topic["Name"]}] {subtopic["Text"]}' topics.append(subtopic) else: topics.append(topic) topics_per_page = 5 chunks = [ topics[i:i + topics_per_page] for i in range(0, len(topics), topics_per_page) ] for i, group in enumerate(chunks): e = make_embed(len(p._pages) + 1) for topic in group: e.add_field(name=topic['Text'][:255], value=topic['FirstURL']) p.add_page(embed=e) await p.run(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [channel]' short_doc = 'Generate text using markov chain' name = 'markov' aliases = (name, 'markovchain') category = 'Actions' bot_perms = (PermissionEmbedLinks(), ) guild_only = True async def on_call(self, ctx, args, **flags): if len(args) == 1: channel = ctx.channel else: channel = await find_channel( args[1:], ctx.guild, global_id_search=True, include_voice=False, include_category=False ) if channel is None: return '{warning} Channel not found' author = channel.guild.get_member(ctx.author.id) if not author or not channel.permissions_for(author).read_messages: return '{error} You don\'t have permission to read messages in that channel' if channel.is_nsfw() > ctx.channel.is_nsfw(): return '{warning} Trying to access nsfw channel from sfw channel' m = await ctx.send('Generating...') try: messages = await channel.history( limit=1000, reverse=True, before=ctx.message.edited_at or ctx.message.created_at ).flatten() except Exception: return await self.bot.edit_message(m, 'Failed to read message history') words = [i for s in [m.content.split(' ') for m in messages if m.content] for i in s] num_words = min((random.randint(5, 100), len(words))) if num_words < 2: return await self.bot.edit_message( m, 'Not enough words to generate text') pairs = [(words[i].lower(), words[i + 1]) for i in range(len(words) - 1)] word_dict = {} for word_1, word_2 in pairs: if word_1 in word_dict: word_dict[word_1].append(word_2) else: word_dict[word_1] = [word_2] chain = [random.choice(words)] for i in range(num_words): word = chain[-1] if word in word_dict: next_word = random.choice(word_dict[word]) else: next_word = random.choice(random.choice(tuple(word_dict.values()))) chain.append(next_word) most_frequent_word = max(word_dict, key=lambda x: len(word_dict[x] if x else [])) e = Embed(colour=Colour.gold(), title='Markov Chain') e.add_field(name='Channel', value=channel.mention) e.add_field(name='Words analyzed', value=len(words)) e.add_field( name='Most frequent word', value=f'**{most_frequent_word[:256]}**: used **{len(word_dict[most_frequent_word])}** times ({round(len(word_dict[most_frequent_word]) / len(words), 4)}%)' ) e.description = trim_text(' '.join(chain), max_len=2048) e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) await self.bot.delete_message(m) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <url>' short_doc = 'Screenshot webpage' name = 'screenshot' aliases = (name, 'ss') category = 'Actions' min_args = 1 max_args = 1 bot_perms = (PermissionEmbedLinks(), PermissionAttachFiles()) async def on_load(self, from_reload): self.lock = asyncio.Lock() async def on_call(self, ctx, args, **flags): m = await ctx.send('Taking screenshot...') url = args[1] if url.startswith('<') and url.endswith('>'): url = url[1:-1] if not url.startswith(('http://', 'https://')): url = 'http://' + url proxy = random.choice(list(self.bot.proxies.keys())) try: async with self.bot.sess.head(url, timeout=15, proxy=proxy) as r: if (r.content_length or 0) > 100000000: return await self.bot.edit_message( m, 'Rejected to navigate') except Exception: return await self.bot.edit_message(m, 'Connection timeout') await self.lock.acquire() try: service = service = services.Chromedriver(log_file=devnull) browser = browsers.Chrome( chromeOptions={ 'args': ['--headless', '--disable-gpu', f'proxy-server={proxy}'] } ) async with get_session(service, browser) as session: await session.set_window_size(1920, 1080) await session.get(url) opened_url = await session.get_url() await asyncio.sleep(2) screenshot = await session.get_screenshot() except UnknownArsenicError: await self.bot.edit_message( m, 'Unknown exception happened') return except Exception: return await self.bot.edit_message( m, 'Could not open page, please check url and try again') finally: try: self.lock.release() except Exception: pass try: title = opened_url.split('/')[2] except IndexError: title = "Screenshot" e = Embed(title=title[:256], colour=Colour.gold(), url=url) e.set_image(url='attachment://screenshot.png') f = File(screenshot, filename='screenshot.png') e.set_footer( text=f'Took {round(time.time() - (m.created_at or m.edited_at).timestamp(), 1)} seconds') await self.bot.delete_message(m) await ctx.send(embed=e, file=f)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <text>' short_doc = 'Make me say something' long_doc = ( 'Command flags:\n' '\t[--file|-f]: respond with audio file\n' '\t[--no-voice|-n]: don\'t use voice channel\n' '\t[--volume|-v] <value>: set volume in % from 0 to 200\n' '\t[--speed|-s] <value>: set speed value from 0 (default is 115)\n' '\t[--language|-l] <language>: select prefered language\n' '\t[--woman|-w]: use female voice if added\n' '\t[--quiet|-q]: whisper text if added\n\n' 'Subcommands:\n' '\t{prefix}{aliases} list: show list of voices') name = 'tts' aliases = (name, 'speak') category = 'Actions' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) flags = { 'language': { 'alias': 'l', 'bool': False }, 'file': { 'alias': 'f', 'bool': True }, 'no-voice': { 'alias': 'n', 'bool': True }, 'volume': { 'alias': 'v', 'bool': False }, 'speed': { 'alias': 's', 'bool': False }, 'woman': { 'alias': 'w', 'bool': True }, 'quiet': { 'alias': 'q', 'bool': True } } async def on_call(self, ctx, args, **flags): if args[1:].lower() == 'list': lines = [f'{k:<15}| {v}' for k, v in LANG_LIST.items()] lines_per_chunk = 30 chunks = [ f'```{"code":<15}| name\n{"-" * 45}\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk) ] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed(title=f'Supported languages ({len(lines)})', colour=Colour.gold(), description=chunk) e.set_footer(text=f'Page {i + 1} / {len(chunks)}') p.add_page(embed=e) return await p.run(ctx) text = args[1:] voice_flag = not flags.get('no-voice', isinstance(ctx.channel, DMChannel)) if voice_flag: if not ctx.author.voice: return '{warning} Please, join voice channel first' if not ctx.author.voice.channel.permissions_for(ctx.author).speak: return '{error} You\'re muted!' if not ctx.author.voice.channel.permissions_for( ctx.guild.me).connect: return '{error} I don\'t have permission to connect to the voice channel' if ctx.guild.voice_client is None: # not connected to voice channel try: vc = await ctx.author.voice.channel.connect() except Exception: return '{warning} Failed to connect to voice channel' elif ctx.author not in ctx.guild.voice_client.channel.members: # connected to a different voice channel await ctx.guild.voice_client.move_to(ctx.author.voice.channel) vc = ctx.guild.voice_client else: # already connected and is in the right place vc = ctx.guild.voice_client try: volume = float(flags.get('volume', 100)) / 100 except ValueError: return '{error} Invalid volume value' program = ['espeak-ng', text, '--stdout'] speed_flag = flags.get('speed') if speed_flag is not None: try: speed = int(speed_flag) + 80 except ValueError: return '{error} Invalid speed value' program.extend(('-s', str(speed))) language_flag = flags.get('language', 'en-us') if language_flag not in LANG_LIST: return '{warning} Language not found. Use `list` subcommand to get list of voices' language = language_flag woman_flag = flags.get('woman', False) quiet_flag = flags.get('quiet', False) if woman_flag: if quiet_flag: return '{error} Can\'t apply both woman and quiet flags' language += f'+f{random.randrange(1, 5)}' elif quiet_flag: language += '+whisper' else: language += f'+m{random.randrange(1, 8)}' program.extend(('-v', language)) process, pid = await create_subprocess_exec(*program) stdout, stderr = await execute_process(process) with TemporaryFile() as tmp: tmp.write(stdout) tmp.seek(0) audio = PCMVolumeTransformer(FFmpegPCMAudio(tmp, **ffmpeg_options), volume) if flags.get('file', not voice_flag): try: await ctx.send(file=File(stdout, filename='tts.wav')) except Exception: await ctx.send('Failed to send file') if voice_flag: if vc.is_playing(): vc.stop() vc.play(audio) await ctx.react('✅')
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <emoji>' short_doc = 'Allows to get emoji image' name = 'emoji' aliases = (name, 'e') category = 'Discord' min_args = 1 max_args = 1 bot_perms = (PermissionEmbedLinks(), PermissionAttachFiles()) async def on_call(self, ctx, args, **flags): e = Embed(colour=Colour.gold()) f = None text = args[1:] emoji_id = None emoji_name = '' id_match = ID_REGEX.fullmatch(text) if id_match: emoji_id = int(id_match.group(0)) else: emoji_match = EMOJI_REGEX.fullmatch(text) if emoji_match: groups = emoji_match.groupdict() emoji_id = int(groups['id']) emoji_name = groups['name'] if emoji_id is None: def to_string(c): digit = f'{ord(c):x}' return f'{digit:>04}' code = '-'.join(map(to_string, text)) async with self.bot.sess.get(TWEMOJI_ENDPOINT.format(code)) as r: if r.status != 200: return '{warning} Could not get emoji from input text' filename = 'emoji.png' f = File(await r.read(), filename=filename) e.title = f'TWEmoji' e.set_image(url=f'attachment://{filename}') else: e.set_footer(text=emoji_id) emoji = self.bot.get_emoji(emoji_id) if emoji is None: async with self.bot.sess.get(EMOJI_ENDPOINT.format(emoji_id)) as r: if r.status != 200: return '{error} Emoji with given id not found' filename = f'emoji.{r.content_type[6:]}' f = File(await r.read(), filename=filename) e.title = f'Emoji {emoji_name or ""}' e.set_image(url=f'attachment://{filename}') else: e.title = f'Emoji {emoji.name}' e.set_image(url=emoji.url) await ctx.send(embed=e, file=f)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [guild]' short_doc = 'Get information about matched guild' name = 'guild' aliases = (name, 'guildinfo', 'server', 'serverinfo') category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): if len(args) == 1: guild = ctx.guild else: guild = await find_guild(args[1:]) if guild is None: return '{warning} Guild not found' invite = '' if guild == ctx.guild: top_role = guild.roles[-1].mention else: try: invite = await guild.channels[0].create_invite( reason=f'requested by {ctx.author}-{ctx.author.id}' + (f' in guild {ctx.guild}-{ctx.guild.id}' if ctx.guild else ''), max_age=3600 * 12 # 12 hours ) except HTTPException: pass else: invite = invite.url top_role = f'@{guild.roles[-1]}' bot_count = sum(1 for m in guild.members if m.bot) voice_channels_num, text_channels_num = 0, 0 for channel in guild.channels: if isinstance(channel, VoiceChannel): voice_channels_num += 1 elif isinstance(channel, TextChannel): text_channels_num += 1 max_emoji = 30 static_emojis = [] animated_emojis = [] for em in guild.emojis or []: (static_emojis, animated_emojis)[em.animated].append(em) e = Embed(title=guild.name, url=invite, colour=Colour.gold()) if guild.icon_url: e.set_thumbnail(url=guild.icon_url) else: # TODO: custom thumbnail for this case pass e.add_field( name='created', inline=False, value= f'`{guild.created_at.replace(microsecond=0)}` ({(datetime.now() - guild.created_at).days} days ago)' ) e.add_field(name='owner', value=guild.owner.mention) e.add_field(name='region', value=guild.region) e.add_field( name='members', value=f'{guild.member_count - bot_count} + {bot_count} robots') e.add_field( name='channels', value=f'{text_channels_num} text, {voice_channels_num} voice') e.add_field(name='total roles', value=len(guild.roles)) e.add_field(name='top role', value=top_role) if guild.icon_url: formats = ['png', 'webp', 'jpg'] e.add_field(name='avatar', value=' | '.join( f'[{f}]({guild.icon_url_as(format=f)})' for f in formats)) if guild == ctx.guild or PermissionExternalEmojis().check( ctx.channel, self.bot.user): e.add_field( name='static emotes', inline=False, value= (f'**{min(max_emoji, len(static_emojis))} / {len(static_emojis)}** shown: ' + ' '.join( str(e) for e in sorted(random.sample( static_emojis, min(max_emoji, len(static_emojis))), key=lambda e: e.name))) if static_emojis else 'Guild does not have them :/') e.add_field( name='animated emotes', inline=False, value= (f'**{min(max_emoji, len(animated_emojis))} / {len(animated_emojis)}** shown: ' + ' '.join( str(e) for e in sorted(random.sample( animated_emojis, min(max_emoji, len( animated_emojis))), key=lambda e: e.name))) if animated_emojis else 'Guild does not have them :/') else: e.add_field(name='emojis', value='I have no permission to show external emojis', inline=False) e.set_footer(text=guild.id) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [alias]' short_doc = 'Get information about module or category' long_doc = ('Subcommands:\n' '\t{prefix}{aliases} all: show help for all commands\n' '\t{prefix}{aliases} random: show random command help') name = 'help' aliases = (name, 'commands') category = 'Bot' bot_perms = (PermissionEmbedLinks(), PermissionAddReactions(), PermissionReadMessageHistory()) flags = { 'show-hidden': { 'alias': 'h', 'bool': True }, 'hide-normal': { 'alias': 'n', 'bool': True } } async def on_call(self, ctx, args, **flags): hidden_flag = flags.get('show-hidden', False) hide_normal_flag = flags.get('hide-normal', False) if len(args) == 1: module_list = [] for module in self.bot.mm.get_all_modules(hidden=hidden_flag): if not module.hidden and hide_normal_flag: continue module_list.append(module) if not module_list: return '{error} No commands found' modules_by_category = {} for module in module_list: category = module.category or 'Uncategorized' if category not in modules_by_category: modules_by_category[category] = [ module, ] else: modules_by_category[category].append(module) chunks_by_category = {} for category, modules in sorted(modules_by_category.items(), key=lambda x: x[0]): lines = sorted([f'{m.name:<20}{m.short_doc}' for m in modules]) lines_per_chunk = 30 chunks = [ lines[i:i + lines_per_chunk] for i in range(0, len(lines), lines_per_chunk) ] chunks_by_category[category] = chunks local_prefix = await get_local_prefix(ctx) total_pages = sum( len(chunks) for chunks in chunks_by_category.values()) + 1 def make_page(title, chunk, page): e = Embed(colour=Colour.gold(), title=title, description='```\n' + '\n'.join(chunk) + '```') e.set_footer(text=f'Current prefix: {local_prefix}') return { 'embed': e, 'content': f'Page **{page}/{total_pages}** ({len(module_list)}) commands' } p = Paginator(self.bot) p.add_page(**make_page('Categories', [], 1)) page = 2 p._pages[0]['embed'].description = f'```\n{"Category":<19} Pages\n' for category, chunks in chunks_by_category.items(): p._pages[0][ 'embed'].description += f'{category:.<19} {page:<2}- {page + len(chunks) - 1}\n' for chunk in chunks: p.add_page(**make_page(category, chunk, page)) page += 1 p._pages[0]['embed'].description += '```' return await p.run(ctx) if len(args) > 2: return '{warning} help for subcommands is not supported yet' if args[1].lower() == 'all': category = 'All commands' modules = self.bot.mm.get_all_modules(hidden=hidden_flag) elif args[1].lower() == 'random': return await random.choice( self.bot.mm.get_all_modules(hidden=hidden_flag) ).on_doc_request(ctx) else: category = args[1] modules = self.bot.mm.get_modules_by_category(category) module_list = [] for module in modules: if module.hidden: if not (flags.get('show-hidden', False)): continue if not module.hidden and flags.get('hide-normal', False): continue module_list.append(module) if module_list: lines = sorted([f'{m.name:<20}{m.short_doc}' for m in module_list]) lines_per_chunk = 30 chunks = [ lines[i:i + lines_per_chunk] for i in range(0, len(lines), lines_per_chunk) ] p = Paginator(self.bot) for i, chunk in enumerate(chunks): p.add_page( embed=Embed(colour=Colour.gold(), title=category, description=f'```' + '\n'.join(chunk) + '```'), content= f'Page **{i + 1}/{len(chunks)}** ({len(modules)}) commands' ) await p.run(ctx) else: module = self.bot.mm.get_module(args[1]) if not module: return '{warning} Unknown command or category' return await module.on_doc_request(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [command...]' short_doc = 'Show command usage stats' name = 'usagestats' aliases = (name, 'usage') category = 'Bot' bot_perms = (PermissionEmbedLinks(), ) flags = { 'show-disabled': { 'alias': 'd', 'bool': True }, 'show-hidden': { 'alias': 'h', 'bool': True }, 'hide-normal': { 'alias': 'n', 'bool': True } } async def on_call(self, ctx, args, **flags): commands = [] if len(args) > 1: keys = [ m.name for m in [self.bot.mm.get_module(n) for n in set(args.args[1:])] if m is not None ] if keys: usage = await self.bot.redis.mget( *[f'command_usage:{k}' for k in keys]) commands = tuple(zip(keys, [int(u) for u in usage])) else: keys = await self.bot.redis.keys('command_usage:*') usage = await self.bot.redis.mget(*keys) for k, u in zip(keys, usage): name = k[14:] module = self.bot.mm.get_module(name) if module: if module.disabled: if not (flags.get('show-disabled', False)): continue if module.hidden: if not (flags.get('show-hidden', False)): continue if not (module.disabled or module.hidden) and flags.get( 'hide-normal', False): continue commands.append((name, int(u))) if not commands: return '{error} No commands found' total_usage = sum(u for c, u in commands) lines = [ f'{c:<20}{u}' for c, u in sorted(commands, key=lambda x: int(x[1]), reverse=True) ] lines_per_chunk = 30 chunks = [ lines[i:i + lines_per_chunk] for i in range(0, len(lines), lines_per_chunk) ] def make_embed(chunk): e = Embed(colour=Colour.gold(), title='Command usage:', description='```\n' + "\n".join(chunk) + '```') e.set_footer(text=f'Commands used in total: {total_usage}', icon_url=self.bot.user.avatar_url) return e p = Paginator(self.bot) for i, chunk in enumerate(chunks): p.add_page(embed=make_embed(chunk), content=f'Page **{i + 1}/{len(chunks)}**') await p.run(ctx)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <subcommand>' short_doc = 'Anonymous chat' long_doc = ( 'Subcommands:\n' '\t[say|send] <text>: send message to current room\n' '\t[new|create]: enter queue for the new room\n' '\t[leave|close] <room id>: exit room\n' '\t[connect|set] <room id>: switch to different room\n' '\tlist: show list of chats you\'re in\n\n' 'To use chat, create room first usinng {prefix}{aliases} new\n' 'After 2nd user connects, you can communicate using {prefix}{aliases} say <text>\n\n' 'If you want to move conversation to different channel, use {prefix}{aliases} connect <room id> in that channel\n\n' 'To quit room, use {prefix}{aliases} close <room id>' ) name = 'chat' aliases = (name,) category = 'Actions' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) async def on_load(self, from_reload): if not await self.bot.redis.exists('last_chat_room_id'): await self.bot.redis.set('last_chat_room_id', '0') async def on_call(self, ctx, args, **flags): subcommand = args[1].lower() if subcommand in ('say', 'send'): room_id = await self.bot.redis.get(f'room_id_by_channel_and_user:{ctx.channel.id}:{ctx.author.id}') if room_id is None: return '{warning} Your message wasn\'t delivered. Please, connect to chat room first' u1_target_channel, u2_target_channel = await self.bot.redis.smembers(f'chat_room:{room_id}') user2_id = (u2_target_channel if u1_target_channel.endswith(str(ctx.author.id)) else u1_target_channel).split(':')[1] target_channel_id = (u1_target_channel if u1_target_channel.endswith(user2_id) else u2_target_channel).split(':')[0] user2 = self.bot.get_user(int(user2_id)) target = self.bot.get_channel(int(target_channel_id)) if target is None: target = user2 if target is None: return '{error} 2nd user not found! Please, try again' e = Embed(title='KiwiBot anonymous chat', description=args[2:], colour=Colour.gold()) e.set_author(name=user2, icon_url=user2.avatar_url) e.set_footer(text=f'Room id: {room_id}') try: await target.send(embed=e) except Exception: return await ctx.send('Failed to deliver message') else: return await ctx.react('✅') elif subcommand in ('connect', 'set'): if len(args) < 3: return await self.on_doc_request(ctx) try: room_id = int(args[2]) except ValueError: return '{error} Chat id is not digit' room = await self.bot.redis.smembers(f'chat_room:{room_id}') if room: u1_target_channel, u2_target_channel = room if u1_target_channel.endswith(str(ctx.author.id)) or u2_target_channel.endswith(str(ctx.author.id)): if u1_target_channel.endswith(str(ctx.author.id)): old_member = u1_target_channel user2_id = u2_target_channel.split(':')[1] else: old_member = u2_target_channel user2_id = u1_target_channel.split(':')[1] new_user1_channel = f'{ctx.channel.id}:{ctx.author.id}' await self.bot.redis.delete(f'room_id_by_channel_and_user:{old_member}') await self.bot.redis.set(f'room_id_by_channel_and_user:{new_user1_channel}', room_id) await self.bot.redis.srem(f'chat_room:{room_id}', old_member) await self.bot.redis.sadd(f'chat_room:{room_id}', new_user1_channel) return await ctx.send(f'Connected to room #{room_id}') return '{warning} You\'re not a room member or room does not exist' elif subcommand in ('new', 'create'): waiting_user = await self.bot.redis.get('waiting_chat_user') if waiting_user is None: await self.bot.redis.set('waiting_chat_user', f'{ctx.channel.id}:{ctx.author.id}') return await ctx.send('Please, wait for 2nd user to connect. This might take a while') channel_id, user_id = waiting_user.split(':') if int(user_id) == ctx.author.id: return '{warning} You\'re already queued. Please, wait for the 2nd user to connect' await self.bot.redis.delete('waiting_chat_user') user2 = self.bot.get_user(int(user_id)) target = self.bot.get_channel(int(channel_id)) if target is None: target = user2 if target is None: return '{error} 2nd user not found! Please, try again' new_room_id = int(await self.bot.redis.incr('last_chat_room_id')) user1_channel = f'{ctx.channel.id}:{ctx.author.id}' user2_channel = f'{channel_id}:{user_id}' await self.bot.redis.sadd(f'chat_room:{new_room_id}',user1_channel, user2_channel) await self.bot.redis.set(f'room_id_by_channel_and_user:{user1_channel}', new_room_id) await self.bot.redis.set(f'room_id_by_channel_and_user:{user2_channel}', new_room_id) e = Embed(title=f'Created chat room #{new_room_id}', colour=Colour.gold()) e.set_footer(text=user2, icon_url=user2.avatar_url) e.description = 'Now you can send messages with `chat say`' failed_to_notify = False try: await target.send(embed=e) except Exception: if not target == user2: try: await self.bot.get_user(int(user_id)).send(embed=e) except Exception: failed_to_notify = True else: failed_to_notify = True if failed_to_notify: e.description += 'Warning: failed to notify 2nd user about chat creation' e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) return await ctx.send(embed=e) elif subcommand in ('leave', 'close'): if len(args) < 3: return await self.on_doc_request(ctx) try: room_id = int(args[2]) except ValueError: return '{error} Chat id is not digit' room = await self.bot.redis.smembers(f'chat_room:{room_id}') if room: u1_target_channel, u2_target_channel = room if u1_target_channel.endswith(str(ctx.author.id)) or u2_target_channel.endswith(str(ctx.author.id)): confirm_msg = await ctx.send( ( f'Are you sure you want to close chat **#{room_id}** ?\n' f'**This action cannot be undone**\n' f'If you want to move chat to different channel, use `connect` subcommand instead\n' f'React with ✅ to continue' ) ) if not await request_reaction_confirmation(confirm_msg, ctx.author): return await self.bot.edit_message(confirm_msg, f'Cancelled closing of chat **#{room_id}**') await self.bot.redis.delete( f'chat_room:{room_id}', f'room_id_by_channel_and_user:{u1_target_channel}', f'room_id_by_channel_and_user:{u2_target_channel}' ) return await self.bot.edit_message(confirm_msg, f'**{ctx.author}** closed chat **#{room_id}**') return '{warning} You\'re not a room member or room does not exist' elif subcommand == 'list': keys = await self.bot.redis.keys('chat_room:*') lines = [] for k in keys: u1_target_channel, u2_target_channel = await self.bot.redis.smembers(k) if u1_target_channel.endswith(str(ctx.author.id)) or u2_target_channel.endswith(str(ctx.author.id)): lines.append(f'#{k[10:]}' ) if not lines: return 'No active chats found' lines_per_chunk = 30 chunks = ['```\n' + '\n'.join(lines[i:i + lines_per_chunk]) + '```' for i in range(0, len(lines), lines_per_chunk)] p = Paginator(self.bot) for i, chunk in enumerate(chunks): e = Embed( title='Active chats', colour=Colour.gold(), description=chunk ) e.set_footer(text=f'Page {i + 1} / {len(chunks)}') p.add_page(embed=e) return await p.run(ctx) else: return '{warning} Unknown subcommand'
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <subject>' short_doc = 'Begin vote procedure' long_doc = ( 'Subcommands:\n' '\t{prefix}{aliases} cancel - cancels vote\n\n' 'Command flags:\n' '\t[--timeout|-t] <time>: set custom timeout, default is 60\n\n' 'Time formatting examples:\n' '\t1hour or 1h or 60m or 3600s or 3600 will result in 1 hour' ) name = 'vote' aliases = (name, ) category = 'Actions' bot_perms = ( PermissionEmbedLinks(), PermissionAddReactions(), PermissionReadMessageHistory() ) min_args = 1 flags = { 'timeout': { 'alias': 't', 'bool': False } } guild_only = True async def on_load(self, from_reload): self.votes = {} for key in await self.bot.redis.keys('vote:*'): value = await self.bot.redis.get(key) channel_id = int(key[5:]) author_id, vote_id, expires_at = [int(i) for i in value.split(':')[:3]] channel = self.bot.get_channel(channel_id) author = self.bot.get_user(author_id) try: vote = await channel.get_message(vote_id) except NotFound: vote = None if None in (channel, author, vote): await self.bot.redis.delete(key) return self.votes[vote.channel.id] = self.bot.loop.create_task( self.end_vote(expires_at, author, vote)) async def on_unload(self): for task in self.votes.values(): task.cancel() async def on_call(self, ctx, args, **flags): if args[1].lower() == 'cancel': task = self.votes.get(ctx.channel.id) if not task: return '{warning} No active vote in channel found' value = await self.bot.redis.get(f'vote:{ctx.channel.id}') author_id, vote_id = [int(i) for i in value.split(':')[:2]] if ctx.author.id != author_id: manage_messages_perm = PermissionManageMessages() if not manage_messages_perm.check(ctx.channel, ctx.author): raise manage_messages_perm task.cancel() await self.bot.redis.delete(f'vote:{ctx.channel.id}') del self.votes[ctx.channel.id] try: vote = await ctx.channel.get_message(vote_id) except NotFound: pass else: await self.bot.edit_message(vote, content='[CANCELLED]') return await ctx.send(f'**{ctx.author}** cancelled vote.') if ctx.channel.id in self.votes: return '{warning} Channel already have active vote' try: wait_until = timedelta_from_string(flags.get('timeout', '60')) except: return '{error} Failed to convert time' expires_at = wait_until.replace(tzinfo=timezone.utc).timestamp() + 1 if not 10 <= expires_at - time.time() <= 3600 * 24 * 7 + 60: return '{error} Timeout should be between **10** seconds and **1** week' subject = args[1:] e = Embed(colour=Colour.gold(), title='Vote', description=subject) e.set_author(name=ctx.author.name, icon_url=ctx.author.avatar_url) e.set_footer( text=f'React with {REACTION_FOR} or {REACTION_AGAINST} to vote') e.timestamp = wait_until try: vote = await ctx.send(embed=e, register=False) for e in (REACTION_FOR, REACTION_AGAINST): await vote.add_reaction(e) except NotFound: return await self.bot.delete_message(ctx.message) await self.bot.redis.set( f'vote:{vote.channel.id}', f'{ctx.author.id}:{vote.id}:{int(expires_at)}:{subject}' ) self.votes[vote.channel.id] = self.bot.loop.create_task( self.end_vote(expires_at, ctx.author, vote)) async def end_vote(self, expires_at, author, vote): await asyncio.sleep(expires_at - time.time()) value = await self.bot.redis.get(f'vote:{vote.channel.id}') author_id, vote_id, expires_at, subject = value.split(':', 3) await self.bot.redis.delete(f'vote:{vote.channel.id}') del self.votes[vote.channel.id] try: vote = await vote.channel.get_message(vote.id) await vote.edit(content='[FINISHED]') except NotFound: return voted_for, voted_against = 0, 0 for r in vote.reactions: if str(r.emoji) == REACTION_AGAINST: voted_against = r.count - 1 elif str(r.emoji) == REACTION_FOR: voted_for = r.count - 1 total_votes = voted_for + voted_against percent_for = round( voted_for / total_votes * 100 if voted_for else 0.0, 2) percent_against = round( voted_against / total_votes * 100 if voted_against else 0.0, 2) author = self.bot.get_user(int(author_id)) e = Embed(colour=Colour.gold(), title='Vote results', description=subject) e.set_author(name=author.name, icon_url=author.avatar_url) e.add_field( name=f'{total_votes} votes', value=f'For: **{voted_for}** (**{percent_for}**%)\nAgainst: **{voted_against}** (**{percent_against}**%)' ) await self.bot.send_message(vote.channel, embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <text>' short_doc = 'Translate text' long_doc = ('Subcommands:\n' '\tlist: get list of languages\n\n' 'Command flags:\n' '\t[--out|-o] <language>: output language\n' '\t[--len|-l] <number>: set custom chain len') name = 'badtranslator' aliases = (name, 'bt') category = 'Actions' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) flags = { 'out': { 'alias': 'o', 'bool': False }, 'len': { 'alias': 'l', 'bool': False } } async def on_load(self, from_reload): self.translator = gt.Translator(proxies=list(self.bot.proxies.keys()) + [None]) async def on_call(self, ctx, args, **flags): if args[1:].lower() == 'list': return '\n'.join(f'`{k}`: {v}' for k, v in gt.LANGUAGES.items()) out_lang = flags.get('out', 'en').lower() if out_lang not in gt.LANGUAGES: return '{warning} Invalid out language. Try using list subcommand' chain_len = flags.get('len', DEFAULT_CHAIN_LEN) if isinstance(chain_len, str) and not chain_len.isdigit(): return '{error} Wrong value given for chain len' chain_len = int(chain_len) if chain_len > MAX_CHAIN_LEN: return '{warning} Max chain len is %d, you asked for %d' % ( MAX_CHAIN_LEN, chain_len) if chain_len < 2: return '{error} Chain len should not be shorter than 2. Use goodtranslator instead' async with ctx.channel.typing(): langs = random.sample(gt.LANGUAGES.keys(), chain_len + 1) if 'en' in langs: langs.remove('en') langs = langs[:chain_len] langs.append(out_lang) text = args[1:] try: for l in langs: translation = await self.translator.translate(text, dest=l) text = translation.text except Exception: return '{error} Failed to translate. Please, try again later. If there are emojis in text, try removing them.' e = Embed(colour=Colour.gold(), title='BadTranslator') e.description = text[:2048] e.add_field(name='Chain', value=' -> '.join( gt.LANGUAGES.get(l, l) for l in langs[:-1])) e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) await ctx.send(embed=e) async def on_unload(self): await self.translator.close()
class Module(ModuleBase): usage_doc = '{prefix}{aliases} <text>' short_doc = 'Translate text' long_doc = ( 'Subcommands:\n' '\tlist: get list of languages\n\n' 'Command flags:\n' '\t[--out|-o] <language>: output language\n' '\t[--len|-l] <number>: set custom chain len' ) name = 'badtranslator2' aliases = (name, 'bt2') category = 'Actions' min_args = 1 bot_perms = (PermissionEmbedLinks(), ) flags = { 'out': { 'alias': 'o', 'bool': False }, 'len': { 'alias': 'l', 'bool': False } } async def on_load(self, from_reload): self.api_key = self.bot.config.get('yandex_api_key') if self.api_key: params = { 'key': self.api_key, 'ui': 'en' } async with self.bot.sess.get(API_URL + 'getLangs', params=params) as r: if r.status == 200: self.langs = (await r.json())['langs'] return raise Exception('No yandex api key in config or key is invalid') async def on_call(self, ctx, args, **flags): if args[1:].lower() == 'list': return '\n'.join(f'`{k}`: {v}' for k, v in self.langs.items()) out_lang = flags.get('out', 'en').lower() if out_lang not in self.langs: return '{warning} Invalid out language. Try using list subcommand' chain_len = flags.get('len', DEFAULT_CHAIN_LEN) if isinstance(chain_len, str) and not chain_len.isdigit(): return '{error} Wrong value given for chain len' chain_len = int(chain_len) if chain_len > MAX_CHAIN_LEN: return '{warning} Max chain len is %d, you asked for %d' % (MAX_CHAIN_LEN, chain_len) if chain_len < 2: return '{error} Chain len should not be shorter than 2. Use goodtranslator2 instead' async with ctx.channel.typing(): langs = random.sample(self.langs.keys(), chain_len + 1) if 'en' in langs: langs.remove('en') langs = langs[:chain_len] langs.append(out_lang) text = args[1:] try: for i, l in enumerate(langs): params = { 'key': self.api_key, 'text': text, 'lang': f'{langs[i - 1] + "-" if i else ""}{l}' } async with self.bot.sess.post(API_URL + 'translate', params=params) as r: if r.status != 200: return '{error} Failed to translate. Please, try again later' text = (await r.json())['text'][0] except Exception: return '{error} Failed to translate. Please, try again later' e = Embed(colour=Colour.gold(), title='BadTranslator 2') e.description = text[:2048] e.add_field( name='Chain', value=' -> '.join(self.langs.get(l, l) for l in langs[:-1]) ) e.set_footer(text=ctx.author, icon_url=ctx.author.avatar_url) await ctx.send(embed=e)
class Module(ModuleBase): usage_doc = '{prefix}{aliases} [channel]' short_doc = 'Get information about given channel' name = 'channel' aliases = (name, 'channelinfo') category = 'Discord' bot_perms = (PermissionEmbedLinks(), ) async def on_call(self, ctx, args, **flags): if len(args) == 1: channel = ctx.channel else: channel = await find_channel(args[1:], ctx.guild, global_id_search=True) if channel is None: return '{warning} Channel not found' e = Embed(colour=Colour.gold(), title=getattr(channel, 'name', 'DM Channel')) additional_fields = [] if isinstance(channel, PrivateChannel): c_type = 'DM Channel' member_count = 2 additional_fields.append( { 'name': 'pins', 'value': len(await channel.pins()) }, ) elif isinstance(channel, TextChannel): c_type = 'Text Channel' member_count = len(channel.members) if channel.permissions_for(channel.guild.me).read_messages: pins = len(await channel.pins()) else: pins = 'No access' additional_fields = [{ 'name': 'Pins', 'value': pins }, { 'name': 'Category', 'value': channel.category.name }, { 'name': 'NSFW', 'value': channel.is_nsfw() }, { 'name': 'Topic', 'value': channel.topic or 'Empty', 'inline': False }] elif isinstance(channel, VoiceChannel): c_type = 'Voice Channel' member_count = len(channel.members) additional_fields = [{ 'name': 'User limit', 'value': channel.user_limit or 'No limit' }, { 'name': 'Bitrate', 'value': channel.bitrate }, { 'name': 'Category', 'value': channel.category.name }] else: c_type = 'Category' member_count = ctx.guild.member_count additional_fields = [{ 'name': 'Channels', 'value': len(channel.channels) }] e.add_field(name='Channel type', value=c_type) e.add_field(name='Created', value=f'`{channel.created_at.replace(microsecond=0)}`') e.add_field(name='Member count', value=member_count) for field in additional_fields: e.add_field(**field) e.set_footer(text=channel.id) await ctx.send(embed=e)