|
31 | 31 | from .user import BitLeaderboardUser, PartialUser, User |
32 | 32 |
|
33 | 33 | if TYPE_CHECKING: |
| 34 | + from typing_extensions import Literal |
34 | 35 | from .http import TwitchHTTP |
| 36 | + |
35 | 37 | __all__ = ( |
36 | 38 | "AdSchedule", |
37 | 39 | "BitsLeaderboard", |
38 | 40 | "Clip", |
39 | 41 | "CheerEmote", |
40 | 42 | "CheerEmoteTier", |
| 43 | + "Emote", |
41 | 44 | "GlobalEmote", |
42 | 45 | "ChannelEmote", |
43 | 46 | "HypeTrainContribution", |
@@ -651,6 +654,104 @@ def __repr__(self): |
651 | 654 | ) |
652 | 655 |
|
653 | 656 |
|
| 657 | +class Emote: |
| 658 | + """ |
| 659 | + Represents an Emote. |
| 660 | +
|
| 661 | + .. note:: |
| 662 | + |
| 663 | + It seems twitch is sometimes returning duplicate information from the emotes endpoint. |
| 664 | + To deduplicate your emotes, you can call ``set()`` on the list of emotes (or any other hashmap), which will remove the duplicates. |
| 665 | +
|
| 666 | + .. code-block:: python |
| 667 | +
|
| 668 | + my_list_of_emotes = await user.get_user_emotes(...) |
| 669 | + deduplicated_emotes = set(my_list_of_emotes) |
| 670 | + |
| 671 | + Attributes |
| 672 | + ----------- |
| 673 | + id: :class:`str` |
| 674 | + The unique ID of the emote. |
| 675 | + set_id: Optional[:class:`str`] |
| 676 | + The ID of the set this emote belongs to. |
| 677 | + Will be ``None`` if the emote doesn't belong to a set. |
| 678 | + owner_id: Optional[:class:`str`] |
| 679 | + The ID of the channel this emote belongs to. |
| 680 | + name: :class:`str` |
| 681 | + The name of this emote, as the user sees it. |
| 682 | + type: :class:`str` |
| 683 | + The reason this emote is available to the user. |
| 684 | + Some available values (twitch hasn't documented this properly, there might be more): |
| 685 | +
|
| 686 | + - follower |
| 687 | + - subscription |
| 688 | + - bitstier |
| 689 | + - hypetrain |
| 690 | + - globals (global emotes) |
| 691 | +
|
| 692 | + scales: list[:class:`str`] |
| 693 | + The available scaling for this emote. These are typically floats (ex. "1.0", "2.0"). |
| 694 | + format_static: :class:`bool` |
| 695 | + Whether this emote is available as a static (PNG) file. |
| 696 | + format_animated: :class:`bool` |
| 697 | + Whether this emote is available as an animated (GIF) file. |
| 698 | + theme_light: :class:`bool` |
| 699 | + Whether this emote is available in light theme background mode. |
| 700 | + theme_dark: :class:`bool` |
| 701 | + Whether this emote is available in dark theme background mode. |
| 702 | + """ |
| 703 | + |
| 704 | + __slots__ = "id", "set_id", "owner_id", "name", "type", "scales", "format_static", "format_animated", "theme_light", "theme_dark" |
| 705 | + |
| 706 | + def __init__(self, data: dict) -> None: |
| 707 | + self.id: str = data["id"] |
| 708 | + self.set_id: Optional[str] = data["emote_set_id"] and None |
| 709 | + self.owner_id: Optional[str] = data["owner_id"] and None |
| 710 | + self.name: str = data["name"] |
| 711 | + self.type: str = data["emote_type"] |
| 712 | + self.scales: List[str] = data["scale"] |
| 713 | + self.theme_dark: bool = "dark" in data["theme_mode"] |
| 714 | + self.theme_light: bool = "light" in data["theme_mode"] |
| 715 | + self.format_static: bool = "static" in data["format"] |
| 716 | + self.format_animated: bool = "animated" in data["format"] |
| 717 | + |
| 718 | + def url_for(self, format: Literal["static", "animated"], theme: Literal["dark", "light"], scale: str) -> str: |
| 719 | + """ |
| 720 | + Returns a cdn url that can be used to download or serve the emote on a website. |
| 721 | + This function validates that the arguments passed are possible values to serve the emote. |
| 722 | +
|
| 723 | + Parameters |
| 724 | + ----------- |
| 725 | + format: Literal["static", "animated"] |
| 726 | + The format of the emote. You can check what formats are available using :attr:`~.format_static` and :attr:`~.format_animated`. |
| 727 | + theme: Literal["dark", "light"] |
| 728 | + The theme of the emote. You can check what themes are available using :attr:`~.format_dark` and :attr:`~.format_light`. |
| 729 | + scale: :class:`str` |
| 730 | + The scale of the emote. This should be formatted in this format: ``"1.0"``. |
| 731 | + The scales available for this emote can be checked via :attr:`~.scales`. |
| 732 | + |
| 733 | + Returns |
| 734 | + -------- |
| 735 | + :class:`str` |
| 736 | + """ |
| 737 | + if scale not in self.scales: |
| 738 | + raise ValueError(f"scale for this emote must be one of {', '.join(self.scales)}, not {scale}") |
| 739 | + |
| 740 | + if (theme == "dark" and not self.theme_dark) or (theme == "light" and not self.theme_light): |
| 741 | + raise ValueError(f"theme {theme} is not an available value for this emote") |
| 742 | + |
| 743 | + if (format == "static" and not self.format_static) or (format == "animated" and not self.format_animated): |
| 744 | + raise ValueError(f"format {format} is not an available value for this emote") |
| 745 | + |
| 746 | + return f"https://static-cdn.jtvnw.net/emoticons/v2/{self.id}/{format}/{theme}/{scale}" |
| 747 | + |
| 748 | + def __repr__(self) -> str: |
| 749 | + return f"<Emote id={self.id} name={self.name}>" |
| 750 | + |
| 751 | + def __hash__(self) -> int: # this exists so we can do set(list of emotes) to get rid of duplicates |
| 752 | + return hash(self.id) |
| 753 | + |
| 754 | + |
654 | 755 | class Marker: |
655 | 756 | """ |
656 | 757 | Represents a stream Marker |
|
0 commit comments