Пример #1
0
    async def cmd_stream_del(self,
                             *,
                             _members: Optional[tuple[discord.Member,
                                                      ...]] = None):
        """
        ->type Reminders
        ->signature pg!stream del
        ->description Remove yourself from the stream-ping-list
        ->extended description
        Remove yourself from the stream-ping-list. You can always add you later
        with `pg!stream add`
        """
        async with db.DiscordDB("stream") as ping_db:
            data: list = ping_db.get([])

            try:
                if _members:
                    for mem in _members:
                        data.remove(mem.id)
                else:
                    data.remove(self.author.id)
            except ValueError:
                raise BotException(
                    "Could not remove member",
                    "Member was not previously added to the ping list",
                )

            ping_db.write(data)

        await self.cmd_stream()
Пример #2
0
    async def cmd_stream_ping(self, message: Optional[String] = None):
        """
        ->type Reminders
        ->signature pg!stream ping [message]
        ->description Ping users in stream-list with an optional message.
        ->extended description
        Ping all users in the ping list to announce a stream.
        You can pass an optional stream message (like the stream topic).
        The streamer name will be included and many people will be pinged so \
        don't make pranks with this command.
        """
        async with db.DiscordDB("stream") as ping_db:
            data: list = ping_db.get([])

        msg = message.string if message else "Enjoy the stream!"
        ping = ("Pinging everyone on ping list:\n" + "\n".join(
            (f"<@!{user}>" for user in data))
                if data else "No one is registered on the ping momento :/")

        try:
            await self.response_msg.delete()
        except discord.errors.NotFound:
            pass
        await self.channel.send(
            f"<@!{self.author.id}> is gonna stream!\n{msg}\n{ping}")
Пример #3
0
    async def cmd_stream(self):
        """
        ->type Reminders
        ->signature pg!stream
        ->description Show the ping-stream-list
        Send an embed with all the users currently in the ping-stream-list
        """
        async with db.DiscordDB("stream") as db_obj:
            data = db_obj.get([])

        if not data:
            await embed_utils.replace(
                self.response_msg,
                title="Memento ping list",
                description="Ping list is empty!",
            )
            return

        await embed_utils.replace(
            self.response_msg,
            title="Memento ping list",
            description=
            ("Here is a list of people who want to be pinged when stream starts"
             "\nUse 'pg!stream ping' to ping them if you start streaming\n" +
             "\n".join((f"<@{user}>" for user in data))),
        )
Пример #4
0
    async def cmd_stream_add(self,
                             *,
                             _members: Optional[tuple[discord.Member,
                                                      ...]] = None):
        """
        ->type Reminders
        ->signature pg!stream add
        ->description Add yourself to the stream-ping-list
        ->extended description
        Add yourself to the stream-ping-list. You can always delete you later
        with `pg!stream del`
        """
        async with db.DiscordDB("stream") as ping_db:
            data: list = ping_db.get([])

            if _members:
                for mem in _members:
                    if mem.id not in data:
                        data.append(mem.id)
            elif self.author.id not in data:
                data.append(self.author.id)

            ping_db.write(data)

        await self.cmd_stream()
Пример #5
0
    async def cmd_reminders(self):
        """
        ->type Reminders
        ->signature pg!reminders
        ->description View all the reminders you have set
        -----
        Implement pg!reminders, for users to view their reminders
        """
        async with db.DiscordDB("reminders") as db_obj:
            db_data = db_obj.get({})

        desc = "You have no reminders set"
        if self.author.id in db_data:
            desc = ""
            cnt = 0
            for on, (reminder, chan_id, _) in db_data[self.author.id].items():
                channel = None
                if common.guild is not None:
                    channel = common.guild.get_channel(chan_id)

                cin = channel.mention if channel is not None else "DM"
                desc += (
                    f"Reminder ID: `{cnt}`\n"
                    f"**On {utils.format_datetime(on)} in {cin}:**\n> {reminder}\n\n"
                )
                cnt += 1

        await embed_utils.replace(
            self.response_msg,
            title=f"Reminders for {self.author.display_name}:",
            description=desc,
        )
