Ruby-Cogs/levelup/generator/pilmojisrc/source.py
Valerie 477974d53c
Some checks are pending
Run pre-commit / Run pre-commit (push) Waiting to run
Upload 2 Cogs & Update README
2025-05-23 01:30:53 -04:00

250 lines
6.2 KiB
Python

from abc import ABC, abstractmethod
from io import BytesIO
from typing import Any, ClassVar, Dict, Optional
from urllib.error import HTTPError
from urllib.parse import quote_plus
from urllib.request import Request, urlopen
try:
import requests
_has_requests = True
except ImportError:
requests = None
_has_requests = False
__all__ = (
"BaseSource",
"HTTPBasedSource",
"DiscordEmojiSourceMixin",
"EmojiCDNSource",
"TwitterEmojiSource",
"AppleEmojiSource",
"GoogleEmojiSource",
"MicrosoftEmojiSource",
"FacebookEmojiSource",
"MessengerEmojiSource",
"EmojidexEmojiSource",
"JoyPixelsEmojiSource",
"SamsungEmojiSource",
"WhatsAppEmojiSource",
"MozillaEmojiSource",
"OpenmojiEmojiSource",
"TwemojiEmojiSource",
"FacebookMessengerEmojiSource",
"Twemoji",
"Openmoji",
)
class BaseSource(ABC):
"""The base class for an emoji image source."""
@abstractmethod
def get_emoji(self, emoji: str, /) -> Optional[BytesIO]:
"""Retrieves a :class:`io.BytesIO` stream for the image of the given emoji.
Parameters
----------
emoji: str
The emoji to retrieve.
Returns
-------
:class:`io.BytesIO`
A bytes stream of the emoji.
None
An image for the emoji could not be found.
"""
raise NotImplementedError
@abstractmethod
def get_discord_emoji(self, id: int, /) -> Optional[BytesIO]:
"""Retrieves a :class:`io.BytesIO` stream for the image of the given Discord emoji.
Parameters
----------
id: int
The snowflake ID of the Discord emoji.
Returns
-------
:class:`io.BytesIO`
A bytes stream of the emoji.
None
An image for the emoji could not be found.
"""
raise NotImplementedError
def __repr__(self) -> str:
return f"<{self.__class__.__name__}>"
class HTTPBasedSource(BaseSource):
"""Represents an HTTP-based source."""
REQUEST_KWARGS: ClassVar[Dict[str, Any]] = {
"headers": {"User-Agent": "Mozilla/5.0"}
}
def __init__(self) -> None:
if _has_requests:
self._requests_session = requests.Session()
def request(self, url: str) -> bytes:
"""Makes a GET request to the given URL.
If the `requests` library is installed, it will be used.
If it is not installed, :meth:`urllib.request.urlopen` will be used instead.
Parameters
----------
url: str
The URL to request from.
Returns
-------
bytes
Raises
------
Union[:class:`requests.HTTPError`, :class:`urllib.error.HTTPError`]
There was an error requesting from the URL.
"""
if _has_requests:
with self._requests_session.get(url, **self.REQUEST_KWARGS) as response:
if response.ok:
return response.content
else:
req = Request(url, **self.REQUEST_KWARGS)
with urlopen(req) as response:
return response.read()
@abstractmethod
def get_emoji(self, emoji: str, /) -> Optional[BytesIO]:
raise NotImplementedError
@abstractmethod
def get_discord_emoji(self, id: int, /) -> Optional[BytesIO]:
raise NotImplementedError
class DiscordEmojiSourceMixin(HTTPBasedSource):
"""A mixin that adds Discord emoji functionality to another source."""
BASE_DISCORD_EMOJI_URL: ClassVar[str] = "https://cdn.discordapp.com/emojis/"
@abstractmethod
def get_emoji(self, emoji: str, /) -> Optional[BytesIO]:
raise NotImplementedError
def get_discord_emoji(self, id: int, /) -> Optional[BytesIO]:
url = self.BASE_DISCORD_EMOJI_URL + str(id) + ".png"
_to_catch = HTTPError if not _has_requests else requests.HTTPError
try:
return BytesIO(self.request(url))
except _to_catch:
pass
class EmojiCDNSource(DiscordEmojiSourceMixin):
"""A base source that fetches emojis from https://emojicdn.elk.sh/."""
BASE_EMOJI_CDN_URL: ClassVar[str] = "https://emojicdn.elk.sh/"
STYLE: ClassVar[str] = None
def get_emoji(self, emoji: str, /) -> Optional[BytesIO]:
if self.STYLE is None:
raise TypeError("STYLE class variable unfilled.")
url = (
self.BASE_EMOJI_CDN_URL
+ quote_plus(emoji)
+ "?style="
+ quote_plus(self.STYLE)
)
_to_catch = HTTPError if not _has_requests else requests.HTTPError
try:
return BytesIO(self.request(url))
except _to_catch:
pass
class TwitterEmojiSource(EmojiCDNSource):
"""A source that uses Twitter-style emojis. These are also the ones used in Discord."""
STYLE = "twitter"
class AppleEmojiSource(EmojiCDNSource):
"""A source that uses Apple emojis."""
STYLE = "apple"
class GoogleEmojiSource(EmojiCDNSource):
"""A source that uses Google emojis."""
STYLE = "google"
class MicrosoftEmojiSource(EmojiCDNSource):
"""A source that uses Microsoft emojis."""
STYLE = "microsoft"
class SamsungEmojiSource(EmojiCDNSource):
"""A source that uses Samsung emojis."""
STYLE = "samsung"
class WhatsAppEmojiSource(EmojiCDNSource):
"""A source that uses WhatsApp emojis."""
STYLE = "whatsapp"
class FacebookEmojiSource(EmojiCDNSource):
"""A source that uses Facebook emojis."""
STYLE = "facebook"
class MessengerEmojiSource(EmojiCDNSource):
"""A source that uses Facebook Messenger's emojis."""
STYLE = "messenger"
class JoyPixelsEmojiSource(EmojiCDNSource):
"""A source that uses JoyPixels' emojis."""
STYLE = "joypixels"
class OpenmojiEmojiSource(EmojiCDNSource):
"""A source that uses Openmoji emojis."""
STYLE = "openmoji"
class EmojidexEmojiSource(EmojiCDNSource):
"""A source that uses Emojidex emojis."""
STYLE = "emojidex"
class MozillaEmojiSource(EmojiCDNSource):
"""A source that uses Mozilla's emojis."""
STYLE = "mozilla"
# Aliases
Openmoji = OpenmojiEmojiSource
FacebookMessengerEmojiSource = MessengerEmojiSource
TwemojiEmojiSource = Twemoji = TwitterEmojiSource