250 lines
6.2 KiB
Python
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
|