Пример #6
0
async def clean_db_member(member: discord.Member):
    """
    This function silently removes users from database messages
    """
    for table_name in ("stream", "reminders", "clock"):
        async with db.DiscordDB(table_name) as db_obj:
            data = db_obj.get({})
            if member.id in data:
                data.pop(member)
                db_obj.write(data)
Пример #7
0
    async def cmd_vibecheck(self):
        """
        ->type Play With Me :snake:
        ->signature pg!vibecheck
        ->description Check my mood.
        -----
        Implement pg!vibecheck, to check the snek's emotion
        """
        async with db.DiscordDB("emotions") as db_obj:
            all_emotions = db_obj.get({})

        emotion_percentage = vibecheck.get_emotion_percentage(all_emotions,
                                                              round_by=-1)
        all_emotion_response = vibecheck.get_emotion_desc_dict(all_emotions)

        bot_emotion = max(emotion_percentage.keys(),
                          key=lambda key: emotion_percentage[key])
        msg = all_emotion_response[bot_emotion]["msg"]
        emoji_link = all_emotion_response[bot_emotion]["emoji_link"]

        if all_emotion_response[bot_emotion].get("override_emotion", None):
            bot_emotion = all_emotion_response[bot_emotion]["override_emotion"]

        color = pygame.Color(vibecheck.EMOTION_COLORS[bot_emotion])

        t = time.time()
        pygame.image.save(vibecheck.emotion_pie_chart(all_emotions, 400),
                          f"temp{t}.png")
        file = discord.File(f"temp{t}.png")

        try:
            await self.response_msg.delete()
        except discord.errors.NotFound:
            # Message already deleted
            pass

        embed_dict = {
            "title": f"The snek is {bot_emotion} right now!",
            "description": msg,
            "thumbnail_url": emoji_link,
            "footer_text":
            "This is currently in beta version, so the end product may look different",
            "footer_icon_url":
            "https://cdn.discordapp.com/emojis/844513909158969374.png?v=1",
            "image_url": f"attachment://temp{t}.png",
            "color": utils.color_to_rgb_int(color),
        }
        embed = embed_utils.create(**embed_dict)
        await self.invoke_msg.reply(file=file,
                                    embed=embed,
                                    mention_author=False)

        os.remove(f"temp{t}.png")
Пример #8
0
    async def cmd_reminders_remove(self, *reminder_ids: int):
        """
        ->type Reminders
        ->signature pg!reminders remove [*ids]
        ->description Remove reminders
        ->extended description
        Remove variable number of reminders, corresponding to each datetime argument
        The reminder id argument must be an integer
        If no arguments are passed, the command clears all reminders
        ->example command pg!reminders remove 1
        -----
        Implement pg!reminders_remove, for users to remove their reminders
        """
        async with db.DiscordDB("reminders") as db_obj:
            db_data = db_obj.get({})
            db_data_copy = copy.deepcopy(db_data)
            cnt = 0
            if reminder_ids:
                for reminder_id in sorted(set(reminder_ids), reverse=True):
                    if self.author.id in db_data:
                        for i, dt in enumerate(db_data_copy[self.author.id]):
                            if i == reminder_id:
                                db_data[self.author.id].pop(dt)
                                cnt += 1
                                break
                    if (reminder_id >= len(db_data_copy[self.author.id])
                            or reminder_id < 0):
                        raise BotException(
                            "Invalid Reminder ID!",
                            "Reminder ID was not an existing reminder ID",
                        )

                if self.author.id in db_data and not db_data[self.author.id]:
                    db_data.pop(self.author.id)

            elif self.author.id in db_data:
                cnt = len(db_data.pop(self.author.id))

            db_obj.write(db_data)

        await embed_utils.replace(
            self.response_msg,
            title="Reminders removed!",
            description=f"Successfully removed {cnt} reminder(s)",
        )
Пример #9
0
async def get_channel_feature(name: str,
                              channel: common.Channel,
                              defaultret: bool = False):
    """
    Get the channel feature. Returns True if the feature name is disabled on
    that channel, False otherwise. Also handles category channel
    """
    async with db.DiscordDB("feature") as db_obj:
        db_dict: dict[int, bool] = db_obj.get({}).get(name, {})

    if channel.id in db_dict:
        return db_dict[channel.id]

    if isinstance(channel, discord.TextChannel):
        if channel.category_id is None:
            return defaultret
        return db_dict.get(channel.category_id, defaultret)

    return defaultret
