예제 #1
0
    async def show_grants(self, ctx, user: discord.Member=None):
        """Shows the roles you or the specified user can grant"""
        guild = ctx.guild
        if not user:
            user = ctx.author

        sql = 'SELECT `role` FROM `role_granting` WHERE guild=%s AND (user=%s OR user_role IN (%s))' % (guild.id, user.id, ', '.join((str(r.id) for r in user.roles)))
        try:
            rows = (await self.dbutil.execute(sql)).fetchall()
        except SQLAlchemyError:
            logger.exception('Failed to get role grants')
            return await ctx.send('Failed execute sql')

        if not rows:
            return await ctx.send("{} can't grant any roles".format(user))

        msg = 'Roles {} can grant:\n'.format(user)
        roles = set()
        for row in rows:
            role = guild.get_role(row['role'])
            if not role:
                continue

            if role.id in roles:
                continue

            roles.add(role.id)
            msg += '{0.name} `{0.id}`\n'.format(role)

        if not roles:
            return await ctx.send("{} can't grant any roles".format(user))

        for s in split_string(msg, maxlen=2000, splitter='\n'):
            await ctx.send(s)
예제 #2
0
    def draw_text(self, color: Color, size, font: ImageFont.FreeTypeFont):
        im = Image.new('RGB', size, color.rgb)

        draw = ImageDraw.Draw(im)
        name = str(color)
        text_size = font.getsize(name)
        text_color = self.text_color(color)
        if text_size[0] > size[0]:
            all_lines = []
            lines = split_string(name,
                                 maxlen=len(name) // (text_size[0] / size[0]))
            margin = 2
            total_y = 0
            for line in lines:
                line = line.strip()
                if not line:
                    continue

                if text_size[1] + total_y > size[1]:
                    break

                x = (size[0] - font.getsize(line)[0]) // 2
                all_lines.append((line, x))
                total_y += margin + text_size[1]

            y = (size[1] - total_y) // 2
            for line, x in all_lines:
                draw.text((x, y), line, font=font, fill=text_color)
                y += margin + text_size[1]

        else:
            x = (size[0] - text_size[0]) // 2
            y = (size[1] - text_size[1]) // 2
            draw.text((x, y), name, font=font, fill=text_color)
        return im
예제 #3
0
    async def local_(self, ctx, include_name=True, delim='\n'):
        """Show all non global emotes on this server"""
        guild = ctx.guild

        _, local_ = self.get_emotes(guild)
        s = self._format_emotes(local_, include_name, delim)
        s = s if s else 'No local emotes'
        for s in split_string(s, maxlen=2000, splitter=delim):
            await ctx.send(s)
예제 #4
0
    async def animated(self, ctx, include_name: bool=True):
        """Show all non global emotes on this server"""
        guild = ctx.guild

        _, _, animated = self.get_emotes(guild)
        s = self._format_emotes(animated, 'animated', include_name)
        s = s if s else 'No animated emotes'
        for s in split_string(s, maxlen=2000, splitter='\n'):
            await ctx.send(s)
예제 #5
0
    async def global_(self, ctx, include_name: bool=True):
        """Show global emotes on this server"""
        guild = ctx.guild

        global_, _, _ = self.get_emotes(guild)
        s = self._format_emotes(global_, include_name=include_name)
        s = s if s else 'No global emotes'
        for s in split_string(s, maxlen=2000, splitter='\n'):
            await ctx.send(s)
예제 #6
0
    def text_only_colors(colors):
        s = ''
        le = len(colors) - 1
        for idx, color in enumerate(colors.values()):
            s += str(color)

            if le != idx:
                s += ', '

        s = split_string(s, maxlen=2000, splitter=', ')
        return s
예제 #7
0
    def _parse_on_delete(msg, conf):
        content = msg.content
        user = msg.author

        message = conf['message']
        d = slots2dict(msg)
        d = slots2dict(user, d)
        for e in ['name', 'message']:
            d.pop(e, None)

        d['channel'] = msg.channel.mention
        message = message.format(name=str(user), message=content, **d)
        return split_string(message)
예제 #8
0
    async def on_message_edit(self, before, after):
        if before.content == after.content:
            image = self.get_image_from_embeds(after.embeds)
            if not image:
                return

            sql = "INSERT INTO `messages` (`shard`, `guild`, `channel`, `user`, `user_id`, `message`, `message_id`, `attachment`, `time`) " \
                  "VALUES (:shard, :guild, :channel, :user, :user_id, :message, :message_id, :attachment, :time) ON DUPLICATE KEY UPDATE attachment=IFNULL(attachment, :attachment)"

            d = self.format_for_db(after)
            self._q.put_nowait((sql, d))

        if before.author.bot or before.channel.id == 336917918040326166:
            return

        channel = self.bot.guild_cache.on_edit_channel(before.guild.id)
        channel = self.bot.get_channel(channel)
        if not channel:
            return

        message = self.bot.guild_cache.on_edit_message(before.guild.id,
                                                       default_message=True)
        if message is None:
            return

        is_embed = self.bot.guild_cache.on_edit_embed(before.guild.id)

        message = format_on_edit(before, after, message)
        if message is None:
            return

        perms = channel.permissions_for(
            channel.guild.get_member(self.bot.user.id))
        if not perms.send_messages or (is_embed and not perms.embed_links):
            return

        message = split_string(message, maxlen=2048 if is_embed else 2000)
        if len(message) > 4:
            m = '{0.id}: {0.name} On edit message had to post over 4 messages'.format(
                before.guild)
            logger.info(m)
            terminal.warning(m)

        for m in message:
            if is_embed:
                await channel.send(embed=self.create_embed(
                    after,
                    f'Message edited in #{after.channel.name} {after.channel.id}',
                    m, after.edited_at))
            else:
                await channel.send(m)
예제 #9
0
    async def whitelist(self, ctx, commands: commands.Greedy[CommandConverter],
                        *, mention: typing.Union[discord.TextChannel,
                                                 discord.Role, discord.User]):
        """Whitelist a command for a user, role or channel
        To whitelist multiple commands at the same time wrap the command names in quotes
        like this {prefix}{name} \"command1 command2 command3\" #channel

        To see specifics on the hierarchy of whitelist/blacklist see `{prefix}help blacklist`

        **WHITELISTING COULD BE DANGEROUS IF YOU DON'T KNOW WHAT YOU ARE DOING!**
        Before whitelisting read the following

        Whitelisting WILL OVERRIDE ANY REQUIRED PERMS for the command being called
        If a command requires ban perms and you whitelist it for a role
        everyone with that role can use that command even when they don't have ban perms

        Due to safety reasons whitelisting commands from this module is not allowed.
        Give the users correct discord perms instead
        """
        msg = ctx.message
        guild = msg.guild

        async def _whitelist(_command):
            name = _command.name
            if _command.cog_name == self.__class__.__name__:
                return f"Due to safety reasons commands from {_command.cog_name} module can't be whitelisted"

            elif isinstance(mention, discord.User):
                return await self._add_user_whitelist(ctx, name, mention,
                                                      guild)

            elif isinstance(mention, discord.Role):
                return await self._add_role_whitelist(ctx, name, mention,
                                                      guild)

            elif isinstance(mention, discord.TextChannel):
                return await self._add_channel_whitelist(
                    ctx, name, mention, guild)

        s = ''
        for command in commands:
            val = await _whitelist(command)
            if isinstance(val, str):
                s += val + '\n'

        if not s:
            return

        for msg in split_string(s, splitter='\n'):
            await ctx.send(msg)
예제 #10
0
    async def add_colors_from_roles(self, ctx, *, roles):
        """Turn existing role(s) to colors.
        Usage:
            {prefix}{name} 326726982656196629 Red @Blue
        Works with role mentions, role ids and name matching"""
        if not roles:
            return await ctx.send('Give some roles to turn to guild colors')

        roles = shlex.split(roles)
        guild = ctx.guild
        success = []
        failed = []
        for role in roles:
            r = get_role(role, guild.roles, name_matching=True)
            if r:
                success.append(r)
            else:
                failed.append(role)

        s = ''
        if success:
            s += 'Adding roles {}\n'.format(', '.join(
                ['`%s`' % r.name for r in success]))

        if failed:
            s += 'Failed to find roles {}'.format(', '.join(
                [f'`{r}`' for r in failed]))

        for s in split_string(s, splitter=', '):
            await ctx.send(s)

        await ctx.send('Do you want to continue?', delete_after=60)
        channel, author = ctx.channel, ctx.author

        def check(msg):
            if msg.channel.id != channel.id:
                return False
            if msg.author.id != author.id:
                return False
            return y_n_check(msg)

        try:
            msg = await self.bot.wait_for('message', timeout=60, check=check)
        except asyncio.TimeoutError:
            msg = None
        if msg is None or not y_check(msg.content):
            return await ctx.send('Cancelling', delete_after=60)

        await self._add_colors_from_roles(success, ctx)
예제 #11
0
    async def update_bot(self, ctx, *, options=None):
        """Does a git pull"""
        cmd = 'git pull'.split(' ')
        if options:
            cmd.extend(shlex.split(options))

        p = subprocess.Popen(cmd,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        out, err = await self.bot.loop.run_in_executor(self.bot.threadpool,
                                                       p.communicate)
        out = out.decode('utf-8')
        if err:
            out = err.decode('utf-8') + out

        # Only tries to update files in the cogs folder
        files = re.findall(r'(cogs/\w+)(?:.py *|)', out)

        if len(out) > 2000:
            out = out[:1996] + '...'

        await ctx.send(out)

        if files:
            files = [f.replace('/', '.') for f in files]
            await ctx.send(f'Do you want to reload files `{"` `".join(files)}`'
                           )

            try:
                msg = await self.bot.wait_for('message',
                                              check=basic_check(
                                                  ctx.author, ctx.channel),
                                              timeout=30)
            except asyncio.TimeoutError:
                return await ctx.send('Timed out')

            if not y_n_check(msg):
                return await ctx.send('Invalid answer. Not auto reloading')

            if not y_check(msg.content):
                return await ctx.send('Not auto updating')

            messages = split_string(await self.reload_multiple(files),
                                    list_join='\n',
                                    splitter='\n')
            for msg in messages:
                await ctx.send(msg)
예제 #12
0
    async def emotes(self, ctx):
        """Show emotes on this server"""
        guild = ctx.guild

        if ctx.invoked_subcommand is None:
            global_emotes, local_emotes = self.get_emotes(guild)

            g = 'No global emotes\n'
            if global_emotes:
                g = 'Global emotes:\n' + self._format_emotes(global_emotes)

            g += '\n'
            l = 'No local emotes'
            if local_emotes:
                l = 'Local emotes:\n' + self._format_emotes(local_emotes)

            strings = split_string(g + l, maxlen=2000, splitter='\n')

            for s in strings:
                await ctx.send(s)
예제 #13
0
    async def emotes(self, ctx):
        """Show emotes on this server"""
        guild = ctx.guild

        if ctx.invoked_subcommand is None:
            global_emotes, local_emotes, animated_emotes = self.get_emotes(guild)

            if global_emotes:
                s = 'Global emotes:\n' + self._format_emotes(global_emotes)
            elif local_emotes:
                s = 'Local emotes:\n' + self._format_emotes(local_emotes, 'local')
            elif animated_emotes:
                s = 'Animated emotes:\n' + self._format_emotes(animated_emotes, 'animated')
            else:
                s = 'No emotes'

            strings = split_string(s, maxlen=2000, splitter='\n')

            for s in strings:
                await ctx.send(s)
예제 #14
0
    async def get_package(self, ctx, *, name):
        """Get a package from pypi"""
        if SearchCommand is None:
            return await ctx.send('Not supported')

        def search():
            try:
                search_command = SearchCommand()
                options, _ = search_command.parse_args([])
                hits = search_command.search(name, options)
                if hits:
                    return hits[0]
            except:
                logger.exception('Failed to search package from PyPi')
                return

        hit = await self.bot.loop.run_in_executor(self.bot.threadpool, search)
        if not hit:
            return await ctx.send('No matches')

        async with self.bot.aiohttp_client.get(
                f'https://pypi.org/pypi/{quote(hit["name"])}/json') as r:
            if r.status != 200:
                return await ctx.send(f'HTTP error {r.status}')

            json = await r.json()

        info = json['info']
        description = info['description']
        if len(description) > 1000:
            description = split_string(description, splitter='\n',
                                       maxlen=1000)[0] + '...'

        embed = discord.Embed(title=hit['name'],
                              description=description,
                              url=info["package_url"])
        embed.add_field(name='Author', value=info['author'])
        embed.add_field(name='Version', value=info['version'])
        embed.add_field(name='License', value=info['license'])

        await ctx.send(embed=embed)
예제 #15
0
    async def leave_set(self, ctx, *, message):
        """Set the leave message on this server
        See {prefix}formatting for help on formatting the message"""
        guild = ctx.guild
        try:
            formatted = format_join_leave(ctx.author, message)
        except Exception as e:
            return await ctx.send(
                'Failed to use format because it returned an error.```py\n{}```'
                .format(e))

        splitted = split_string(formatted, splitter='\n')
        if len(splitted) > 1:
            return await ctx.send(
                'The message generated using this format is too long. Please reduce the amount of text/variables'
            )

        success = await self.cache.set_leave_message(guild.id, message)
        if not success:
            await ctx.send('Failed to set message format because of an error')
        else:
            await ctx.send('Successfully set the message format')
예제 #16
0
    async def get_roles(self, ctx, page=''):
        """Get roles on this server"""
        guild_roles = sorted(ctx.guild.roles, key=lambda r: r.name)
        idx = 0
        if page:
            try:
                idx = int(page) - 1
                if idx < 0:
                    return await ctx.send('Index must be bigger than 0')
            except ValueError:
                return await ctx.send('%s is not a valid integer' % page,
                                      delete_after=30)

        roles = 'A total of %s roles\n' % len(guild_roles)
        for role in guild_roles:
            roles += '{}: {}\n'.format(role.name, role.mention)

        roles = split_string(roles, splitter='\n', maxlen=1000)
        await send_paged_message(ctx,
                                 roles,
                                 starting_idx=idx,
                                 page_method=lambda p, i: '```{}```'.format(p))
예제 #17
0
    async def on_message_delete(self, msg):
        if msg.author.bot or msg.channel.id == 336917918040326166:
            return

        channel = self.bot.guild_cache.on_delete_channel(msg.guild.id)
        channel = self.bot.get_channel(channel)
        if channel is None:
            return

        is_embed = self.bot.guild_cache.on_delete_embed(msg.guild.id)

        perms = channel.permissions_for(
            channel.guild.get_member(self.bot.user.id))
        if not perms.send_messages or (is_embed and not perms.embed_links):
            return

        message = self.bot.guild_cache.on_delete_message(msg.guild.id,
                                                         default_message=True)
        message = format_on_delete(msg, message)
        message = split_string(message,
                               splitter='\n',
                               maxlen=2048 if is_embed else 2000)
        if len(message) > 2:
            m = '{0.id}: {0.name} On delete message had to post over 2 messages'.format(
                msg.guild)
            logger.info(m)
            terminal.warning(m)

        for m in message:
            if is_embed:
                await channel.send(embed=self.create_embed(
                    msg,
                    f'Message deleted in #{msg.channel.name} {msg.channel.id}',
                    m, msg.created_at))
            else:
                await channel.send(m)
예제 #18
0
    async def commands_(self, ctx, user: discord.Member = None):
        """Get your or the specified users white- and blacklisted commands on this server"""
        guild = ctx.guild
        if not user:
            user = ctx.author

        if user.roles:
            roles = '(role IS NULL OR role IN ({}))'.format(', '.join(
                map(lambda r: str(r.id), user.roles)))
        else:
            roles = 'role IS NULL'

        where = f'guild={guild.id} AND (user={user.id} or user IS NULL) AND channel IS NULL AND {roles}'

        rows = self.get_rows(where)

        commands = {}
        for row in rows:
            name = row['command']

            if name in commands:
                commands[name].append(row)
            else:
                commands[name] = [row]

        whitelist = []
        blacklist = []
        global_blacklist = []
        for name, rows in commands.items():
            row = self.get_applying_perm(rows)
            name = f'`{name}`'
            if row is False:
                global_blacklist.append(name)
                continue

            # Don't want channel or server specific blacklists
            if row is None:
                continue

            if row['type'] == BlacklistTypes.WHITELIST:
                whitelist.append(name)

            elif row['type'] == BlacklistTypes.BLACKLIST:
                blacklist.append(name)

        s = ''
        if whitelist:
            s += f'{user}s whitelisted commands\n' + '\n'.join(
                whitelist) + '\n\n'

        if blacklist:
            s += f'Commands blacklisted fom {user}\n' + '\n'.join(
                blacklist) + '\n\n'

        if global_blacklist:
            s += f'Commands globally blacklisted for {user}\n' + '\n'.join(
                global_blacklist) + '\n\n'

        if not s:
            s = '{0} has no special perms set up on the server {1}'.format(
                user, guild.name)
        else:
            s += '{}s perms on server {}\nChannel specific perms are not checked'.format(
                user, guild.name)

        s = split_string(s, maxlen=2000, splitter='\n')
        for ss in s:
            await ctx.author.send(ss)
예제 #19
0
 async def reload_all(self, ctx):
     messages = await self.reload_multiple(self.bot.default_cogs)
     messages = split_string(messages, list_join='\n', splitter='\n')
     for msg in messages:
         await ctx.send(msg)
예제 #20
0
    async def blacklist(self,
                        ctx,
                        commands: commands.Greedy[CommandConverter] = None,
                        *,
                        mention: typing.Union[discord.TextChannel,
                                              discord.Role,
                                              discord.User] = None):
        """Blacklist a command for a user, role or channel
        To blacklist multiple commands at the same time wrap the command names in quotes
        like this {prefix}{name} \"command1 command2 command3\" #channel
        The hierarchy of `blacklist` and `whitelist` is as follows
        Whitelist always overrides blacklist of the same level

        Then levels of scope it can have are as follows
        `User` > `Role` > `Channel` > `Server` where each level overrides every scope perm after it
        e.g. Blacklisting command ping for role Member and whitelisting it for role Mod
        would make it so people with Member role wouldn't be able to use it unless they had Mod role

        Also if you further whitelisted ping from a single member
        that user would be able to use the command always
        since user whitelist overrides every other scope

        To blacklist a command server wide specify the commands and don't specify the mention param
        like this `{prefix}blacklist "cmd1 cmd2 etc"` which would blacklist those commands
        for everyone in the server unless they have it whitelisted
        Whitelisting server wide isn't possible

        For dangers of whitelisting see `{prefix}help whitelist`"""
        guild = ctx.guild

        if not commands and mention is None:
            return await ctx.send('No parameters given')

        async def _blacklist(name):
            if mention is None:
                whereclause = 'guild=%s AND type IN (%s, %s) AND command="%s" AND channel IS NULL AND role IS NULL AND user IS NULL' % (
                    guild.id, BlacklistTypes.BLACKLIST,
                    BlacklistTypes.WHITELIST, name)
                success = await self._set_blacklist(ctx,
                                                    whereclause,
                                                    guild=guild.id,
                                                    command=name)
                if success:
                    return 'Blacklisted command {} from this server'.format(
                        name)
                elif success is None:
                    return 'Removed blacklist for command {} on this server'.format(
                        name)

            elif isinstance(mention, discord.User):
                return await self._add_user_blacklist(ctx, name, mention,
                                                      guild)

            elif isinstance(mention, discord.Role):
                return await self._add_role_blacklist(ctx, name, mention,
                                                      guild)

            elif isinstance(mention, discord.TextChannel):
                return await self._add_channel_blacklist(
                    ctx, name, mention, guild)

        s = ''
        if commands is None:
            val = await self._set_all_commands(ctx, mention)
            if isinstance(val, str):
                s += val
        else:
            for command in commands:
                if command.name == 'privacy':
                    await ctx.send(
                        "Cannot blacklist privacy command as it's required that anyone can see it"
                    )
                    continue

                val = await _blacklist(command.name)
                if isinstance(val, str):
                    s += val + '\n'

        if not s:
            return

        for msg in split_string(s, splitter='\n'):
            await ctx.send(msg)
예제 #21
0
    async def remove_every(self, guild, channel, message, title, winners):
        guild = self.bot.get_guild(guild)
        if not guild:
            await self.delete_giveaway_from_db(message)
            return

        role = guild.get_role(323098643030736919 if not self.bot.test_mode else 440964128178307082)
        if role is None:
            await self.delete_giveaway_from_db(message)
            return

        channel = self.bot.get_channel(channel)
        if not channel:
            await self.delete_giveaway_from_db(message)
            return

        try:
            message = await channel.get_message(message)
        except discord.NotFound:
            logger.exception('Could not find message for every toggle')
            await self.delete_giveaway_from_db(message)
            return
        except Exception:
            logger.exception('Failed to get toggle every message')

        react = None
        for reaction in message.reactions:
            emoji = reaction.emoji
            if isinstance(emoji, str):
                continue
            if emoji.id == 363025405562585088 and emoji.name == 'GWjojoGachiGASM':
                react = reaction
                break

        if react is None:
            logger.debug('react not found')
            return

        title = 'Giveaway: {}'.format(title)
        description = 'No winners'
        users = await react.users(limit=react.count).flatten()
        candidates = [guild.get_member(user.id) for user in users if user.id != self.bot.user.id and guild.get_member(user.id)]
        winners = choice(candidates, min(winners, len(candidates)), replace=False)
        if len(winners) > 0:
            winners = sorted(winners, key=lambda u: u.name)
            description = 'Winners: {}'.format('\n'.join([user.mention for user in winners]))

        added = 0
        removed = 0
        for winner in winners:
            winner = guild.get_member(winner.id)
            if not winner:
                continue
            if role in winner.roles:
                retval = await retry(winner.remove_roles, role, reason='Won every toggle giveaway')
                removed += 1

            else:
                retval = await retry(winner.add_roles, role, reason='Won every toggle giveaway')
                added += 1

            if isinstance(retval, Exception):
                logger.debug('Failed to toggle every role on {0} {0.id}\n{1}'.format(winner, retval))

        embed = discord.Embed(title=title, description=description[:2048], timestamp=datetime.utcnow())
        embed.set_footer(text='Expired at', icon_url=get_avatar(self.bot.user))
        await message.edit(embed=embed)
        description += '\nAdded every to {} user(s) and removed it from {} user(s)'.format(added, removed)
        for msg in split_string(description, splitter='\n', maxlen=2000):
            await message.channel.send(msg)

        await self.delete_giveaway_from_db(message.id)
예제 #22
0
        def do_it():
            nonlocal text
            # Linearly decreasing fontsize
            fontsize = int(round(45.0 - 0.08 * len(text)))
            fontsize = min(max(fontsize, 15), 45)
            font = ImageFont.truetype(os.path.join('M-1c', 'mplus-1c-bold.ttf'), fontsize)
            im = Image.open(os.path.join(TEMPLATES, 'narancia.png'))
            shadow = Image.open(os.path.join(TEMPLATES, 'narancia_shadow.png'))
            draw = ImageDraw.Draw(im)
            size = (250, 350)  # Size of the page
            spot = (400, 770)  # Pasting spot for first page
            text = text.replace('\n', ' ')

            # We need to replace the height of the text with the height of A
            # Since that what draw.text uses in it's text drawing methods but not
            # in the text size methods. Nice design I know. It makes textsize inaccurate
            # so don't use that method
            text_size = font.getsize(text)
            text_size = (text_size[0], font.getsize('A')[1])

            # Linearly growing spacing
            spacing = int(round(0.5 + 0.167 * fontsize))
            spacing = min(max(spacing, 3), 6)

            # We add 2 extra to compensate for measuring inaccuracies
            line_height = text_size[1]
            spot_changed = False

            all_lines = []
            # Split lines based on average width
            # If max characters per line is less than the given max word
            # use max line width as max word width
            max_line = int(len(text) // (text_size[0] / size[0]))
            lines = split_string(text, maxlen=max_line, max_word=min(max_line, 30))
            total_y = 0

            for line in lines:
                line = line.strip()
                if not line:
                    continue

                total_y += line_height
                if total_y > size[1]:
                    draw.multiline_text(spot, '\n'.join(all_lines), font=font,
                                        fill='black', spacing=spacing)
                    all_lines = []
                    if spot_changed:
                        # We are already on second page. Let's stop here
                        break

                    spot_changed = True
                    # Pasting spot and size for second page
                    spot = (678, 758)
                    size = (250, 350)
                    total_y = line_height

                total_y += spacing

                all_lines.append(line)

            draw.multiline_text(spot, '\n'.join(all_lines), font=font,
                                fill='black', spacing=spacing)

            im.alpha_composite(shadow)
            return self.save_image(im, 'PNG')