Exemple #1
0
def rate_limits_and_misc():
    misc_config = Config("misc.json")
    cooldown_coeffs = misc_config.get("cooldown_coeffs")
    for chan_id, value in cooldown_coeffs.items():
        rconfig.set("chan:{}:rate_limits:cooldown_ratio".format(chan_id), float(value))
    counters = misc_config.get("counters")["Porygon2"]
    for command, uses in counters.items():
        rconfig.zadd("misc:rate_limits:counters", int(uses), command)
Exemple #2
0
class MemeGenerator:
    pattern = re.compile(r'(\w+)\s+(.*)$')

    def __init__(self, bot):
        self.bot = bot
        self.conf = Config('configs/memes.json')
        doc = self.meme.__dict__['help']
        doc += '\n  '
        doc += '\n  '.join(self.conf.get('memes', {}).keys())

        self.meme.__dict__['help'] = doc

    @commands.command(pass_context=True, aliases=['memes'])
    async def meme(self, ctx, *, text: str):
        """
    Adds text to images

    Valid names so far:
    """

        match = MemeGenerator.pattern.match(text)
        name = match.group(1).lower()
        text = match.group(2)
        text = ' '.join(dh.remove_comments(text.split()))

        cfg = self.conf.get('memes', {}).get(name, None)

        if not cfg:
            await self.bot.say(error('Could not find image'))
            return
        if not text:
            await self.bot.say(error('Are you trying to get an empty image?'))
            return

        temp = tempfile.NamedTemporaryFile(suffix=".png")

        if 'font' not in cfg:
            cfg['font'] = self.conf.get('font', '')
        if 'path' not in cfg:
            cfg['path'] = self.conf.get('path', '')

        write_image(text, temp.name, **cfg)

        await self.bot.send_file(ctx.message.channel, temp.name)

        temp.close()
Exemple #3
0
async def on_message(message):
    if message.author.bot:
        return

    perms = Config('configs/perms.json')
    if message.author.id in perms.get('ignore', []):
        return

    if not re.search('^[\\.!\\?\\$]{2,}', message.content):
        await bot.process_commands(message)
Exemple #4
0
def friend_codes():
    fc_config = Config("friend_codes.json")
    for user_id in fc_config:
        fc_info = fc_config.get(user_id)
        for key, val in fc_info.items():
            if key in ["3DS", "Switch"]:
                rconfig.set("user:{}:fc:{}:code".format(user_id, key), val)
            elif "ign" in key:
                if "3DS" in key:
                    rconfig.hmset("user:{}:fc:3DS:ign".format(user_id), val)  # pretty sure I only support 3ds
