Ejemplo n.º 1
0
    def __init__(self, bot):
        super().__init__(bot)
        self.insects = AsyncJSONStorage("./insects.json")
        self.insect_creation_lock = asyncio.Lock()

        # clobber original on_error because it's a faux-event
        self._original_on_error = bot.on_error
        bot.on_error = self.on_error
Ejemplo n.º 2
0
class Sample(Cog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.tags = AsyncJSONStorage('tags.json')

    def __unload(self):
        print('Original unload.')

    @command()
    async def sample(self, ctx):
        """ A sample command. """
        await ctx.send('Hello!')

    @Cog.every(10, wait_until_ready=True)
    async def scheduled(self):
        pass

    @group(invoke_without_command=True)
    async def tag(self, ctx, *, key):
        """ Tag management commands. """
        tag = self.tags.get(key)
        if not tag:
            await ctx.send('No such tag.')
        else:
            await ctx.send(tag)

    @tag.command()
    async def list(self, ctx):
        """ Lists all tags. """
        tags = self.tags.all().keys()
        await ctx.send(f'{len(tags)}: {", ".join(tags)}')

    @tag.command(aliases=['create'])
    async def make(self, ctx, key: commands.clean_content, *,
                   value: commands.clean_content):
        """ Creates a tag. """
        if key in self.tags:
            await ctx.send('Tag already exists.')
            return
        await self.tags.put(key, value)
        await ctx.ok()
Ejemplo n.º 3
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.tags = AsyncJSONStorage('tags.json')
Ejemplo n.º 4
0
 def __init__(self, *args, **kwargs):
     super().__init__(*args, **kwargs)
     self.insects = AsyncJSONStorage('./insects.json')
Ejemplo n.º 5
0
class Errors(Cog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.insects = AsyncJSONStorage('./insects.json')

    async def _save_insect(self, error: Exception):
        insects = self.insects.get('insects') or []

        insect_id = str(uuid.uuid4())
        insects.append({
            'id': insect_id,
            'creation_time': time.time(),
            'traceback': get_traceback(error, hide_paths=True)
        })

        await self.insects.put('insects', insects)
        return insect_id

    @group(hidden=True)
    @commands.is_owner()
    async def errors(self, ctx):
        """Manages errors."""
        pass

    @errors.command(aliases=['show', 'info'])
    async def view(self, ctx, insect_id: str):
        """Views an error by insect ID."""
        all_insects = self.insects.get('insects') or []
        insect = discord.utils.find(lambda insect: insect['id'] == insect_id,
                                    all_insects)

        if not insect:
            return await ctx.send("An insect with that ID wasn't found.")

        created = datetime.datetime.fromtimestamp(insect['creation_time'])
        ago = str(datetime.datetime.utcnow() - created)

        embed = discord.Embed(title=f'Insect {insect_id}')
        embed.add_field(name='Created',
                        value=f'{created} ({ago} ago)',
                        inline=False)
        await ctx.send(codeblock(insect['traceback'], lang='py'), embed=embed)

    @errors.command()
    async def throw(self, ctx, *, message='Error!'):
        """Throws an intentional error. Used for debugging."""
        raise RuntimeError(f'Intentional error: {message}')

    async def on_command_error(self, ctx: commands.Context, error: Exception):
        """Default error handler."""

        error_handlers = OrderedDict([
            (commands.BotMissingPermissions, ('Whoops!', True)),
            (commands.MissingPermissions, ('Whoops!', True)),
            (commands.NoPrivateMessage, ("You can't do that in a DM.", False)),
            (commands.NotOwner, ("Only the owner of this bot can do that.",
                                 False)),
            (commands.DisabledCommand, ('That command has been disabled.',
                                        False)),
            (commands.UserInputError, ('User input error', True)),
            (commands.CheckFailure, ('Permissions error', True))
        ])

        for error_type, info in error_handlers.items():
            if not isinstance(error, error_type):
                continue

            prefix, prepend_message = info
            return await ctx.send(prefix +
                                  (f': {error}' if prepend_message else ''))

        if isinstance(error, commands.CommandInvokeError):
            insect_id = await self._save_insect(error)
            await ctx.send(
                'A fatal error has occurred while running that command. ' +
                f'Please report this error ID to the bot owner: `{insect_id}` '
                + 'Thanks!')
            self.log.error('Fatal error. ' + get_traceback(error))
Ejemplo n.º 6
0
 def __init__(self, bot):
     super().__init__(bot)
     self.tracking = AsyncJSONStorage('osu.json', loop=bot.loop)
     self.track_task = bot.loop.create_task(self.poll())
     self.session = aiohttp.ClientSession(
         headers={'user-agent': 'bottlecap/0.0.0'})
Ejemplo n.º 7
0
class Osu(Cog):
    wait_interval: int = 10

    def __init__(self, bot):
        super().__init__(bot)
        self.tracking = AsyncJSONStorage('osu.json', loop=bot.loop)
        self.track_task = bot.loop.create_task(self.poll())
        self.session = aiohttp.ClientSession(
            headers={'user-agent': 'bottlecap/0.0.0'})

    def __unload(self):
        self.session.close()
        self.track_task.cancel()

    def endpoint(self, url: str) -> str:
        return f'https://osu.ppy.sh/api{url}'

    async def get_recent_plays(self,
                               user_id: Union[int, str],
                               *,
                               limit: int = 10) -> List[OsuPlay]:
        params = {'k': self.bot.cfg.osu_api_key, 'u': user_id, 'limit': limit}
        async with self.session.get(self.endpoint('/get_user_recent'),
                                    params=params) as resp:
            resp.raise_for_status()
            plays = await resp.json()
            return [OsuPlay(**play) for play in plays]

    async def get_top_plays(self,
                            user_id: Union[int, str],
                            *,
                            limit: int = 10) -> List[OsuPlay]:
        params = {'k': self.bot.cfg.osu_api_key, 'u': user_id, 'limit': limit}
        async with self.session.get(self.endpoint('/get_user_best'),
                                    params=params) as resp:
            plays = await resp.json()
            return [OsuPlay(**play) for play in plays]

    async def get_beatmap(self, beatmap_id: str) -> OsuBeatmap:
        params = {'k': self.bot.cfg.osu_api_key, 'b': beatmap_id}
        async with self.session.get(self.endpoint('/get_beatmaps'),
                                    params=params) as resp:
            resp.raise_for_status()
            beatmaps = await resp.json()
            return OsuBeatmap(**beatmaps[0])

    async def alert_play(self, user_id: int, info: Dict[str, Any],
                         play: OsuPlay):
        log.debug('Attempting to alert %d about recent play: %s.', user_id,
                  play)
        last_tracked = info.get('last_tracked')

        if play.date == last_tracked:
            log.debug('Detected a stale play for %d (%s).', user_id, play.date)
            return

        await self.tracking.put(user_id, {**info, 'last_tracked': play.date})

        if play.rank == 'F':
            log.debug('Detected failed play, not tracking.')
            return

        log.debug('Tracking this play. (%s)', play)

        beatmap = await self.get_beatmap(play.beatmap_id)
        stars = float(beatmap.difficultyrating)
        user = self.bot.get_user(user_id)

        embed = discord.Embed()
        embed.title = f'{beatmap.artist} - {beatmap.title} [{beatmap.version}]'
        embed.url = f'https://osu.ppy.sh/b/{play.beatmap_id}'
        embed.set_author(name=f"{info['osu_username']} ({user})",
                         url=f'https://osu.ppy.sh/u/{play.user_id}')
        embed.description = PLAY_DESCRIPTION.format(play=play,
                                                    player=user_id,
                                                    beatmap=beatmap,
                                                    stars=stars)

        top_plays = await self.get_top_plays(play.user_id)
        log.debug('Top plays for %d: %s', user_id, top_plays)
        as_top_play: Optional[OsuPlay] = discord.utils.get(top_plays,
                                                           date=play.date)
        log.debug('Current play date: %s, detected top play: %s', play.date,
                  as_top_play)

        channel = self.bot.get_channel(info['channel_id'])
        if not channel:
            log.warning('Cannot locate channel %d, not alerting.',
                        info['channel_id'])
            return

        try:
            if as_top_play:
                log.debug('Using top play PP score (%s).', as_top_play.pp)
                content = f'<@{user_id}> **+{as_top_play.pp} PP'
            else:
                content = ''
            log.debug('Alerting %d in %d.', user_id, info['channel_id'])
            await channel.send(embed=embed, content=content)
        except discord.Forbidden:
            pass

    async def poll(self):
        while True:
            for user_id, info in self.tracking.all().items():
                user_id = int(user_id)
                plays = await self.get_recent_plays(info['osu_username'],
                                                    limit=1)
                if not plays:
                    log.warning('Found NO plays for %s.', info['osu_username'])
                    continue
                await self.alert_play(user_id, info, plays[0])
            await asyncio.sleep(self.wait_interval)

    @group(invoke_without_command=True)
    async def track(self, ctx: Context, username):
        """tracks you on osu"""
        log.info('Now tracking %d (%s).', ctx.author.id, username)
        await self.tracking.put(
            ctx.author.id, {
                'channel_id': ctx.channel.id,
                'osu_username': username,
                'created_at': time.time(),
                'guild_id': ctx.guild.id
            })
        await ctx.send('okay, tracking you here')

    @track.command(hidden=True)
    async def reset(self, ctx: Context, who: discord.User = None):
        """resets your tracking status"""
        target = who or ctx.author
        record = self.tracking.get(target.id)

        if not record:
            return

        try:
            del record['last_tracked']
        except KeyError:
            return

        await self.tracking.put(target.id, record)
        await ctx.send(f"ok, reset {target}'s tracking state.")

    @command()
    async def untrack(self, ctx: Context):
        """untracks you on osu"""
        log.info('Untracking %d.', ctx.author.id)
        try:
            await self.tracking.delete(ctx.author.id)
        except KeyError:
            pass
        await ctx.ok()
Ejemplo n.º 8
0
class Errors(lifesaver.Cog):
    def __init__(self, bot):
        super().__init__(bot)
        self.insects = AsyncJSONStorage("./insects.json")
        self.insect_creation_lock = asyncio.Lock()

        # clobber original on_error because it's a faux-event
        self._original_on_error = bot.on_error
        bot.on_error = self.on_error

    #: Errors to ignore.
    ignored_errors = {
        # Silently ignore ratelimit violations.
        commands.CommandOnCooldown,
        # Silently ignore other check failures.
        commands.CheckFailure,
        # Silently ignore command not found errors.
        commands.CommandNotFound,
    }

    #: Default error handlers.
    error_handlers = OrderedDict([
        (commands.TooManyArguments, ("Too many arguments.", False)),
        (commands.BotMissingPermissions, ("Permissions error", True)),
        (commands.MissingPermissions, ("Permissions error", True)),
        (
            commands.NoPrivateMessage,
            ("You can't do that in a direct message.", False),
        ),
        (commands.NotOwner, ("Only of the owner of this bot can do that.",
                             False)),
        (commands.DisabledCommand, ("This command has been disabled.", False)),
        (commands.UserInputError, ("User input error", True)),
        (commands.CheckFailure, ("Permissions error", True)),
        (
            lifesaver.commands.SubcommandInvocationRequired,
            (
                "You need to specify a subcommand to run. Run `{prefix}help {command}` for help.",
                False,
            ),
        ),
    ])

    def cog_unload(self):
        super().cog_unload()

        # restore original on_error
        self.bot.on_error = self._original_on_error

    def make_insect_id(self) -> str:
        return secrets.token_hex(6)

    async def create_insect(self, error: Exception) -> str:
        """Create and save an insect object, returning its ID."""
        async with self.insect_creation_lock:
            insects = self.insects.get("insects", [])

            insect_id = self.make_insect_id()
            insects.append({
                "id":
                insect_id,
                "creation_time":
                time.time(),
                "traceback":
                format_traceback(error, hide_paths=True),
            })

            await self.insects.put("insects", insects)

        return insect_id

    @lifesaver.group(hidden=True, hollow=True)
    @commands.is_owner()
    async def errors(self, ctx: lifesaver.commands.Context):
        """Manages errors."""

    @errors.command(name="recent")
    async def errors_recent(self,
                            ctx: lifesaver.commands.Context,
                            amount: int = 5):
        """Shows recent insects."""
        all_insects = self.insects.get("insects", [])

        if not all_insects:
            await ctx.send("There are no insects.")
            return

        recent_insects = sorted(
            all_insects[-amount:],
            key=lambda insect: insect["creation_time"],
            reverse=True,
        )

        def format_insect(insect):
            ago = human_delta(
                datetime.datetime.utcfromtimestamp(insect["creation_time"]))
            summary = summarize_traceback(insect["traceback"])
            return f'\N{BUG} **`{insect["id"]}`** `{summary}` ({ago})'

        embed = discord.Embed(
            title="Recent Insects",
            color=discord.Color.red(),
            description="\n".join(map(format_insect, recent_insects)),
        )
        embed.set_footer(
            text=f"{pluralize(insect=len(all_insects))} in total.")

        try:
            await ctx.send(embed=embed)
        except discord.HTTPException:
            await ctx.send("Too much information to display.")

    @errors.command(name="view", aliases=["show", "info"])
    async def errors_view(self, ctx: lifesaver.commands.Context, insect_id):
        """Views an error by insect ID."""
        all_insects = self.insects.get("insects", [])
        insect = discord.utils.find(lambda insect: insect["id"] == insect_id,
                                    all_insects)

        if not insect:
            await ctx.send("There is no insect with that ID.")
            return

        created = datetime.datetime.utcfromtimestamp(insect["creation_time"])
        ago = human_delta(created)

        embed = discord.Embed(
            title=f"Insect {insect_id}",
            color=discord.Color.red(),
            description=codeblock(insect["traceback"], lang="py"),
        )
        embed.add_field(name="Created",
                        value=f"{created} UTC ({ago} ago)",
                        inline=False)
        await ctx.send(embed=embed)

    @errors.command(name="throw", hidden=True)
    async def errors_throw(self,
                           ctx: lifesaver.commands.Context,
                           *,
                           message="!"):
        """Throws an error. Useful for debugging."""
        raise RuntimeError(f"Intentional error: {message}")

    async def on_error(self, event, *args, **kwargs):
        type, value, traceback = sys.exc_info()
        self.log.error(
            "Fatal error in %s (args=%r, kwargs=%r). %s",
            event,
            args,
            kwargs,
            format_traceback(value),
        )
        await self.create_insect(value)

    @lifesaver.Cog.listener()
    async def on_command_error(self, ctx: lifesaver.commands.Context,
                               error: Exception):
        ignored_errors = getattr(ctx.bot, "ignored_errors", [])
        filtered_handlers = OrderedDict(
            (key, value) for (key, value) in self.error_handlers.items()
            if key not in ignored_errors)

        if isinstance(error, commands.BadArgument):
            if "failed for parameter" in str(error):
                self.log.error("Generic check error. %s",
                               format_traceback(error.__cause__))
            await ctx.send(f"Bad argument. {error}")
            return

        for (
                error_type,
            (message_format, do_append_message),
        ) in filtered_handlers.items():
            if not isinstance(error, error_type):
                continue

            message = message_format.format(prefix=ctx.prefix,
                                            command=ctx.command.qualified_name)

            if do_append_message:
                await ctx.send(f"{message}: {error}")
            else:
                await ctx.send(message)

            return

        if type(error) in self.ignored_errors:
            return

        self.log.error("Fatal error. %s", format_traceback(error))
        insect_id = await self.create_insect(error)
        await ctx.send(f"Something went wrong. \N{BUG} `{insect_id}`")
Ejemplo n.º 9
0
class Sample(lifesaver.Cog):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.tags = AsyncJSONStorage("tags.json")

    def cog_unload(self):
        print("Calling super unload:", super().cog_unload)
        super().cog_unload()
        print("My unload.")

    @lifesaver.command()
    async def long_help(self, ctx: lifesaver.commands.Context):
        """A command with a long help message.

        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin
        ultricies nulla sed sapien varius posuere. Suspendisse volutpat
        lobortis ligula sit amet feugiat. Duis lobortis turpis id diam porta,
        eget egestas dui posuere. Maecenas scelerisque efficitur nunc. Ut ac
        sem placerat, placerat libero sit amet, consectetur tellus. Vivamus
        venenatis efficitur leo, at sollicitudin odio efficitur vel. Orci
        varius natoque penatibus et magnis dis parturient montes, nascetur
        ridiculus mus. Praesent porta elementum dolor et euismod. Etiam et
        ullamcorper elit, quis bibendum tellus. Vivamus mattis ipsum ac gravida
        efficitur. Vestibulum et finibus ipsum. Vestibulum lorem lectus,
        rhoncus ac odio in, maximus iaculis lacus. Ut a volutpat nisl. Integer
        a fringilla arcu. Cras condimentum maximus magna at viverra.
        Pellentesque habitant morbi tristique senectus et netus et malesuada
        fames ac turpis egestas.

        Pellentesque rutrum lectus eget consectetur varius. Suspendisse
        facilisis condimentum nisi sit amet ultrices. Suspendisse non ipsum id
        metus volutpat venenatis id sed odio. Suspendisse eu fringilla ante,
        quis tempus arcu. Vivamus lacus leo, facilisis et viverra et,
        ullamcorper eget dui. Proin eleifend bibendum enim, sit amet congue
        justo molestie eget. Proin in felis quis nisi efficitur lacinia non nec
        enim. Proin vel sapien vel ipsum commodo venenatis. Morbi ornare porta
        dui, eget varius turpis fermentum id. Cras nec nulla elit. Morbi sit
        amet nunc lobortis, porttitor odio nec, malesuada sem. Suspendisse
        efficitur malesuada viverra. Duis eleifend, felis a tristique faucibus,
        neque odio faucibus purus, ac lacinia quam orci quis leo.
        """

    @lifesaver.group()
    async def sample_group(self, ctx: lifesaver.commands.Context):
        """I am a group."""

    @sample_group.command(typing=True)
    async def subcommand_two(self, ctx: lifesaver.commands.Context):
        """I am a second subcommand."""
        await asyncio.sleep(5)
        await ctx.send("yo!")

    @sample_group.command(typing=True)
    async def subcommand(self, ctx: lifesaver.commands.Context):
        """I am a subcommand."""
        await asyncio.sleep(5)
        await ctx.send("yo!")

    @lifesaver.command()
    async def message(self, ctx: lifesaver.commands.Context):
        """Sends the configurable message."""
        await ctx.send(self.config.configurable_message)

    @lifesaver.command()
    async def pages_example(self, ctx: lifesaver.commands.Context):
        """A demonstration of `ctx +=`."""
        from random import random

        for _ in range(150):
            ctx += str(random())
        await ctx.send_pages()

    @lifesaver.command()
    async def sample(self, ctx: lifesaver.commands.Context):
        """A sample command."""
        await ctx.send("Hello!")

    @lifesaver.command()
    async def source(self, ctx: lifesaver.commands.Context, command):
        """View the source of a command."""
        cmd = ctx.bot.get_command(command)
        if not cmd:
            await ctx.send("No such command.")
            return
        from inspect import getsource

        await ctx.send(codeblock(getsource(cmd.callback), lang="py"))

    @lifesaver.Cog.every(10, wait_until_ready=True)
    async def scheduled(self):
        print("Scheduled function.")

    @lifesaver.group(invoke_without_command=True)
    async def tag(self, ctx: lifesaver.commands.Context, *, key):
        """Tag management commands."""
        tag = self.tags.get(key)
        if not tag:
            await ctx.send("No such tag.")
        else:
            await ctx.send(tag)

    @tag.command()
    async def list(self, ctx: lifesaver.commands.Context):
        """Lists all tags."""
        tags = self.tags.all().keys()
        await ctx.send(f'{len(tags)}: {", ".join(tags)}')

    @tag.command(aliases=["create"])
    async def make(
        self,
        ctx: lifesaver.commands.Context,
        key: commands.clean_content,
        *,
        value: commands.clean_content,
    ):
        """Creates a tag."""
        if key in self.tags:
            await ctx.send("Tag already exists.")
            return
        await self.tags.put(key, value)
        await ctx.ok()