Пример #10
0
async def routine():
    """
    Function that gets called routinely. This function inturn, calles other
    routine functions to handle stuff
    """
    async with db.DiscordDB("reminders") as db_obj:
        await handle_reminders(db_obj)

    if random.randint(0, 4) == 0:
        await emotion.update("bored", 1)

    await common.bot.change_presence(activity=discord.Activity(
        type=discord.ActivityType.watching,
        name="discord.io/pygame_community",
    ))
    await asyncio.sleep(3)
    await common.bot.change_presence(activity=discord.Activity(
        type=discord.ActivityType.playing,
        name="in discord.io/pygame_community",
    ))
Пример #11
0
    async def cmd_clock(
        self,
        action: str = "",
        timezone: Optional[float] = None,
        color: Optional[pygame.Color] = None,
        *,
        _member: Optional[discord.Member] = None,
    ):
        """
        ->type Get help
        ->signature pg!clock
        ->description 24 Hour Clock showing <@&778205389942030377> s who are available to help
        -> Extended description
        People on the clock can run the clock with more arguments, to update their data.
        `pg!clock update [timezone in hours] [color as hex string]`
        `timezone` is float offset from GMT in hours.
        `color` optional color argument, that shows up on the clock.
        Note that you might not always display with that colour.
        This happens if more than one person are on the same timezone
        Use `pg!clock remove` to remove yourself from the clock
        -----
        Implement pg!clock, to display a clock of helpfulies/mods/wizards
        """
        async with db.DiscordDB("clock") as db_obj:
            timezones = db_obj.get({})
            if action:
                if _member is None:
                    member = self.author
                    if member.id not in timezones:
                        raise BotException(
                            "Cannot update clock!",
                            "You cannot run clock update commands because you are "
                            + "not on the clock",
                        )
                else:
                    member = _member

                if action == "update":
                    if timezone is not None and abs(timezone) > 12:
                        raise BotException("Failed to update clock!",
                                           "Timezone offset out of range")

                    if member.id in timezones:
                        if timezone is not None:
                            timezones[member.id][0] = timezone
                        if color is not None:
                            timezones[member.id][1] = utils.color_to_rgb_int(
                                color)
                    else:
                        if timezone is None:
                            raise BotException(
                                "Failed to update clock!",
                                "Timezone is required when adding new people",
                            )

                        if color is None:
                            color = pygame.Color((
                                random.randint(0, 0xFFFFFF) << 8) | 0xFF)

                        timezones[member.id] = [
                            timezone, utils.color_to_rgb_int(color)
                        ]

                    # sort timezones dict after an update operation
                    timezones = dict(
                        sorted(timezones.items(), key=lambda x: x[1][0]))

                elif action == "remove":
                    try:
                        timezones.pop(member.id)
                    except KeyError:
                        raise BotException(
                            "Failed to update clock!",
                            "Cannot remove non-existing person from clock",
                        )

                else:
                    raise BotException("Failed to update clock!",
                                       f"Invalid action specifier {action}")

                db_obj.write(timezones)

        t = time.time()

        pygame.image.save(
            await clock.user_clock(t, timezones, self.get_guild()),
            f"temp{t}.png")
        common.cmd_logs[self.invoke_msg.id] = await self.channel.send(
            file=discord.File(f"temp{t}.png"))
        os.remove(f"temp{t}.png")

        try:
            await self.response_msg.delete()
        except discord.NotFound:
            pass