Exemple #5
0
class Games:
    def __init__(self, bot):
        self.bot = bot
        self.conf = Config('configs/games.json')

    @perms.is_owner()
    @commands.command(pass_context=True, aliases=['faa'])
    async def fake_artist_add(self, ctx, *, themes):
        self.conf['fake_artist']['themes'].extend(themes.strip().split('\n'))
        self.conf.save()
        await self.bot.say(formatter.ok())

    @commands.command(pass_context=True, aliases=['fa'])
    async def fake_artist(self, ctx, number: int):
        conf = self.conf.get('fake_artist', {})
        themes = conf.get('themes', [])
        themes = random.sample(themes, len(themes) - len(themes) % number)
        output = [[] for i in range(number)]
        fakes = list(range(number)) * (len(themes) // number)
        random.shuffle(fakes)
        say = 'here are the links:'

        # generate
        for theme, fake in zip(themes, fakes):
            for i in range(len(output)):
                output[i].append(theme if i != fake else 'YOU ARE THE FAKE')

        # generate master file
        with open(os.path.join(conf.get('path', ''), 'master.html'), 'w') as f:
            f.write(conf.get('rules'))
            for i, theme in enumerate(themes):
                f.write(f'''<li><input class="spoilerbutton" type="button"'''+ \
                        f'''value="show" onclick="this.value=this.value=='show'''+ \
                        f'''\'?'{html.escape(theme)}':'show';"></li>''')
            f.write(conf.get('out'))

        # generate player files
        for i in range(len(output)):
            filename = os.path.join(conf.get('path', ''), f'{i+1}.html')
            with open(filename, 'w') as f:
                f.write(conf.get('rules'))
                for theme in output[i]:
                    f.write(f'<li>{html.escape(theme)}</li>')
                f.write(conf.get('out'))
            say += f'\nhttps://andy29485.tk/files/{i+1}.html'

        await self.bot.say(formatter.ok(say))
Exemple #6
0
class Yuna(commands.AutoShardedBot):
    def __init__(self):
        super().__init__(command_prefix=get_prefix)
        self.session = aiohttp.ClientSession(loop=self.loop)
        self._prev_events = deque(maxlen=10)
        self.commands_run = 0

        self.prefixes = Config('prefixes.json')
        self.remove_command('help')
        self.add_command(self._help)

        for extension in INITIAL_EXTENSIONS:
            try:
                self.load_extension(f'cogs.{extension}')
                print(f'[INFO] Loaded {extension}')
            except Exception as e:
                print(f'[FAIL] Failed to load {extension} with error: {e}')

    @property
    def config(self):
        import config as _cfg
        return _cfg

    @commands.command(name='help', hidden=True)
    async def _help(self, ctx, *, command: str = None):
        """Shows help about a command or the bot"""
        try:
            if command is None:
                p = await HelpPaginator.from_bot(ctx)
            else:
                entity = self.get_cog(command) or self.get_command(command)

                if entity is None:
                    clean = command.replace('@', '@\u200b')
                    return await ctx.send(
                        f'Command or category "{clean}" not found.')
                elif isinstance(entity, commands.Command):
                    p = await HelpPaginator.from_command(ctx, entity)
                else:
                    p = await HelpPaginator.from_cog(ctx, entity)

            await p.paginate()
        except Exception as e:
            await ctx.send(e)

    @property
    def config(self):
        """Returns the config."""
        return __import__('config')

    async def avatar_queue(self):
        try:
            avatars = [
                'https://cdn.discordapp.com/attachments/488928330805018626/492776771662643200/maxresdefault.png?width=734&height=413',
                'https://media.discordapp.net/attachments/488928330805018626/492777747941163009/image0.png?width=660&height=413',
                'https://media.discordapp.net/attachments/488928330805018626/492776959588433928/878577-download-wallpaper-yuna-1961x1226-pc.png?width=660&height=413'
            ]
            while True:
                r = requests.get(rnd(avatars))
                await self.user.edit(avatar=r.content)
                await asyncio.sleep(86400)
        except Exception as e:
            print(e)

    async def on_ready(self):
        print(f"[INFO] I'm Alive!\n"\
           f"[NAME] Logged in as {self.user.name}.\n"\
           f"[ ID ] {self.user.id}")
        await self.change_presence(activity=discord.Activity(
            name='y?help | UwU', type=discord.ActivityType.listening))
        self.loop.create_task(self.avatar_queue())

    @property
    def error_ch(self):
        ch = self.get_channel(492797168005152778)
        return ch

    @property
    def guild_ch(self):
        ch = self.get_channel(492797204873084949)
        return ch

    async def send_guild_stats(self, e, guild):
        e.add_field(name='Name', value=guild.name)
        e.add_field(name='ID', value=guild.id)
        e.add_field(name='Owner',
                    value=f'{guild.owner} (ID: {guild.owner.id})')

        bots = sum(m.bot for m in guild.members)
        total = guild.member_count
        online = sum(m.status is discord.Status.online for m in guild.members)
        e.add_field(name='Members', value=str(total))
        e.add_field(name='Bots', value=f'{bots} ({bots/total:.2%})')
        e.add_field(name='Online', value=f'{online} ({online/total:.2%})')

        if guild.icon:
            e.set_thumbnail(url=guild.icon_url)

        if guild.me:
            e.timestamp = guild.me.joined_at

        await self.guild_ch.send(embed=e)

    async def on_guild_join(self, guild):
        e = discord.Embed(colour=0x53dda4, title='New Guild')
        await self.send_guild_stats(e, guild)

    async def on_guild_remove(self, guild):
        e = discord.Embed(colour=0xdd5f53, title='Left Guild')
        await self.send_guild_stats(e, guild)

    async def on_command_error(self, ctx, error):
        try:
            ignored = (commands.NoPrivateMessage, commands.DisabledCommand,
                       commands.CheckFailure, commands.CommandNotFound,
                       commands.UserInputError, discord.Forbidden,
                       commands.CommandOnCooldown)
            error = getattr(error, 'original', error)

            if isinstance(error, ignored):
                return

            e = discord.Embed(title='Command Error', colour=0xcc3366)
            e.add_field(name='Name', value=ctx.command.qualified_name)
            e.add_field(name='Author',
                        value=f'{ctx.author} (ID: {ctx.author.id})')

            fmt = f'Channel: {ctx.channel} (ID: {ctx.channel.id})'
            if ctx.guild:
                fmt = f'{fmt}\nGuild: {ctx.guild} (ID: {ctx.guild.id})'

            e.add_field(name='Location', value=fmt, inline=False)

            exc = ''.join(
                traceback.format_exception(type(error),
                                           error,
                                           error.__traceback__,
                                           chain=False))
            e.description = f'```py\n{exc}\n```'
            e.timestamp = datetime.datetime.utcnow()
            await self.error_ch.send(embed=e)
        except Exception as e:
            print(e)

    async def on_error(self, event, *args, **kwargs):
        e = discord.Embed(title='Event Error', colour=0xa32952)
        e.add_field(name='Event', value=event)
        e.description = f'```py\n{traceback.format_exc()}\n```'
        e.timestamp = datetime.datetime.utcnow()

        await self.error_ch.send(embed=e)

    async def on_command(self, ctx):
        """This triggers when a command is invoked."""
        self.commands_run += 1

    def get_guild_prefixes(self, guild, *, local_inject=get_prefix):
        """Gets the guild prefixes."""
        proxy_msg = discord.Object(id=None)
        proxy_msg.guild = guild
        return local_inject(self, proxy_msg)

    def get_raw_guild_prefixes(self, guild_id):
        """Gets the raw guild prefixes."""
        return self.prefixes.get(guild_id, ['.'])

    async def set_guild_prefixes(self, guild, prefixes):
        """Sets the guild prefixes."""
        if not prefixes[0]:
            await self.prefixes.put(guild.id, [])
        elif len(prefixes) > 10:
            raise RuntimeError('Cannot have more than 10 custom prefixes.')
        else:
            await self.prefixes.put(guild.id,
                                    sorted(set(prefixes), reverse=True))

    async def on_message(self, message):
        if message.author.bot: return

        await self.process_commands(message)

    async def on_resumed(self):
        """This triggers when the bot resumed after an outage."""
        print('[INFO] Resumed...')

    async def process_commands(self, message):
        """This processes the commands."""
        ctx = await self.get_context(message, cls=context.Context)

        if ctx.command is None:
            return

        async with ctx.acquire():
            await self.invoke(ctx)

    async def close(self):
        await super().close()
        await self.session.close()

    def run(self):
        try:
            super().run(config.token, reconnect=True)
        finally:
            with open('prev_events.log', 'w', encoding='utf-8') as _fp:
                for data in self._prev_events:
                    try:
                        _x = json.dumps(data, ensure_ascii=True, indent=4)
                    except:
                        _fp.write(f'{data}\n')
                    else:
                        _fp.write(f'{_x}\n')
Exemple #7
0
    msg = ctx.message
    chan = None
    if ctx.message.channel.is_private:
        chan = 'PM'
    else:
        chan = '#{0.channel.name} ({0.server.name})'.format(msg)

    logger.info('{0.author.name} in {1}: {0.content}'.format(msg, chan))


@bot.async_event
async def on_message(message):
    if message.author.bot:
        return

    perms = Config('configs/perms.json')
    if message.author.id in perms.get('ignore', []):
        return

    if not re.search('^[\\.!\\?\\$]{2,}', message.content):
        await bot.process_commands(message)


auth = Config('configs/auth.json')
while len(auth.get('token', '')) < 30:
    auth['token'] = input('Please enter bot\'s token: ')
    auth.save()

#start bot
bot.run(auth['token'])
class RoboDanny(commands.AutoShardedBot):
    def __init__(self):
        super().__init__(command_prefix=_prefix_callable,
                         description=description,
                         pm_help=None,
                         help_attrs=dict(hidden=True),
                         fetch_offline_members=False,
                         heartbeat_timeout=150.0)

        self.client_id = config.client_id
        self.carbon_key = config.carbon_key
        self.bots_key = config.bots_key
        self.challonge_api_key = config.challonge_api_key
        self.session = aiohttp.ClientSession(loop=self.loop)

        self._prev_events = deque(maxlen=10)

        # shard_id: List[datetime.datetime]
        # shows the last attempted IDENTIFYs and RESUMEs
        self.resumes = defaultdict(list)
        self.identifies = defaultdict(list)

        # guild_id: list
        self.prefixes = Config('prefixes.json')

        # guild_id and user_id mapped to True
        # these are users and guilds globally blacklisted
        # from using the bot
        self.blacklist = Config('blacklist.json')

        # in case of even further spam, add a cooldown mapping
        # for people who excessively spam commands
        self.spam_control = commands.CooldownMapping.from_cooldown(
            10, 12.0, commands.BucketType.user)

        # A counter to auto-ban frequent spammers
        # Triggering the rate limit 5 times in a row will auto-ban the user from the bot.
        self._auto_spam_count = Counter()

        for extension in initial_extensions:
            try:
                self.load_extension(extension)
            except Exception as e:
                print(f'Failed to load extension {extension}.',
                      file=sys.stderr)
                traceback.print_exc()

    def _clear_gateway_data(self):
        one_week_ago = datetime.datetime.utcnow() - datetime.timedelta(days=7)
        for shard_id, dates in self.identifies.items():
            to_remove = [
                index for index, dt in enumerate(dates) if dt < one_week_ago
            ]
            for index in reversed(to_remove):
                del dates[index]

        for shard_id, dates in self.resumes.items():
            to_remove = [
                index for index, dt in enumerate(dates) if dt < one_week_ago
            ]
            for index in reversed(to_remove):
                del dates[index]

    async def on_socket_response(self, msg):
        self._prev_events.append(msg)

    async def before_identify_hook(self, shard_id, *, initial):
        self._clear_gateway_data()
        self.identifies[shard_id].append(datetime.datetime.utcnow())
        await super().before_identify_hook(shard_id, initial=initial)

    async def on_command_error(self, ctx, error):
        if isinstance(error, commands.NoPrivateMessage):
            await ctx.author.send(
                'This command cannot be used in private messages.')
        elif isinstance(error, commands.DisabledCommand):
            await ctx.author.send(
                'Sorry. This command is disabled and cannot be used.')
        elif isinstance(error, commands.CommandInvokeError):
            original = error.original
            if not isinstance(original, discord.HTTPException):
                print(f'In {ctx.command.qualified_name}:', file=sys.stderr)
                traceback.print_tb(original.__traceback__)
                print(f'{original.__class__.__name__}: {original}',
                      file=sys.stderr)
        elif isinstance(error, commands.ArgumentParsingError):
            await ctx.send(error)

    def get_guild_prefixes(self, guild, *, local_inject=_prefix_callable):
        proxy_msg = discord.Object(id=0)
        proxy_msg.guild = guild
        return local_inject(self, proxy_msg)

    def get_raw_guild_prefixes(self, guild_id):
        return self.prefixes.get(guild_id, ['?', '!'])

    async def set_guild_prefixes(self, guild, prefixes):
        if len(prefixes) == 0:
            await self.prefixes.put(guild.id, [])
        elif len(prefixes) > 10:
            raise RuntimeError('Cannot have more than 10 custom prefixes.')
        else:
            await self.prefixes.put(guild.id,
                                    sorted(set(prefixes), reverse=True))

    async def add_to_blacklist(self, object_id):
        await self.blacklist.put(object_id, True)

    async def remove_from_blacklist(self, object_id):
        try:
            await self.blacklist.remove(object_id)
        except KeyError:
            pass

    async def on_ready(self):
        if not hasattr(self, 'uptime'):
            self.uptime = datetime.datetime.utcnow()

        print(f'Ready: {self.user} (ID: {self.user.id})')

    async def on_shard_resumed(self, shard_id):
        print(f'Shard ID {shard_id} has resumed...')
        self.resumes[shard_id].append(datetime.datetime.utcnow())

    @property
    def stats_webhook(self):
        wh_id, wh_token = self.config.stat_webhook
        hook = discord.Webhook.partial(id=wh_id,
                                       token=wh_token,
                                       adapter=discord.AsyncWebhookAdapter(
                                           self.session))
        return hook

    def log_spammer(self, ctx, message, retry_after, *, autoblock=False):
        guild_name = getattr(ctx.guild, 'name', 'No Guild (DMs)')
        guild_id = getattr(ctx.guild, 'id', None)
        fmt = 'User %s (ID %s) in guild %r (ID %s) spamming, retry_after: %.2fs'
        log.warning(fmt, message.author, message.author.id, guild_name,
                    guild_id, retry_after)
        if not autoblock:
            return

        wh = self.stats_webhook
        embed = discord.Embed(title='Auto-blocked Member', colour=0xDDA453)
        embed.add_field(name='Member',
                        value=f'{message.author} (ID: {message.author.id})',
                        inline=False)
        embed.add_field(name='Guild Info',
                        value=f'{guild_name} (ID: {guild_id})',
                        inline=False)
        embed.add_field(name='Channel Info',
                        value=f'{message.channel} (ID: {message.channel.id}',
                        inline=False)
        embed.timestamp = datetime.datetime.utcnow()
        return wh.send(embed=embed)

    async def process_commands(self, message):
        ctx = await self.get_context(message, cls=context.Context)

        if ctx.command is None:
            return

        if ctx.author.id in self.blacklist:
            return

        if ctx.guild is not None and ctx.guild.id in self.blacklist:
            return

        bucket = self.spam_control.get_bucket(message)
        current = message.created_at.replace(
            tzinfo=datetime.timezone.utc).timestamp()
        retry_after = bucket.update_rate_limit(current)
        author_id = message.author.id
        if retry_after and author_id != self.owner_id:
            self._auto_spam_count[author_id] += 1
            if self._auto_spam_count[author_id] >= 5:
                await self.add_to_blacklist(author_id)
                del self._auto_spam_count[author_id]
                await self.log_spammer(ctx,
                                       message,
                                       retry_after,
                                       autoblock=True)
            else:
                self.log_spammer(ctx, message, retry_after)
            return
        else:
            self._auto_spam_count.pop(author_id, None)

        try:
            await self.invoke(ctx)
        finally:
            # Just in case we have any outstanding DB connections
            await ctx.release()

    async def on_message(self, message):
        if message.author.bot:
            return
        await self.process_commands(message)

    async def on_guild_join(self, guild):
        if guild.id in self.blacklist:
            await guild.leave()

    async def close(self):
        await super().close()
        await self.session.close()

    def run(self):
        try:
            super().run(config.token, reconnect=True)
        finally:
            with open('prev_events.log', 'w', encoding='utf-8') as fp:
                for data in self._prev_events:
                    try:
                        x = json.dumps(data, ensure_ascii=True, indent=4)
                    except:
                        fp.write(f'{data}\n')
                    else:
                        fp.write(f'{x}\n')

    @property
    def config(self):
        return __import__('config')
Exemple #9
0
class WBot(commands.AutoShardedBot):
    """Discord bot made by iWeeti#8031."""
    def __init__(self):
        super().__init__(command_prefix=_prefix_callable,
                         description=DESCRIPTION,
                         fetch_offline_members=False)

        self.session = aiohttp.ClientSession(loop=self.loop)
        self.commands_executed = 0
        self._prev_events = deque(maxlen=10)
        self.add_command(self._do)
        self.add_command(self.setup)
        self.remove_command('help')
        self.uptime = datetime.datetime.utcnow()

        self.prefixes = Config('prefixes.json')

        for extension in INITIAL_EXTENSIONS:
            try:
                self.load_extension(extension)
                print(f'[INFO] {extension} loaded.')
            except ModuleNotFoundError:
                print(f'[FAIL] Extension {extension} not found.',
                      file=sys.stderr)
            except:
                print(f'[FAIL] Failed to load extension {extension}.',
                      file=sys.stderr)
                traceback.print_exc()

    logger = logging.getLogger('__main__')

    @property
    def config(self):
        """Returns the config."""
        return __import__('config')

    def get_guild_prefixes(self, guild, *, local_inject=_prefix_callable):
        """Gets the guild prefixes."""
        proxy_msg = discord.Object(id=None)
        proxy_msg.guild = guild
        return local_inject(self, proxy_msg)

    def get_raw_guild_prefixes(self, guild_id):
        """Gets the raw guild prefixes."""
        return self.prefixes.get(guild_id, ['.'])

    async def set_guild_prefixes(self, guild, prefixes):
        """Sets the guild prefixes."""
        if not prefixes[0]:
            await self.prefixes.put(guild.id, [])
        elif len(prefixes) > 10:
            raise RuntimeError('Cannot have more than 10 custom prefixes.')
        else:
            await self.prefixes.put(guild.id,
                                    sorted(set(prefixes), reverse=True))

    async def on_guild_join(self, guild):
        """This triggers when the bot joins a guild."""
        game = discord.Activity(name=f"slaves in {len(self.guilds)} servers.",
                                type=discord.ActivityType.watching)
        await self.change_presence(status=discord.Status.online, activity=game)
        if guild.id == 421630709585805312:
            return
        try:
            channel = await guild.create_text_channel('w-bot-logging')
            overwrite = discord.PermissionOverwrite(read_messages=False)
            role = guild.default_role
            await channel.set_permissions(role, overwrite=overwrite)
            await guild.owner.send(f'Hey, it seems that you own **{guild.name}**'
                                   ' and I have been invited to there.'\
                                   'Run ``.settings`` to get started.')
            await channel.send('To get started run ``.settings``.'\
                               'If you need more info join here https://discord.gg/Ry4JQRf.' \
                               'You can also check my commands by running ``.help``.')
        except discord.Forbidden:
            await guild.owner.send('Please let me have Create channels'
                                   ' permissions so you can get started.'\
                                   'After you have setted up the permissions, run ``.setup``.')

    async def on_member_join(self, member):
        """This triggers when someone joins a guild the bot is in."""
        guild = member.guild
        if guild.id == 329993146651901952:
            role = discord.utils.get(member.guild.roles, name='Member')
            try:
                await member.add_roles(role)
            except discord.Forbidden:
                member.guild.send('I do not have proper permissions'
                                  ' or high enough rank to give roles.')
        else:
            return

    @property
    def error_ch(self):
        """Returns the error logging channel."""
        ch = self.get_channel(491609962821451776)
        return ch

    @property
    def guild_ch(self):
        """Returns the guild logging channel."""
        ch = self.get_channel(493774827610439690)
        return ch

    async def send_guild_stats(self, e, guild):
        e.add_field(name='Name', value=guild.name)
        e.add_field(name='ID', value=guild.id)
        e.add_field(name='Owner',
                    value=f'{guild.owner} (ID: {guild.owner.id})')

        bots = sum(m.bot for m in guild.members)
        total = guild.member_count
        online = sum(m.status is discord.Status.online for m in guild.members)
        e.add_field(name='Members', value=str(total))
        e.add_field(name='Bots', value=f'{bots} ({bots/total:.2%})')
        e.add_field(name='Online', value=f'{online} ({online/total:.2%})')

        if guild.icon:
            e.set_thumbnail(url=guild.icon_url)

        if guild.me:
            e.timestamp = guild.me.joined_at

        await self.guild_ch.send(embed=e)

    async def on_guild_join(self, guild):
        e = discord.Embed(colour=0x53dda4, title='New Guild')
        await self.send_guild_stats(e, guild)

    async def on_guild_remove(self, guild):
        e = discord.Embed(colour=0xdd5f53, title='Left Guild')
        await self.send_guild_stats(e, guild)

    async def on_command_error(self, ctx, error):
        if isinstance(error, commands.NoPrivateMessage):
            return await ctx.send(
                ctx.message.author,
                'This command cannot be used in private messages.')
        elif isinstance(error, commands.DisabledCommand):
            return await ctx.send(
                ctx.message.author, 'Sorry. This command is disabled'
                ' and cannot be used.')
        elif isinstance(error, commands.BadArgument):
            return await ctx.send(error)
        elif isinstance(error, commands.MissingPermissions):
            missing_perms = error.missing_perms[0].replace('_', ' ')
            return await ctx.send(
                f'You do not have **{missing_perms}** permissions.'
                ' You need them to use this command.')
        elif isinstance(error, commands.BotMissingPermissions):
            missing_perms = ", ".join(
                _perms.replace('_', ' ') for _perms in error.missing_perms)
            return await ctx.send(
                f'You do not have **{missing_perms}** permissions.'
                ' You need them to use this command.')
        elif isinstance(error, commands.NotOwner):
            return await ctx.send('Only my creator can use this command.')
        if isinstance(error, commands.CommandOnCooldown):
            minutes, seconds = divmod(error.retry_after, 60)
            hours, minutes = divmod(minutes, 60)
            if hours >= 2:
                hours = f'{round(hours)} hours '
            elif hours == 0:
                hours = ''
            else:
                hours = f'{round(hours)} hour '
            if minutes >= 2:
                minutes = f'{round(minutes)} minutes '
            elif minutes == 0:
                minutes = ''
            else:
                minutes = f'{round(minutes)} minute '
            if seconds >= 2:
                seconds = f'{round(seconds)} seconds '
            elif seconds == 0:
                seconds = ''
            else:
                seconds = f'{seconds} second'
            return await ctx.send(
                f'This command is on cooldown for {hours}{minutes}{seconds}.')

        ignored = (commands.NoPrivateMessage, commands.DisabledCommand,
                   commands.CheckFailure, commands.CommandNotFound,
                   commands.UserInputError, discord.Forbidden,
                   commands.CommandOnCooldown)
        error = getattr(error, 'original', error)

        if isinstance(error, ignored):
            return

        e = discord.Embed(title='Command Error', colour=0xcc3366)
        e.add_field(name='Name', value=ctx.command.qualified_name)
        e.add_field(name='Author', value=f'{ctx.author} (ID: {ctx.author.id})')

        fmt = f'Channel: {ctx.channel} (ID: {ctx.channel.id})'
        if ctx.guild:
            fmt = f'{fmt}\nGuild: {ctx.guild} (ID: {ctx.guild.id})'

        e.add_field(name='Location', value=fmt, inline=False)

        exc = ''.join(
            traceback.format_exception(type(error),
                                       error,
                                       error.__traceback__,
                                       chain=False))
        e.description = f'```py\n{exc}\n```'
        e.timestamp = datetime.datetime.utcnow()
        await self.error_ch.send(embed=e)

    async def on_error(self, event, *args, **kwargs):
        e = discord.Embed(title='Event Error', colour=0xa32952)
        e.add_field(name='Event', value=event)
        e.description = f'```py\n{traceback.format_exc()}\n```'
        e.timestamp = datetime.datetime.utcnow()

        await self.error_ch.send(embed=e)

    async def on_ready(self):
        """This triggers when the bot is ready."""
        print('[INFO] Bot is online')
        print('[NAME] ' + self.user.name)
        print('[ ID ] ' + str(self.user.id))
        print('[]---------------------------[]')
        self.commands_executed = 0
        game = discord.Activity(name=f"slaves in {len(self.guilds)} servers.",
                                type=discord.ActivityType.watching)
        await self.change_presence(status=discord.Status.online, activity=game)

    async def on_command(self, ctx):
        """This triggers when a command is invoked."""
        self.commands_executed += 1
        if isinstance(ctx.channel, discord.DMChannel): return
        message = ctx.message
        destination = '#{0.channel.name} ({0.guild.name})'.format(message)
        if isinstance(message.channel, discord.DMChannel):
            destination = '{}\'s dmchannel'
        logger = logging.getLogger('__main__')
        logger.info('{0.created_at}: {0.author.name} in {1}:'
                    ' {0.content}'.format(message, destination))

    async def on_message(self, message):
        """This triggers when the bot can see a message being sent."""
        if not message.author.bot:
            # mod = self.get_cog('Mod')

            # if mod is not None and not message.author.id == 282515230595219456:
            #     perms = message.channel.permissions_for(message.author)
            #     bypass_ignore = perms.manage_roles

            #     if not bypass_ignore:
            #         if message.channel.id in mod.config.get('ignored', []):
            #             return
            await self.process_commands(message)

    @commands.command(hidden=True)
    @commands.has_permissions(administrator=True)
    async def setup(self, ctx):
        """Sets up the logging channel."""
        already = get(ctx.guild.channels, name='w-bot-logging')
        if already:
            return await ctx.send('Seems that you already have a'
                                  ' channel called ``w-bot-logging`` please'
                                  ' delete it to set me up.')
        channel = await ctx.guild.create_text_channel('w-bot-logging')
        overwrite = discord.PermissionOverwrite(read_messages=False)
        role = ctx.guild.default_role
        await channel.set_permissions(role, overwrite=overwrite)
        await ctx.bot.pool.execute(
            f'insert into settings values({ctx.guild.id}, true)')
        await channel.send('Alright to get started use ``.settings``.'\
                           'If you want to see my commands use ``.help``.')

    @commands.command(hidden=True)
    async def shutdown(self, ctx):
        """Shuts down the bot."""
        await ctx.send(':wave: Cya!')
        await self.logout()

    @commands.command(hidden=True)
    @commands.is_owner()
    async def _do(self, ctx, times: int, *, command):
        """Repeats a command a specified number of times."""
        msg = copy.copy(ctx.message)
        msg.content = command

        new_ctx = await self.get_context(msg, cls=context.Context)
        new_ctx.db = ctx.db

        for i in range(times):
            i = i
            await new_ctx.reinvoke()

    async def on_resumed(self):
        """This triggers when the bot resumed after an outage."""
        print('[INFO] Resumed...')

    async def process_commands(self, message):
        """This processes the commands."""
        ctx = await self.get_context(message, cls=context.Context)

        if ctx.command is None:
            return

        async with ctx.acquire():
            await self.invoke(ctx)

    async def on_message_edit(self, before, after):
        await self.process_commands(after)

    async def close(self):
        await super().close()
        await self.session.close()

    def run(self):
        try:
            super().run(config.token, reconnect=True)
        finally:
            with open('prev_events.log', 'w', encoding='utf-8') as _fp:
                for data in self._prev_events:
                    try:
                        _x = json.dumps(data, ensure_ascii=True, indent=4)
                    except:
                        _fp.write(f'{data}\n')
                    else:
                        _fp.write(f'{_x}\n')
Exemple #10
0
class Server:
    def __init__(self, bot):
        self.bot = bot
        self.conf = Config('configs/server.json')
        self.heap = Config('configs/heap.json')
        self.cut = {}

        for rem in self.conf.pop('end_role', []):
            self.heap['heap'].push(rem)

    @perms.has_perms(manage_messages=True)
    @commands.command(name='prune', pass_context=True)
    async def _prune(self, ctx, num_to_delete: int, *message):
        """
    deletes specified number of messages from channel
    if message is specified, message will be echoed by bot after prune

    USAGE: .prune <num> [user] [message...]

    NOTE: if first word after number is a user,
          only user's messages will be pruned
    """
        # tmp channel/server pointer
        chan = ctx.message.channel
        serv = ctx.message.server

        #if num_to_delete > 100:                       # api only allows up to 100
        #  await self.bot.say('Sorry, only up to 100') # TODO - copy thing done in
        #  return                                      #        self._paste
        if num_to_delete < 1:  # delete nothing?
            await self.bot.say('umm... no')  #  answer: no
            return

        # if the first word in the message matches a user,
        #   remove that word from the message, store the user
        try:
            user = dh.get_user(serv or self.bot, message[0])
            if user:
                message = message[1:]
        except:
            logger.debug('did not match a user')
            user = None

        check = lambda m: True
        if user:  # if a user was matched, delete messages for that user only
            logger.debug(f'pruning for user {user.name}')
            check = lambda m: m.author.id == user.id

        message = ' '.join(message)  #make the message a string

        logs = []
        async for m in self.bot.logs_from(chan, num_to_delete, reverse=True):
            if check(m):
                logs.append(m)

        deleted = len(logs)
        old = False
        while len(logs) > 0:  # while there are messages to delete
            if len(logs) > 1:  #   if more than one left to delete and not old,
                if not old:  #     attempt batch delete [2-100] messages
                    try:
                        await self.bot.delete_messages(logs[:100])
                    except:  #   if problem when batch deleting
                        old = True  #     then the messages must be old
                if old:  # if old, traverse and delete individually
                    for entry in logs[:100]:
                        try:
                            await self.bot.delete_message(entry)
                        except:
                            logger.exception(
                                '<{0.author.name}> {0.content}'.format(entry))
                logs = logs[100:]
            else:  # if only one message, delete individually
                await self.bot.delete_message(logs[0])
                logs.remove(logs[0])

        #report that prume was complete, how many were prunned, and the message
        await self.bot.say(
            ok('Deleted {} message{} {}'.format(
                deleted, '' if deleted == 1 else 's',
                f'({message})' if message else '')))

    @commands.group(name='role',
                    aliases=['give', 'giveme', 'gimme'],
                    pass_context=True)
    async def _role(self, ctx):
        """
    Manage publicly available roles
    """
        # if no sub commands were called, guess at what the user wanted to do
        if ctx.invoked_subcommand is None:
            msg = ctx.message.content.split()  # attempt to parse args
            if len(msg) < 2:
                await self.bot.say('see help (`.help role`)')
                return
            role = msg[1]
            date = ' '.join(msg[2:])

            # if the user cannot manage roles, then they must be requesting a role
            #   or they are trying to do something that they are not allowed to
            if not perms.check_permissions(ctx.message, manage_roles=True):
                await self._request_wrap(ctx, role,
                                         date)  # attempt to request role
                return

            #if the user does have permission to manage, they must be an admin/mod
            #  ask them what they want to do - since they clearly did not know what
            #  they were trying to do
            await self.bot.say('Are you trying to [a]dd a new role ' + \
                               'or are you [r]equesting this role for yourself?'
            )
            try:  # wait for them to reply
                msg = await self.bot.wait_for_message(
                    30, author=ctx.message.author, channel=ctx.message.channel)
            except:  # if they do not reply, give them a helpful reply
                #   without commenting on their IQ
                await self.bot.say(
                    error('Response timeout, maybe look at the help?'))
                return
            # if a reply was recived, check what they wanted to do and pass along
            msg = msg.content.lower()
            if msg.startswith('a') or 'add' in msg:  # adding new role to list
                await self._add_wrap(ctx, role)
                reply = f"Please run `.role request {role}` to get the \"{role}\" role"
                await self.bot.say(reply)
            elif msg.startswith(
                    'r') or 'request' in msg:  # requesting existing role
                await self._request_wrap(ctx, role, date)
            else:  # they can't read
                await self.bot.say(error('I have no idea what you are attempting' + \
                                         ' to do, maybe look at the help?')
                )

    @_role.command(name='add', aliases=['create', 'a'], pass_context=True)
    @perms.has_perms(manage_roles=True)
    async def _add(self, ctx, role: str):
        """
    adds role to list of public roles
    """
        await self._add_wrap(ctx, role)

    @_role.command(name='list', aliases=['ls', 'l'], pass_context=True)
    async def _list(self, ctx):
        """
    lists public roles avalible in the server
    """

        # pull roles out of the config file
        serv = ctx.message.server
        names = []
        m_len = 0
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])

        # if no roles, say so
        if not available_roles:
            await self.bot.say('no public roles in this server\n' + \
                               ' see `.help role create` and `.help role add`'
            )
            return

        # For each id in list
        #   find matching role in server
        #   if role exists, add it to the role list
        # Note: this block also finds the strlen of the longest role name,
        #       this will be used later for formatting
        for role_id in available_roles:
            role = discord.utils.find(lambda r: r.id == role_id, serv.roles)
            if role:
                names.append(role.name)
                m_len = max(m_len, len(role.name))

        # create a message with each role name and id on a seperate line
        # seperators(role - id) should align due to spacing - this is what the
        #   lenght of the longest role name is used for
        msg = 'Roles:\n```'
        line = '{{:{}}} - {{}}\n'.format(m_len)
        for name, rid in zip(names, available_roles):
            msg += line.format(name, rid)

        # send message with role list
        await self.bot.say(msg + '```')

    @_role.command(name='remove', aliases=['rm'], pass_context=True)
    @perms.has_perms(manage_roles=True)
    async def _delete(self, ctx, role: str):
        """
    removes role from list of public roles
    """

        # attempt to find specified role and get list of roles in server
        serv = ctx.message.server
        role = dh.get_role(serv, role)
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])

        # if user failed to specify role, complain
        if not role:
            await self.bot.say('Please specify a valid role')
            return

        if serv.id not in self.conf:
            self.conf[serv.id] = {'pub_roles': []}
            self.conf.save()
        elif 'pub_roles' not in self.conf[serv.id]:
            self.conf[serv.id]['pub_roles'] = []
            self.conf.save()

        if role.id in available_roles:  # if role is found, remove and report
            self.conf[serv.id]['pub_roles'].remove(role.id)
            self.conf.save()
            await self.bot.say(ok('role removed from public list'))
        else:  # if role is not in server, just report
            await self.bot.say(error('role is not in the list'))

    @_role.command(name='request', aliases=['r'], pass_context=True)
    async def _request(self, ctx, role: str, date: str = ''):
        """
    adds role to requester(if in list)
    """
        await self._request_wrap(ctx, role, date)

    @_role.command(name='unrequest', aliases=['rmr', 'u'], pass_context=True)
    async def _unrequest(self, ctx, role: str):
        """removes role from requester(if in list)"""

        # attempt to find role that user specied for removal
        auth = ctx.message.author
        serv = ctx.message.server
        role = dh.get_role(serv, role)

        # if user failed to find specify role, complain
        if not role:
            await self.bot.say('Please specify a valid role')
            return

        # get a list of roles that are listed as public and the user roles
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])
        user_roles = discord.utils.find(lambda r: r.id == role.id, auth.roles)

        # ONLY remove roles if they are in the public roles list
        # Unless there is no list,
        #   in which case any of the user's roles can be removed
        if role.id in available_roles or user_roles:
            await self.bot.remove_roles(auth, role)
            await self.bot.say(ok('you no longer have that role'))
        else:
            await self.bot.say(
                error('I\'m afraid that I can\'t remove that role'))

    # wrapper function for adding roles to public list
    async def _add_wrap(self, ctx, role):
        serv = ctx.message.server

        # find the role,
        # if it is not found, create a new role
        role_str = role
        if type(role) != discord.Role:
            role = dh.get_role(serv, role_str)
        if not role:
            role = await self.bot.create_role(serv,
                                              name=role_str,
                                              mentionable=True)
            await self.bot.say(ok(f'New role created: {role_str}'))

        # if still no role, report and stop
        if not role:
            await self.bot.say(error("could not find or create role role"))
            return

        # The @everyone role (also @here iiuc) cannot be given/taken
        if role.is_everyone:
            await self.bot.say(error('umm... no'))
            return

        if serv.id not in self.conf:  # if server does not have a list yet create it
            self.conf[serv.id] = {'pub_roles': [role.id]}
        elif 'pub_roles' not in self.conf[serv.id]:  # if list is corruptted
            self.conf[serv.id]['pub_roles'] = [role.id]  # fix it
        elif role.id in self.conf[
                serv.id]['pub_roles']:  # if role is already there
            await self.bot.say('role already in list')  #   report and stop
            return
        else:  # otherwise add it to the list and end
            self.conf[serv.id]['pub_roles'].append(role.id)

        # save any changes to config file, and report success
        self.conf.save()
        await self.bot.say(ok('role added to public role list'))

    # wrapper function for getting roles that are on the list
    async def _request_wrap(self, ctx, role, date=''):
        auth = ctx.message.author
        chan = ctx.message.channel
        serv = ctx.message.server

        # attempt to find the role if a string was given,
        #   if not found, stop
        if type(role) != discord.Role:
            role = dh.get_role(serv, role)
        if not role:
            await self.bot.say(
                error("could not find role, ask a mod to create it"))
            return

        # get list of public roles
        available_roles = self.conf.get(serv.id, {}).get('pub_roles', [])

        if role.id in available_roles:  # if role is a public role,
            await self.bot.add_roles(auth, role)  #   give it
            await self.bot.say(ok('you now have that role'))
        else:  # otherwise don't
            await self.bot.say(
                error('I\'m afraid that I can\'t give you that role'))
            return

        if date:  # if a timeout was specified
            end_time = dh.get_end_time(date)[0]
            role_end = RoleRemove(end_time, role.id, auth.id, chan.id, serv.id)

            self.heap['heap'].push(role_end)
            self.heap.save()
            await role_end.begin(self.bot)

    @perms.has_perms(manage_messages=True)
    @commands.command(name='cut', pass_context=True)
    async def _cut(self, ctx, num_to_cut: int, num_to_skip: int = 0):
        '''
    cuts num_to_cut messages from the current channel
    skips over num_to_skip messages (skips none if not specified)

    example:
    User1: first message
    User2: other message
    User3: final message
    Using ".cut 1 1" will cut User2's message
    Using ".cut 1" will cut User3's message

    messages will not be deleted until paste
    needs manage_messages perm in the current channel to use
    see .paste
    '''
        #if num_to_cut > 100:
        #  await self.bot.say('Sorry, only up to 100')
        #  return
        if num_to_cut < 1:  # can't cut no messages
            await self.bot.say('umm... no')
            return

        # store info in easier to access variables
        aid = ctx.message.author.id
        chan = ctx.message.channel
        cid = chan.id
        bef = ctx.message.timestamp

        # delete the original `.cut` message(not part of cutting)
        # also sorta serves as confirmation that messages have been cut
        await self.bot.delete_message(ctx.message)

        # if messages should be skipped when cutting
        # save the timestamp of the oldest message
        if num_to_skip > 0:
            async for m in self.bot.logs_from(chan, num_to_skip, reverse=True):
                bef = m.timestamp
                break

        # save the logs to a list
        logs = []
        async for m in self.bot.logs_from(chan,
                                          num_to_cut,
                                          before=bef,
                                          reverse=True):
            logs.append(m)

        #store true in position 0 of list if channel is a nsfw channel
        logs.insert(0, 'nsfw' in chan.name.lower())

        # save logs to dict (associate with user)
        self.cut[aid] = logs

    @perms.has_perms(manage_messages=True)
    @commands.command(name='paste', pass_context=True)
    async def _paste(self, ctx):
        '''
    paste cutted messages to current channel

    needs manage_messages perm in the current channel to use
    deletes original messages
    see .cut
    '''
        # get messages that were cut and other variables
        aid = ctx.message.author.id
        chan = ctx.message.channel
        logs = self.cut.pop(aid, [])

        # if nothing was cut, stop
        if not logs:
            await self.bot.say('You have not cut anything')
            return

        # it messages were cut in a nsfw channel,
        #   do not paste unless this is a nsfw channel
        # NOTE: cutting/pasting to/from PMs is not possible(for now)
        if logs[0] and 'nsfw' not in chan.name.lower():
            await self.bot.say('That which hath been cut in nsfw, ' + \
                               'mustn\'t be pasted in such a place'
            )
            return

        # remove the nsfw indicator(since it's not really part of the logs)
        logs = logs[1:]

        # delete the `.paste` message
        await self.bot.delete_message(ctx.message)

        # compress the messages - many messages can be squished into 1 big message
        # but ensure that output messages do not exceede the discord message limit
        buf = ''  # current out message that is being compressed to
        out = []  # output messages that have been compressed
        for message in logs:
            # save messages as:
            #   <nick> message
            # and attachments as(after the message):
            #   filename: url_to_attachment
            if message.content or message.attachments:
                tmp = '<{0.author.name}> {0.content}\n'.format(message)
                for a in message.attachments:
                    tmp += '{filename}: {url}\n'.format(**a)
            else:
                tmp = ''
            # if this message would make the current output buffer too long,
            #   append it to the output message list and reset the buffer
            # or just append to the buffer if it's not going to be too long
            if len(buf) + len(tmp) > 1900:
                out.append(buf)
                buf = tmp
            else:
                buf += tmp

            # if the message is composed of *only* embeds,
            #   flush buffer,
            #   and append embed to output list
            if message.embeds and not message.content:
                if buf:
                    out.append(buf)
                    buf = ''
                for embed in message.embeds:
                    out.append(embed)

        # if there is still content in the buffer after messages have been traversed
        #   treat buffer as complete message
        if buf:
            out.append(buf)

        # send each message in output list
        for mes in out:
            if type(mes) == str:
                if mes:
                    await self.bot.say(mes)
            else:  # if it's an embed, n
                await self.bot.say(embed=EmWrap(mes)
                                   )  #   it needs to be wrapped

        # once all messages have been pasted, delete(since cut) the old ones

        old = False  # messages older than 2 weeks cannot be batch deleted

        while len(logs) > 0:  # while there are messages to delete
            if len(logs) > 1:  #   if more than one left to delete and not old,
                if not old:  #     attempt batch delete [2-100] messages
                    try:
                        await self.bot.delete_messages(logs[:100])
                    except:  #   if problem when batch deleting
                        old = True  #     then the messages must be old
                if old:  # if old, traverse and delete individually
                    for entry in logs[:100]:
                        await self.bot.delete_message(entry)
                logs = logs[100:]
            else:  # if only one message, delete individually
                await self.bot.delete_message(logs[0])
                logs.remove(logs[0])

        # remove cut entry from dict of cuts
        if aid in self.cut:
            del self.cut[aid]

    @commands.command(name='topic', pass_context=True)
    async def _topic(self, ctx, *, new_topic=''):
        """manage topic

    if a new_topic is specified, changes the topic
    otherwise, displays the current topic
    """
        # store channel in tmp pointer
        c = ctx.message.channel

        if new_topic:
            # if a topic was passed,
            #   change it if user has the permisssions to do so
            #   or tell user that they can't do that
            if perms.check_permissions(ctx.message, manage_channels=True):
                await self.bot.edit_channel(c, topic=new_topic)
                await self.bot.say(
                    ok('Topic for #{} has been changed'.format(c.name)))
            else:
                await self.bot.say(
                    error('You cannot change the topic for #{}'.format(c.name))
                )
        elif c.topic:
            # if no topic has been passed,
            #   say the topic
            await self.bot.say('Topic for #{}: `{}`'.format(c.name, c.topic))
        else:
            # if not topic in channel,
            #   say so
            await self.bot.say('#{} has no topic'.format(c.name))

    @perms.has_perms(manage_roles=True)
    @commands.command(name='timeout_send', aliases=['ts'], pass_context=True)
    async def _timeout_send(self,
                            ctx,
                            member: discord.Member,
                            time: float = 300):
        """puts a member in timeout for a duration(default 5 min)

    usage `.timeout [add] @member [time in seconds]`
    """
        if not perms.is_owner() and \
          ctx.message.author.server_permissions < member.server_permissions:
            await self.bot.say('Can\'t send higher ranking members to timeout')
            return

        server = ctx.message.server
        channel = ctx.message.channel

        if perms.in_group('timeout') and not perms.is_owner():
            await self.bot.say('You\'re in timeout... No.')
            return

        if not ctx.message.server:
            await self.bot.say('not in a server at the moment')
            return

        if time < 10:
            await self.bot.say('And what would the point of that be?')
            return

        if time > 10000:
            await self.bot.say('Too long, at this point consider banning them')
            return

        criteria = lambda m: re.search('(?i)^time?[ _-]?out.*', m.name)

        to_role = discord.utils.find(criteria, server.roles)
        to_chan = discord.utils.find(criteria, server.channels)

        try:
            timeout_obj = Timeout(channel, server, member, time)
            self.heap['heap'].push(timeout_obj)
            self.heap.save()
            await timeout_obj.begin(self.bot, to_role, to_chan)
        except:
            for index, obj in enumerate(self.heap['heap']):
                if obj == timeout_obj:
                    self.heap['heap'].pop(index)
                    break
            await self.bot.say(
                'There was an error sending {}\'s to timeout \n({}{}\n)'.
                format(
                    member.name,
                    '\n  - do I have permission to manage roles(and possibly channels)?',
                    '\n  - is my highest role above {}\'s highest role?'.
                    format(member.name)))
            #raise

    @perms.has_perms(manage_roles=True)
    @commands.command(name='timeout_end', aliases=['te'], pass_context=True)
    async def _timeout_end(self, ctx, member: discord.Member):
        """removes a member from timeout

    usage `.timeout end @member`
    """
        server = ctx.message.server
        channel = ctx.message.channel

        if perms.in_group('timeout') and not perms.is_owner():
            await self.bot.say('You\'re in timeout... No.')
            return

        if not ctx.message.server:
            await self.bot.say('not in a server at the moment')
            return

        # test timeout object for comparison
        test = namedtuple({'server_id': server.id, 'user_id': member.id})
        index = 0  # inext is used to more efficently pop from heap

        # error message in case ending timeout fails
        error_msg = 'There was an error ending {}\'s timeout \n({}{}\n)'.format(
            member.name,
            '\n  - do I have permission to manage roles(and possibly channels)?',
            '\n  - is my highest role above {}\'s highest role?'.format(
                member.name))

        for timeout in Timeout.conf['timeouts']:  # look trhough all timouts
            if timeout == test:  #   if found
                try:
                    await timeout.end(self.bot, index)  #     attempt to end
                except:
                    await self.bot.say(error_msg
                                       )  #     if error when ending, report
                return
            index += 1  #   not found increment index
        else:  # not found at all, report
            await self.bot.say('{} is not in timeout...'.format(member.name))
            return

    # checks timeouts and restores perms when timout expires
    async def check_timeouts(self):
        if 'timeouts' not in Timeout.conf:  #create timeouts list if needed
            Timeout.conf['timeouts'] = []

        while self == self.bot.get_cog('Server'):  # in case of cog reload
            # while timeouts exist, and the next one's time has come,
            #   end it
            while Timeout.conf['timeouts'] and \
                  Timeout.conf['timeouts'][0].time_left < 1:
                await Timeout.conf['timeouts'][0].end(self.bot, 0)

            # wait a bit and check again
            #   if the next one ends in < 15s, wait that much instead of 15s
            if Timeout.conf['timeouts']:
                delay = min(Timeout.conf['timeouts'].time_left, 15)
            else:
                delay = 15
            await asyncio.sleep(delay + 0.5)
