예제 #1
0
    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)
예제 #2
0
    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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
    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)
예제 #6
0
    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)
예제 #7
0
    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)
예제 #8
0
    async def on_call(self, ctx, args, **flags):
        interval = flags.get('interval', None)
        if interval is not None:
            if not interval.isdigit():
                return '{warning} Interval is not integer'

            interval = int(interval)
            if interval < 20:
                return '{warning} Minimum allowed interval is 20 seconds'

            self.interval = interval
            await self.bot.redis.set('activity_interval', interval)

            if len(args) == 1:
                return 'Interval updated'

        status = flags.get('status', 'online')

        subcommand = args[1].lower()

        if subcommand == 'list':
            items = await self.bot.redis.smembers('activity')
            if not items:
                return 'Nothing to show'

            lines = [f'{i + 1}) {p}' for i, p in enumerate(items)]
            lines_per_chunk = 4
            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(content='List of activities:```\n' +
                           '\n'.join(chunk) + '```')

            return await p.run(ctx)

        elif subcommand == 'remove':
            items = await self.bot.redis.smembers('activity')
            if not items:
                return 'Nothing to remove'
            if len(items) == 1:
                await self.bot.redis.srem('activity', items[0])
                await self._change_precense(0, '', 'online')
                return f'Deleted activity `{items[0]}`'

            lines = [f'{i + 1}) {p}' for i, p in enumerate(items)]
            lines_per_chunk = 4
            chunks = [
                lines[i:i + lines_per_chunk]
                for i in range(0, len(lines), lines_per_chunk)
            ]

            p = SelectionPaginator(self.bot)
            for i, chunk in enumerate(chunks):
                p.add_page(content='Please, type index to remove```\n' +
                           '\n'.join(chunk) + '```')

            await p.run(ctx, len(lines))
            if p.choice is not None:
                await self.bot.redis.srem('activity', items[p.choice - 1])
                return f'Deleted activity `{items[p.choice - 1]}`'

        elif subcommand == 'playing':
            a_type = 0
            a_name = args[2:]
        elif subcommand == 'streaming':
            a_type = 1
            a_name = args[2:]
        elif subcommand == 'listening':
            a_type = 2
            a_name = args[2:]
        elif subcommand == 'watching':
            a_type = 3
            a_name = args[2:]
        else:
            return await self.on_doc_request(ctx)

        if a_name:
            await self.bot.redis.sadd('activity',
                                      f'{a_type}:{status}:{a_name}')
        await self._change_precense(a_type, a_name, status)

        return f'Status switched to `{a_name}`' if a_name else 'Status removed'
예제 #9
0
    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('✅')
예제 #10
0
    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)
예제 #11
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'
예제 #12
0
    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')