diff options
| author | 2022-09-18 22:03:34 +0100 | |
|---|---|---|
| committer | 2022-09-18 22:03:34 +0100 | |
| commit | 46d1e8ddb217f1bb5e07179b32db50b6a04b6de8 (patch) | |
| tree | c3f4db63c2751c51acfee97d016551b8f677e29b /bot/exts/utilities/twemoji.py | |
| parent | Remove unnecessary hasattr check (diff) | |
| parent | Fix Poetry 1.2 Support (#1099) (diff) | |
Merge branch 'main' into fix-whitelist-inheritance
Diffstat (limited to '')
| -rw-r--r-- | bot/exts/utilities/twemoji.py | 150 | 
1 files changed, 150 insertions, 0 deletions
| diff --git a/bot/exts/utilities/twemoji.py b/bot/exts/utilities/twemoji.py new file mode 100644 index 00000000..c915f05b --- /dev/null +++ b/bot/exts/utilities/twemoji.py @@ -0,0 +1,150 @@ +import logging +import re +from typing import Literal, Optional + +import discord +from discord.ext import commands +from emoji import UNICODE_EMOJI_ENGLISH, is_emoji + +from bot.bot import Bot +from bot.constants import Colours, Roles +from bot.utils.decorators import whitelist_override +from bot.utils.extensions import invoke_help_command + +log = logging.getLogger(__name__) +BASE_URLS = { +    "png": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/72x72/", +    "svg": "https://raw.githubusercontent.com/twitter/twemoji/master/assets/svg/", +} +CODEPOINT_REGEX = re.compile(r"[a-f1-9][a-f0-9]{3,5}$") + + +class Twemoji(commands.Cog): +    """Utilities for working with Twemojis.""" + +    def __init__(self, bot: Bot): +        self.bot = bot + +    @staticmethod +    def get_url(codepoint: str, format: Literal["png", "svg"]) -> str: +        """Returns a source file URL for the specified Twemoji, in the corresponding format.""" +        return f"{BASE_URLS[format]}{codepoint}.{format}" + +    @staticmethod +    def alias_to_name(alias: str) -> str: +        """ +        Transform a unicode alias to an emoji name. + +        Example usages: +        >>> alias_to_name(":falling_leaf:") +        "Falling leaf" +        >>> alias_to_name(":family_man_girl_boy:") +        "Family man girl boy" +        """ +        name = alias.strip(":").replace("_", " ") +        return name.capitalize() + +    @staticmethod +    def build_embed(codepoint: str) -> discord.Embed: +        """Returns the main embed for the `twemoji` commmand.""" +        emoji = "".join(Twemoji.emoji(e) or "" for e in codepoint.split("-")) + +        embed = discord.Embed( +            title=Twemoji.alias_to_name(UNICODE_EMOJI_ENGLISH[emoji]), +            description=f"{codepoint.replace('-', ' ')}\n[Download svg]({Twemoji.get_url(codepoint, 'svg')})", +            colour=Colours.twitter_blue, +        ) +        embed.set_thumbnail(url=Twemoji.get_url(codepoint, "png")) +        return embed + +    @staticmethod +    def emoji(codepoint: Optional[str]) -> Optional[str]: +        """ +        Returns the emoji corresponding to a given `codepoint`, or `None` if no emoji was found. + +        The return value is an emoji character, such as "π". The `codepoint` +        argument can be of any format, since it will be trimmed automatically. +        """ +        if code := Twemoji.trim_code(codepoint): +            return chr(int(code, 16)) + +    @staticmethod +    def codepoint(emoji: Optional[str]) -> Optional[str]: +        """ +        Returns the codepoint, in a trimmed format, of a single emoji. + +        `emoji` should be an emoji character, such as "π" and "π₯°", and +        not a codepoint like "1f1f8". When working with combined emojis, +        such as "πΈπͺ" and "π¨βπ©βπ¦", send the component emojis through the method +        one at a time. +        """ +        if emoji is None: +            return None +        return hex(ord(emoji)).removeprefix("0x") + +    @staticmethod +    def trim_code(codepoint: Optional[str]) -> Optional[str]: +        """ +        Returns the meaningful information from the given `codepoint`. + +        If no codepoint is found, `None` is returned. + +        Example usages: +        >>> trim_code("U+1f1f8") +        "1f1f8" +        >>> trim_code("\u0001f1f8") +        "1f1f8" +        >>> trim_code("1f466") +        "1f466" +        """ +        if code := CODEPOINT_REGEX.search(codepoint or ""): +            return code.group() + +    @staticmethod +    def codepoint_from_input(raw_emoji: tuple[str, ...]) -> str: +        """ +        Returns the codepoint corresponding to the passed tuple, separated by "-". + +        The return format matches the format used in URLs for Twemoji source files. + +        Example usages: +        >>> codepoint_from_input(("π",)) +        "1f40d" +        >>> codepoint_from_input(("1f1f8", "1f1ea")) +        "1f1f8-1f1ea" +        >>> codepoint_from_input(("π¨βπ§βπ¦",)) +        "1f468-200d-1f467-200d-1f466" +        """ +        raw_emoji = [emoji.lower() for emoji in raw_emoji] +        if is_emoji(raw_emoji[0]): +            emojis = (Twemoji.codepoint(emoji) or "" for emoji in raw_emoji[0]) +            return "-".join(emojis) + +        emoji = "".join( +            Twemoji.emoji(Twemoji.trim_code(code)) or "" for code in raw_emoji +        ) +        if is_emoji(emoji): +            return "-".join(Twemoji.codepoint(e) or "" for e in emoji) + +        raise ValueError("No codepoint could be obtained from the given input") + +    @commands.command(aliases=("tw",)) +    @whitelist_override(roles=(Roles.everyone,)) +    async def twemoji(self, ctx: commands.Context, *raw_emoji: str) -> None: +        """Sends a preview of a given Twemoji, specified by codepoint or emoji.""" +        if len(raw_emoji) == 0: +            await invoke_help_command(ctx) +            return +        try: +            codepoint = self.codepoint_from_input(raw_emoji) +        except ValueError: +            raise commands.BadArgument( +                "please include a valid emoji or emoji codepoint." +            ) + +        await ctx.send(embed=self.build_embed(codepoint)) + + +def setup(bot: Bot) -> None: +    """Load the Twemoji cog.""" +    bot.add_cog(Twemoji(bot)) | 