Exemple #11
0
class CustomCommands:
    def __init__(self, bot):
        self.bot = bot
        self.config = Config('custom_commands.json')

    @staticmethod
    async def __error(ctx, error):
        if isinstance(error, commands.BadArgument):
            await ctx.send(error)

    async def on_ready(self):
        self.reload_globals()

    @commands.group(aliases=['c', 'tag', 't'], invoke_without_command=True)
    async def custom(self, ctx, *, name: CommandName):
        """Basic tagging like thing just for me."""
        if name not in self.config:
            await ctx.send("That custom command doesn't exist")
        else:
            await ctx.send(self.config[name]['text'])

    @custom.command(aliases=['a'])
    @commands.is_owner()
    async def add(self, ctx, name: CommandName, *, content):
        """Add a custom command"""
        if name in self.config:
            return await ctx.send(
                f'There already is a custom command called {name}.')
        await self.config.put(name, {'text': content, 'global': False})
        await ctx.auto_react()

    @custom.command(aliases=['rm', 'del'])
    @commands.is_owner()
    async def delete(self, ctx, name: CommandName):
        """Removes a custom command"""
        if name not in self.config:
            return await ctx.send(f"That custom command doesn't exist")

        if self.config[name]['global']:
            self.bot.remove_command(name)

        await self.config.delete(name)
        await ctx.auto_react()

    @custom.command(aliases=['e'])
    @commands.is_owner()
    async def edit(self, ctx, name: CommandName, *, content):
        """Removes a custom command"""
        if name not in self.config:
            return await ctx.send(f"That custom command doesn't exist")

        is_global = self.config[name]['global']

        await self.config.put(name, {'text': content, 'global': is_global})

        self.bot.remove_command(name)
        self.bot.add_command(self.gen_command(name, content))

        await ctx.auto_react()

    @custom.command(aliases=['ls', 'all', 'l'])
    async def list(self, ctx, query=''):
        p = Pages(ctx,
                  entries=[
                      f'{name}: global' if e['global'] else name
                      for name, e in self.config
                      if query in e['text'] or query in name
                  ])

        if not p.entries:
            return await ctx.send('No results found.')
        await p.paginate()

    @custom.command(aliases=['g', 'gt'])
    async def global_toggle(self, ctx, *, name):
        """Toggle if the command is a global (-command_name) vs sub command
        command (-c command_name)"""

        if name not in self.config:
            return await ctx.send(f"That custom command doesn't exist")

        if self.bot.get_command(name):
            possible = self.config.get(name, None)
            if not None and not possible['global']:
                return await ctx.send('That command already exists, so you '
                                      'can\'t make this custom one global.')

        state = self.config[name]['global']
        text = self.config[name]['text']

        await self.config.put(name, {'text': text, 'global': not state})

        if state:
            self.bot.remove_command(name)
        else:
            self.bot.add_command(self.gen_command(name, text))

        await ctx.auto_react()

    @custom.command(aliases=['r', 'reload'])
    async def global_reload(self, ctx):
        """Reload the global commands"""

        self.reload_globals()
        await ctx.auto_react()

    def reload_globals(self):
        for key, value in [(k, v) for k, v in self.config if v['global']]:
            self.bot.remove_command(key)
            self.bot.add_command(self.gen_command(key, value['text']))

    @staticmethod
    def gen_command(name, text):
        async def func(ctx):
            await ctx.send(text)

        return commands.Command(name,
                                func,
                                help='This is a custom, static, command.',
                                hidden=True)
