コード例 #1
0
ファイル: module_why.py プロジェクト: fossabot/KiwiBot
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)
コード例 #2
0
ファイル: module_guilds.py プロジェクト: fossabot/KiwiBot
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)
コード例 #3
0
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)
コード例 #4
0
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)
コード例 #5
0
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)
コード例 #6
0
ファイル: module_invite.py プロジェクト: fossabot/KiwiBot
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)
コード例 #7
0
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)
コード例 #8
0
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)
コード例 #9
0
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)
コード例 #10
0
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)
コード例 #11
0
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}
コード例 #12
0
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)
コード例 #13
0
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)
コード例 #14
0
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'
            }
コード例 #15
0
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')
コード例 #16
0
ファイル: module_poll.py プロジェクト: fossabot/KiwiBot
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)
コード例 #17
0
ファイル: module_ddg.py プロジェクト: fossabot/KiwiBot
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)
コード例 #18
0
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)
コード例 #19
0
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)
コード例 #20
0
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('✅')
コード例 #21
0
ファイル: module_emoji.py プロジェクト: fossabot/KiwiBot
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)
コード例 #22
0
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)
コード例 #23
0
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)
コード例 #24
0
ファイル: module_usagestats.py プロジェクト: fossabot/KiwiBot
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)
コード例 #25
0
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'
コード例 #26
0
ファイル: module_vote.py プロジェクト: fossabot/KiwiBot
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)
コード例 #27
0
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()
コード例 #28
0
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)
コード例 #29
0
ファイル: module_channel.py プロジェクト: fossabot/KiwiBot
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)