Пример #12
0
    async def cmd_reminders_add(
        self,
        msg: String,
        on: datetime.datetime,
        *,
        _delta: Optional[datetime.timedelta] = None,
    ):
        """
        ->type Reminders
        ->signature pg!reminders add <message> <datetime in iso format>
        ->description Set a reminder to yourself
        ->extended description
        Allows you to set a reminder to yourself
        The date-time must be an ISO time formatted string, in UTC time
        string
        ->example command pg!reminders add "do the thing" "2034-10-26 11:19:36"
        -----
        Implement pg!reminders_add, for users to set reminders for themselves
        """

        if _delta is None:
            now = datetime.datetime.utcnow()
            _delta = on - now
        else:
            now = on
            on = now + _delta

        if on < now:
            raise BotException(
                "Failed to set reminder!",
                "Time cannot go backwards, negative time does not make sense..."
                "\n Or does it? \\*vsauce music plays in the background\\*",
            )

        elif _delta <= datetime.timedelta(seconds=10):
            raise BotException(
                "Failed to set reminder!",
                "Why do you want me to set a reminder for such a small duration?\n"
                "Pretty sure you can remember that one yourself :wink:",
            )

        # remove microsecond precision of the 'on' variable
        on -= datetime.timedelta(microseconds=on.microsecond)

        async with db.DiscordDB("reminders") as db_obj:
            db_data = db_obj.get({})
            if self.author.id not in db_data:
                db_data[self.author.id] = {}

            # user is editing old reminder message, discard the old reminder
            for key, (_, chan_id,
                      msg_id) in tuple(db_data[self.author.id].items()):
                if chan_id == self.channel.id and msg_id == self.invoke_msg.id:
                    db_data[self.author.id].pop(key)

            limit = 25 if self.is_priv else 10
            if len(db_data[self.author.id]) >= limit:
                raise BotException(
                    "Failed to set reminder!",
                    f"I cannot set more than {limit} reminders for you",
                )

            db_data[self.author.id][on] = (
                msg.string.strip(),
                self.channel.id,
                self.invoke_msg.id,
            )
            db_obj.write(db_data)

        await embed_utils.replace(
            self.response_msg,
            title="Reminder set!",
            description=
            (f"Gonna remind {self.author.name} in {utils.format_timedelta(_delta)}\n"
             f"And that is on {utils.format_datetime(on)}"),
        )