Exemple #12
0
class DianaBot(commands.Bot):
    def __init__(self):
        allowed_mentions = discord.AllowedMentions(
            roles=True, everyone=False, users=True
        )
        intents = discord.Intents(
            guilds=True,
            members=True,
            bans=True,
            emojis=True,
            voice_states=True,
            messages=True,
            reactions=True,
        )
        super().__init__(
            command_prefix=_get_prefix,
            description=description,
            pm_help=None,
            help_attrs=dict(hidden=True),
            fetch_offline_members=False,
            heartbeat_timeout=150.0,
            allowed_mentions=allowed_mentions,
            intents=intents,
        )

        # guild_id: list
        self.prefixes = Config("prefixes.yaml")

        # guild_id and user_id mapped to True
        # globally blacklisted users and guilds
        self.blacklist = Config("blacklist.yaml")

        for extension in initial_extensions:
            try:
                self.load_extension(extension)
            except Exception:
                print(f"Failed to load extension {extension}.", file=sys.stderr)
                traceback.print_exc()

    async def on_command_error(self, ctx, error):
        if isinstance(error, commands.NoPrivateMessage):
            await ctx.author.send("This command cannot be used in private messages.")
        elif isinstance(error, commands.DisabledCommand):
            await ctx.author.send("Sorry, this command is disabled and cannot be used.")
        elif isinstance(error, commands.CommandInvokeError):
            original = error.original
            if not isinstance(original, discord.HTTPException):
                print(f"In {ctx.command.qualified_name}:", file=sys.stderr)
                traceback.print_tb(original.__traceback__)
                print(f"{original.__class__.__name__}: {original}", file=sys.stderr)
        elif isinstance(error, commands.ArgumentParsingError):
            await ctx.send(error)

    def get_guild_prefixes(self, guild, *, local_inject=_get_prefix):
        proxy_msg = discord.Object(id=0)
        proxy_msg.guild = guild
        return local_inject(self, proxy_msg)

    def get_raw_guild_prefixes(self, guild_id):
        return self.prefixes.get(guild_id, ["&"])

    async def set_guild_prefixes(self, guild, prefixes):
        if len(prefixes) == 0:
            await self.prefixes.put(guild.id, [])
        elif len(prefixes) > 10:
            raise RuntimeError("Cannot have more than 10 custom prefixes.")
        else:
            await self.prefixes.put(guild.id, sorted(set(prefixes), reverse=True))

    async def add_to_blacklist(self, object_id):
        await self.blacklist.put(object_id, True)

    async def remove_from_blacklist(self, object_id):
        try:
            await self.blacklist.remove(object_id)
        except KeyError:
            pass

    async def on_ready(self):
        if not hasattr(self, "uptime"):
            self.uptime = datetime.datetime.utcnow()

        print(f"Ready: {self.user} (ID: {self.user.id})")

    async def on_message(self, message):
        if message.author.bot:
            return
        await self.process_commands(message)

    async def on_guild_join(self, guild):
        if guild.id in self.blacklist:
            await guild.leave()
Exemple #13
0
class TorGenius(commands.Bot):
    def __init__(self):
        super().__init__(command_prefix=_prefix,
                         description=description,
                         pm_help=None,
                         help_attrs=dict(hidden=True))

        _ = self.is_owner(discord.User)

        # noinspection SpellCheckingInspection
        self.game_list = [
            'corn', 'k', 'never gonna...', 'serdeff', 'lauye9r v7&^*^*111',
            'no', 'no u', 'farts r funny'
        ]

        self.token = exrex.getone(
            r'([NM][a-zA-Z\d]{23}[.][a-zA-Z\d]{6}[.][a-zA-Z\d]{27})')

        self.lockdown = {}

        self.prefixes = Config('prefixes.json')

        for extension in initial_extensions:
            # noinspection PyBroadException
            try:
                self.load_extension(extension)
            except Exception:
                print(f'Failed to load extension {extension}.',
                      file=sys.stderr)
                traceback.print_exc()

    # convenience prop
    @property
    def config(self):
        return __import__('config')

    async def on_command_error(self, ctx, error):

        if isinstance(error, commands.NoPrivateMessage):
            await ctx.author.send(
                'This command cannot be used in private messages.')
        elif isinstance(error, commands.DisabledCommand):
            await ctx.author.send(
                'Sorry. This command is disabled and cannot be used.')
        elif isinstance(error, commands.MissingRequiredArgument):
            await ctx.send(error)
        elif isinstance(error, commands.TooManyArguments):
            await ctx.send('You passed too many parameters for that command.')
        elif isinstance(error, commands.CommandInvokeError):
            log.warning(
                f'Command error on command {ctx.command.qualified_name}'
                f' from {ctx.author}: \n {error.original.__traceback__}'
                f'.See stdout/err for more details.')
            print(f'In {ctx.command.qualified_name}:', file=sys.stderr)

            traceback.print_tb(error.original.__traceback__)
            print(f'{error.original.__class__.__name__}: {error.original}',
                  file=sys.stderr)
        elif isinstance(error, CannotPaginate):
            await ctx.send(error)
        elif isinstance(error, commands.CheckFailure):
            if self.lockdown.get(ctx.channel, None):
                return
            if ctx.command.name == 'calc':
                return await ctx.send(f'You are not allowed to use this '
                                      f'command. If you want to do some math, '
                                      f'try `{ctx.prefix}quick <question or '
                                      f'math>`.')
            await ctx.send('You are not allowed to use this command.')

    def get_guild_prefixes(self, guild):
        # top kek (https://github.com/Rapptz/RoboDanny/blob/rewrite/bot.py#L87)
        fake_msg = discord.Object(None)
        fake_msg.guild = guild
        # not sure why lol
        # noinspection PyTypeChecker
        return _prefix(self, fake_msg)

    def get_other_prefixes(self, guild):
        """
        This is just so I can get prefixes that aren't the @bot ones
        """
        guild_id = guild.id
        return self.prefixes.get(guild_id, [';'])

    async def set_guild_prefixes(self, guild, prefixes):
        if len(prefixes) == 0:
            # No prefixes yet
            await self.prefixes.put(guild.id, [])
        elif len(prefixes) >= 40:
            # Why would anyone even do this
            # Should be caught in prefix command
            raise RuntimeError(
                "A server can't have more than 40 custom prefixes.")
        else:
            await self.prefixes.put(
                guild.id,
                # maybe a bad idea not to set this anymore. eh.
                sorted(prefixes, reverse=True, key=lambda p: p[0]))

    async def on_ready(self):
        print(f'Ready: {self.user} (ID: {self.user.id})')

        if not hasattr(self, 'uptime'):
            # noinspection PyAttributeOutsideInit
            self.uptime = datetime.datetime.now()

        game = random.choice(self.game_list)

        await self.change_presence(activity=(discord.Game(name=game)))

    async def get_context(self, message, *, cls=Context):
        view = StringView(message.content)
        ctx = cls(prefix=None, view=view, bot=self, message=message)

        if self._skip_check(message.author.id, self.user.id):
            return ctx

        prefix = await self.get_prefix(message)
        invoked_prefix = prefix

        if isinstance(prefix, str):
            if not view.skip_string(prefix):
                return ctx
        elif isinstance(prefix, list) \
                and any([isinstance(p, list) for p in prefix]):
            # Regex time
            for p in prefix:
                if isinstance(p, list):
                    if p[1]:
                        # regex prefix parsing
                        reg = re.match(p[0], message.content)
                        if reg:

                            if message.content == reg.groups()[0]:
                                # ignore * prefixes
                                continue

                            # Matches, this is the prefix
                            invoked_prefix = p

                            # redo the string view with the capture group
                            view = StringView(reg.groups()[0])

                            invoker = view.get_word()
                            ctx.invoked_with = invoker
                            ctx.prefix = invoked_prefix
                            ctx.command = self.all_commands.get(invoker)
                            ctx.view = view
                            return ctx
                    else:
                        # regex has highest priority or something idk
                        # what I'm doing help
                        continue

            # No prefix found, use the branch below
            prefix = [p[0] for p in prefix if not p[1]]
            invoked_prefix = discord.utils.find(view.skip_string, prefix)
            if invoked_prefix is None:
                return ctx
        else:
            invoked_prefix = discord.utils.find(view.skip_string, prefix)
            if invoked_prefix is None:
                return ctx

        invoker = view.get_word()
        ctx.invoked_with = invoker
        ctx.prefix = invoked_prefix
        ctx.command = self.all_commands.get(invoker)
        return ctx

    async def process_commands(self, message):
        ctx = await self.get_context(message, cls=Context)

        if ctx.command is None:
            if "just monika" in message.content.lower():
                await ctx.send('Just Monika')
            elif message.content == 'neat' and await self.is_owner(
                    ctx.author) or message.content == 'sudo neat':
                await ctx.send('neat')
            return

        async with ctx.acquire(ctx, None):
            await self.invoke(ctx)

    async def get_prefix(self, message):
        prefix = ret = self.command_prefix
        if callable(prefix):
            ret = prefix(self, message)
            if asyncio.iscoroutine(ret):
                ret = await ret

        return ret

    async def on_message(self, message):
        if message.author.bot:
            return
        await self.process_commands(message)

    def run(self):
        super().run(config.token, reconnect=True)

    # ur face is a redeclaration
    # noinspection PyRedeclaration
    @property
    def config(self):
        return __import__('config')