Пример #13
0
    async def call_cmd(self):
        """
        Command handler, calls the appropriate sub function to handle commands.
        This one takes in the parsed arguments from the parse_args function,
        and handles that according to the function being called, by using
        the inspect module, and verifying that all args and kwargs are accurate
        before calling the actual function. Relies on argument annotations to
        cast args/kwargs to the types required by the function
        """
        cmd, args, kwargs = parse_args(self.cmd_str)

        # command has been blacklisted from running
        async with db.DiscordDB("blacklist") as db_obj:
            if cmd in db_obj.get([]):
                raise BotException(
                    "Cannot execute comamand!",
                    f"The command '{cmd}' has been temporarily been blocked from "
                    "running, while wizards are casting their spells on it!\n"
                    "Please try running the command after the maintenance work "
                    "has been finished",
                )

        # First check if it is a group command, and handle it.
        # get the func object
        is_group = False
        func = None
        if cmd in self.groups:
            # iterate over group commands sorted in descending order, so that
            # we find the correct match
            for func in sorted(self.groups[cmd],
                               key=lambda x: len(x.subcmds),
                               reverse=True):
                n = len(func.subcmds)
                if func.subcmds == tuple(args[:n]):
                    args = args[n:]
                    is_group = True
                    break

        if not is_group:
            if cmd not in self.cmds_and_funcs:
                if cmd in common.admin_commands:
                    raise BotException(
                        "Permissions Error!",
                        f"The command '{cmd}' is an admin command, and you do "
                        "not have access to that",
                    )

                raise BotException(
                    "Unrecognized command!",
                    f"The command '{cmd}' does not exist.\nFor help on bot "
                    "commands, do `pg!help`",
                )
            func = self.cmds_and_funcs[cmd]

        if hasattr(func, "no_dm") and self.is_dm:
            raise BotException(
                "Cannot run this commands on DM",
                "This command is not supported on DMs",
            )

        if hasattr(func, "fun_cmd"):
            if await utils.get_channel_feature("nofun", self.channel):
                raise BotException(
                    "Could not run command!",
                    "This command is a 'fun' command, and is not allowed "
                    "in this channel. Please try running the command in "
                    "some other channel.",
                )

            bored = await emotion.get("bored")
            if bored < -60 and -bored / 100 >= random.random():
                raise BotException(
                    "I am Exhausted!",
                    "I have been running a lot of commands lately, and now I am tired.\n"
                    "Give me a bit of a break, and I will be back to normal!",
                )

            confused = await emotion.get("confused")
            if confused > 60 and random.random() < confused / 400:
                await embed_utils.replace(
                    self.response_msg,
                    title="I am confused...",
                    description="Hang on, give me a sec...",
                )

                await asyncio.sleep(random.randint(3, 5))
                await embed_utils.replace(
                    self.response_msg,
                    title="Oh, never mind...",
                    description="Sorry, I was confused for a sec there",
                )
                await asyncio.sleep(0.5)

        if func is None:
            raise BotException("Internal bot error",
                               "This should never happen kek")

        # If user has put an attachment, check whether it's a text file, and
        # handle as code block
        for attach in self.invoke_msg.attachments:
            if attach.content_type is not None and (
                    attach.content_type.startswith("text")
                    or attach.content_type.endswith(("json", "javascript"))):
                contents = await attach.read()
                ext = ""
                if "." in attach.filename:
                    ext = attach.filename.split(".")[-1]

                args.append(CodeBlock(contents.decode(), ext))

        sig = inspect.signature(func)

        i = -1
        is_var_pos = is_var_key = False
        keyword_only_args = []
        all_keywords = []

        # iterate through function parameters, arrange the given args and
        # kwargs in the order and format the function wants
        for i, key in enumerate(sig.parameters):
            param = sig.parameters[key]
            iskw = False

            if param.kind not in [param.POSITIONAL_ONLY, param.VAR_POSITIONAL]:
                all_keywords.append(key)

            if (i == 0 and isinstance(param.annotation, str)
                    and self.invoke_msg.reference is not None
                    and ("discord.Message" in param.annotation
                         or "discord.PartialMessage" in param.annotation)):
                # first arg is expected to be a Message object, handle reply into
                # the first argument
                msg = str(self.invoke_msg.reference.message_id)
                if self.invoke_msg.reference.channel_id != self.channel.id:
                    msg = str(self.invoke_msg.reference.channel_id) + "/" + msg

                args.insert(0, msg)

            if param.kind == param.VAR_POSITIONAL:
                is_var_pos = True
                for j in range(i, len(args)):
                    args[j] = await self.cast_arg(param, args[j], cmd)
                continue

            elif param.kind == param.VAR_KEYWORD:
                is_var_key = True
                for j in kwargs:
                    if j not in keyword_only_args:
                        kwargs[j] = await self.cast_arg(param, kwargs[j], cmd)
                continue

            elif param.kind == param.KEYWORD_ONLY:
                iskw = True
                keyword_only_args.append(key)
                if key not in kwargs:
                    if param.default == param.empty:
                        raise KwargError(
                            f"Missed required keyword argument `{key}`", cmd)
                    kwargs[key] = param.default
                    continue

            elif i == len(args):
                # ran out of args, try to fill it with something
                if key in kwargs:
                    if param.kind == param.POSITIONAL_ONLY:
                        raise ArgError(
                            f"`{key}` cannot be passed as a keyword argument",
                            cmd)
                    args.append(kwargs.pop(key))

                elif param.default == param.empty:
                    raise ArgError(f"Missed required argument `{key}`", cmd)
                else:
                    args.append(param.default)
                    continue

            elif key in kwargs:
                raise ArgError(
                    "Positional cannot be passed again as a keyword argument",
                    cmd)

            # cast the argument into the required type
            if iskw:
                kwargs[key] = await self.cast_arg(param, kwargs[key], cmd, key)
            else:
                args[i] = await self.cast_arg(param, args[i], cmd, key)

        i += 1
        # More arguments were given than required
        if not is_var_pos and i < len(args):
            raise ArgError(f"Too many args were given (`{len(args)}`)", cmd)

        # Iterate through kwargs to check if we received invalid ones
        if not is_var_key:
            for key in kwargs:
                if key not in all_keywords:
                    raise KwargError(
                        f"Received invalid keyword argument `{key}`", cmd)

        await func(*args, **kwargs)