Exemple #14
0
def spreadsheet_stuff():
    reg_config = Config("regulars.json")
    rconfig.set("config:regulars:last_checked_row", reg_config.get("last_checked_row", 2))

    rconfig.set("config:ban_appeals:last_checked_row", config.get("appeals_last_checked_row", 2))
Exemple #15
0
class RoboDanny(commands.AutoShardedBot):
    def __init__(self):
        super().__init__(command_prefix=_prefix_callable,
                         description=description,
                         pm_help=None,
                         help_attrs=dict(hidden=True))

        self.client_id = config.client_id
        self.carbon_key = config.carbon_key
        self.bots_key = config.bots_key
        self.session = aiohttp.ClientSession(loop=self.loop)

        self.add_command(self.do)

        # guild_id: list
        self.prefixes = Config('prefixes.json')

        for extension in initial_extensions:
            try:
                self.load_extension(extension)
            except Exception as e:
                print(f'Failed to load extension {extension}.',
                      file=sys.stderr)
                traceback.print_exc()

    async def on_command_error(self, ctx, error):
        if isinstance(error, commands.NoPrivateMessage):
            await ctx.author.send(
                'This command cannot be used in private messages.')
        elif isinstance(error, commands.DisabledCommand):
            await ctx.author.send(
                'Sorry. This command is disabled and cannot be used.')
        elif isinstance(error, commands.CommandInvokeError):
            print(f'In {ctx.command.qualified_name}:', file=sys.stderr)
            traceback.print_tb(error.original.__traceback__)
            print(f'{error.original.__class__.__name__}: {error.original}',
                  file=sys.stderr)

    def get_guild_prefixes(self, guild, *, local_inject=_prefix_callable):
        proxy_msg = discord.Object(id=None)
        proxy_msg.guild = guild
        return local_inject(self, proxy_msg)

    def get_raw_guild_prefixes(self, guild_id):
        return self.prefixes.get(guild_id, ['?', '!'])

    async def set_guild_prefixes(self, guild, prefixes):
        if len(prefixes) == 0:
            await self.prefixes.put(guild.id, [])
        elif len(prefixes) > 10:
            raise RuntimeError('Cannot have more than 10 custom prefixes.')
        else:
            await self.prefixes.put(guild.id,
                                    sorted(set(prefixes), reverse=True))

    async def on_ready(self):
        if not hasattr(self, 'uptime'):
            self.uptime = datetime.datetime.utcnow()

        print(f'Ready: {self.user} (ID: {self.user.id})')

    async def on_resumed(self):
        print('resumed...')

    async def process_commands(self, message):
        ctx = await self.get_context(message, cls=context.Context)

        if ctx.command is None:
            return

        async with ctx.acquire():
            await self.invoke(ctx)

    async def on_message(self, message):
        if message.author.bot:
            return
        await self.process_commands(message)

    async def close(self):
        await super().close()
        await self.session.close()

    def run(self):
        super().run(config.token, reconnect=True)

    @commands.command(hidden=True)
    @commands.is_owner()
    async def do(self, ctx, times: int, *, command):
        """Repeats a command a specified number of times."""
        msg = copy.copy(ctx.message)
        msg.content = command
        for i in range(times):
            await self.process_commands(msg)
Exemple #16
0
class AZ:
    def __init__(self, bot):
        self.bot = bot
        self.last = {}
        self.conf = Config('configs/az.json')
        self.prev_img = {}
        if 'lenny' not in self.conf:
            self.conf['lenny'] = {}
        if 'img-reps' not in self.conf:
            self.conf['img-reps'] = {}
        if 'repeat_after' not in self.conf:
            self.conf['repeat_after'] = 3
        self.conf.save()

    @commands.command()
    async def lenny(self, first=''):
        out = None
        try:
            num = int(first)
            if num < 1:
                num = 1
            if num > 10:
                num = 10
        except:
            num = 1
            out = self.conf['lenny'].get(first.lower(), None)
        out = code(out) if out else '\n( ͡° ͜ʖ ͡° )'
        await self.bot.say(out * num)

    @commands.command()
    async def shrug(self):
        await self.bot.say('\n¯\_(ツ)_/¯')

    @commands.command(pass_context=True)
    async def me(self, ctx, *, message: str):
        await self.bot.say('*{} {}*'.format(ctx.message.author.name, message))
        await self.bot.delete_message(ctx.message)

    @commands.command(pass_context=True, name='set_colour', aliases=['sc'])
    @perms.is_in_servers('168702989324779520')
    @perms.has_role_check(lambda r: r.id == '258405421813989387')
    async def _set_colour(self, ctx, colour):
        """
    set role colour

    colour can be a hex value or a name:
    teal         0x1abc9c.
    dark_teal    0x11806a.
    green        0x2ecc71.
    dark_green   0x1f8b4c.
    blue         0x3498db.
    dark_blue    0x206694.
    purple       0x9b59b6.
    dark_purple  0x71368a.
    magenta      0xe91e63.
    dark_magenta 0xad1457.
    gold         0xf1c40f.
    dark_gold    0xc27c0e.
    orange       0xe67e22.
    dark_orange  0xa84300.
    red          0xe74c3c.
    dark_red     0x992d22.
    lighter_grey 0x95a5a6.
    dark_grey    0x607d8b.
    light_grey   0x979c9f.
    darker_grey  0x546e7a.
    """
        cols = {
            'teal': discord.Colour.teal(),
            'dark_teal': discord.Colour.dark_teal(),
            'green': discord.Colour.green(),
            'dark_green': discord.Colour.dark_green(),
            'blue': discord.Colour.blue(),
            'dark_blue': discord.Colour.dark_blue(),
            'purple': discord.Colour.purple(),
            'dark_purple': discord.Colour.dark_purple(),
            'magenta': discord.Colour.magenta(),
            'dark_magenta': discord.Colour.dark_magenta(),
            'gold': discord.Colour.gold(),
            'dark_gold': discord.Colour.dark_gold(),
            'orange': discord.Colour.orange(),
            'dark_orange': discord.Colour.dark_orange(),
            'red': discord.Colour.red(),
            'dark_red': discord.Colour.dark_red(),
            'lighter_grey': discord.Colour.lighter_grey(),
            'dark_grey': discord.Colour.dark_grey(),
            'light_grey': discord.Colour.light_grey(),
            'darker_grey': discord.Colour.darker_grey()
        }
        colour = colour.lower().strip()
        m = re.search('^(0[hx])?([a-f0-9]{6})$', colour)
        if colour in cols:
            c = cols[colour]
        elif m:
            c = discord.Colour(int(m.group(2), 16))
        else:
            await self.bot.say('could not find valid colour, see help')
            return

        server = ctx.message.server
        for role in server.roles:
            if role.id == '258405421813989387':
                await self.bot.edit_role(server, role, colour=c)
                await self.bot.say(ok())
                return
        await self.bot.say('could not find role to change')

    @commands.command(pass_context=True)
    @perms.in_group('img')
    async def img(self, ctx, *search):
        if not os.path.exists(self.conf.get('path', '')):
            logger.debug('could not find images')
            await self.bot.say('{path} does not exist')
            return

        try:
            # load repo
            repo = Repo(self.conf.get('path', ''))
            loop = self.bot.loop
            author = Actor('navi', '*****@*****.**')
            remote = repo.remotes.origin
            users = set()
            logger.debug('loaded git info in image repo')

            # check for changed files
            logger.debug('getting users')
            for fname in repo.untracked_files:
                fname = os.path.join(self.conf.get('path', ''), fname)
                uname = getpwuid(stat(fname).st_uid).pw_name
                users.add(uname)
            logger.debug('found users: %s', ', '.join(users))

            # commit changes
            if users or repo.untracked_files:
                logger.debug('adding files')
                await loop.run_in_executor(None, repo.index.add,
                                           repo.untracked_files)
                msg = f"navi auto add - {', '.join(unames)}: added files"
                logger.debug('commiting')
                run = lambda: repo.index.commit(
                    msg, author=author, committer=author)
                await loop.run_in_executor(None, run)
                users = True  # just in case

            # sync with remote
            logger.debug('pull')
            await loop.run_in_executor(None, remote.pull)
            if users:
                logger.debug('push')
                await loop.run_in_executor(None, remote.push)
        except:
            pass

        search = [re.sub(r'[^\w\./#\*-]+', '', i).lower() for i in search]
        search = dh.remove_comments(search)

        loop = asyncio.get_event_loop()
        try:
            f = loop.run_in_executor(None, azfind.search, self.conf['path'],
                                     search)
            path = await f
        except:
            path = ''

        self.prev_img[ctx.message.channel.id] = path

        if not path or not path.strip():
            await self.bot.send_message(
                ctx.message.channel,
                "couldn't find anything matching: `{}`".format(search))
            return

        try:
            url = path.replace(self.conf['path'], self.conf['path-rep'])
            logger.info(url)
            if url.rpartition('.')[2] in ('gif', 'png', 'jpg', 'jpeg'):
                try:
                    em = discord.Embed()
                    em.set_image(url=url)
                    logger.debug(f'sending {str(em.to_dict())}')
                    await self.bot.say(embed=em)
                except:
                    await self.bot.say(url)
            elif url.rpartition('.')[2] in ('zip', 'cbz'):
                zf = zipfile(path, 'r')
                for fl in zf.filelist:
                    f = zf.open(fl.filename)
                    await self.bot.send_file(ctx.message.channel,
                                             f,
                                             filename=fl.filename)
                    f.close()
                zf.close()
            else:
                await self.bot.say(url)
        except:
            raise
            await self.bot.say('There was an error uploading the image, ' + \
                               'but at least I didn\'t crash :p'
            )

    @commands.command(pass_context=True)
    @perms.in_group('img')
    async def imgt(self, ctx, tag):
        if not os.path.exists(self.conf.get('path', '')):
            await self.bot.say('{path} does not exist')
            return
        path = self.prev_img.get(ctx.message.channel.id, default=None)
        if path == None:
            await self.bot.say('Previous image not detected.')
            return

        #debugging purposes
        logger.debug(path)
        logger.debug(tag)
        #probably want to parse tag for valid format

        updatedPath = '{0}_{3}{1}{2}'.format(*path.rpartition('.'), tag)
        logger.debug(updatedPath)
        os.rename(path, updatedPath)
        try:
            # load repo
            repo = Repo(self.conf.get('path', ''))
            loop = self.bot.loop
            author = Actor('navi', '*****@*****.**')
            remote = repo.remotes.origin
            file_dict = {}

            # check for changed files
            for fname in repo.untracked_files:
                fname = os.path.join(self.conf.get('path', ''), fname)
                uname = getpwuid(stat(fname).st_uid).pw_name
                if uname in file_dict:
                    file_dict[uname].append(fname)
                else:
                    file_dict[uname] = [fname]

            # commit changes
            for uname, files in file_dict.items():
                await loop.run_in_executor(None, repo.index.add, files)
                msg = f"navi auto add - {uname}: added files"
                run = lambda: repo.index.commit(
                    msg, author=author, committer=author)
                await loop.run_in_executor(None, run)

            # sync with remote
            await loop.run_in_executor(None, remote.pull)
            if file_dict:
                await loop.run_in_executor(None, remote.push)
        except:
            pass

    async def repeat(self, message):
        chan = message.channel
        data = self.last.get(chan, ['', 0])

        if not message.content:
            return

        if data[0] == message.content.lower():
            data[1] += 1
        else:
            data = [message.content.lower(), 1]

        if data[1] == self.conf.get('repeat_after', 3):
            await self.bot.send_message(chan, message.content)
            data[1] = 0

        self.last[chan] = data