Files
python-client-sdk/revolt/message.py
T
2023-05-20 03:04:52 +01:00

270 lines
9.7 KiB
Python
Executable File

from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Any, Coroutine, Optional, Union
from revolt.types.message import SystemMessageContent
from .asset import Asset, PartialAsset
from .channel import DMChannel, GroupDMChannel, TextChannel
from .embed import Embed, SendableEmbed, to_embed
from .utils import Ulid
if TYPE_CHECKING:
from .server import Server
from .state import State
from .types import Embed as EmbedPayload
from .types import Interactions as InteractionsPayload
from .types import Masquerade as MasqueradePayload
from .types import Message as MessagePayload
from .types import MessageReplyPayload
from .user import User
from .member import Member
__all__ = (
"Message",
"MessageReply",
"Masquerade",
"MessageInteractions"
)
class Message(Ulid):
"""Represents a message
Attributes
-----------
id: :class:`str`
The id of the message
content: :class:`str`
The content of the message, this will not include system message's content
attachments: list[:class:`Asset`]
The attachments of the message
embeds: list[Union[:class:`WebsiteEmbed`, :class:`ImageEmbed`, :class:`TextEmbed`, :class:`NoneEmbed`]]
The embeds of the message
channel: :class:`Messageable`
The channel the message was sent in
author: Union[:class:`Member`, :class:`User`]
The author of the message, will be :class:`User` in DMs
edited_at: Optional[:class:`datetime.datetime`]
The time at which the message was edited, will be None if the message has not been edited
mentions: list[Union[:class:`Member`, :class:`User`]]
The users or members that where mentioned in the message
replies: list[:class:`Message`]
The message's this message has replied to, this may not contain all the messages if they are outside the cache
reply_ids: list[:class:`str`]
The message's ids this message has replies to
reactions: dict[str, list[:class:`User`]]
The reactions on the message
interactions: Optional[:class:`MessageInteractions`]
The interactions on the message, if any
"""
__slots__ = ("state", "id", "content", "attachments", "embeds", "channel", "author", "edited_at", "mentions", "replies", "reply_ids", "reactions", "interactions")
def __init__(self, data: MessagePayload, state: State):
self.state: State = state
self.id: str = data["_id"]
content = data.get("content", "")
if not isinstance(content, str):
self.system_content: SystemMessageContent = content
self.content: str = ""
else:
self.content = content
self.attachments: list[Asset] = [Asset(attachment, state) for attachment in data.get("attachments", [])]
self.embeds: list[Embed] = [to_embed(embed, state) for embed in data.get("embeds", [])]
channel = state.get_channel(data["channel"])
assert isinstance(channel, Union[TextChannel, GroupDMChannel, DMChannel])
self.channel: TextChannel | GroupDMChannel | DMChannel = channel
self.server_id: str | None = self.channel.server_id
self.mentions: list[Member | User]
if self.server_id:
author = state.get_member(self.server_id, data["author"])
self.mentions = [self.server.get_member(member_id) for member_id in data.get("mentions", [])]
else:
author = state.get_user(data["author"])
self.mentions = [state.get_user(member_id) for member_id in data.get("mentions", [])]
self.author: Member | User = author
if masquerade := data.get("masquerade"):
if name := masquerade.get("name"):
self.author.masquerade_name = name
if avatar := masquerade.get("avatar"):
self.author.masquerade_avatar = PartialAsset(avatar, state)
if edited_at := data.get("edited"):
self.edited_at: Optional[datetime.datetime] = datetime.datetime.strptime(edited_at, "%Y-%m-%dT%H:%M:%S.%f%z")
self.replies: list[Message] = []
self.reply_ids: list[str] = []
for reply in data.get("replies", []):
try:
message = state.get_message(reply)
self.replies.append(message)
except LookupError:
pass
self.reply_ids.append(reply)
reactions = data.get("reactions", {})
self.reactions: dict[str, list[User]] = {}
for emoji, users in reactions.items():
self.reactions[emoji] = [self.state.get_user(user_id) for user_id in users]
self.interactions: MessageInteractions | None
if interactions := data.get("interactions"):
self.interactions = MessageInteractions(reactions=interactions.get("reactions"), restrict_reactions=interactions.get("restrict_reactions", False))
else:
self.interactions = None
def _update(self, *, content: Optional[str] = None, embeds: Optional[list[EmbedPayload]] = None, edited: Optional[Union[str, int]] = None):
if content is not None:
self.content = content
if embeds is not None:
self.embeds = [to_embed(embed, self.state) for embed in embeds]
if edited is not None:
if isinstance(edited, int):
self.edited_at = datetime.datetime.fromtimestamp(edited / 1000, tz=datetime.timezone.utc)
else:
self.edited_at = datetime.datetime.strptime(edited, "%Y-%m-%dT%H:%M:%S.%f%z")
async def edit(self, *, content: Optional[str] = None, embeds: Optional[list[SendableEmbed]] = None) -> None:
"""Edits the message. The bot can only edit its own message
Parameters
-----------
content: :class:`str`
The new content of the message
"""
new_embeds = [embed.to_dict() for embed in embeds] if embeds else None
await self.state.http.edit_message(self.channel.id, self.id, content, new_embeds)
async def delete(self) -> None:
"""Deletes the message. The bot can only delete its own messages and messages it has permission to delete """
await self.state.http.delete_message(self.channel.id, self.id)
def reply(self, *args: Any, mention: bool = False, **kwargs: Any) -> Coroutine[Any, Any, Message]:
"""Replies to this message, equivilant to:
.. code-block:: python
await channel.send(..., replies=[MessageReply(message, mention)])
"""
return self.channel.send(*args, **kwargs, replies=[MessageReply(self, mention)])
async def add_reaction(self, emoji: str) -> None:
await self.state.http.add_reaction(self.channel.id, self.id, emoji)
async def remove_reaction(self, emoji: str, user: Optional[User] = None, remove_all: bool = False) -> None:
await self.state.http.remove_reaction(self.channel.id, self.id, emoji, user.id if user else None, remove_all)
async def remove_all_reactions(self) -> None:
await self.state.http.remove_all_reactions(self.channel.id, self.id)
@property
def server(self) -> Server:
""":class:`Server` The server this voice channel belongs too
Raises
-------
:class:`LookupError`
Raises if the channel is not part of a server
"""
return self.channel.server
class MessageReply:
"""represents a reply to a message.
Parameters
-----------
message: :class:`Message`
The message being replied to.
mention: :class:`bool`
Whether the reply should mention the author of the message. Defaults to false.
"""
__slots__ = ("message", "mention")
def __init__(self, message: Message, mention: bool = False):
self.message: Message = message
self.mention: bool = mention
def to_dict(self) -> MessageReplyPayload:
return { "id": self.message.id, "mention": self.mention }
class Masquerade:
"""represents a message's masquerade.
Parameters
-----------
name: Optional[:class:`str`]
The name to display for the message
avatar: Optional[:class:`str`]
The avatar's url to display for the message
colour: Optional[:class:`str`]
The colour of the name, similar to role colours
"""
__slots__ = ("name", "avatar", "colour")
def __init__(self, name: Optional[str] = None, avatar: Optional[str] = None, colour: Optional[str] = None):
self.name: str | None = name
self.avatar: str | None = avatar
self.colour: str | None = colour
def to_dict(self) -> MasqueradePayload:
output: MasqueradePayload = {}
if name := self.name:
output["name"] = name
if avatar := self.avatar:
output["avatar"] = avatar
if colour := self.colour:
output["colour"] = colour
return output
class MessageInteractions:
"""Represents a message's interactions, this is for allowing preset reactions and restricting adding reactions to only those.
Parameters
-----------
reactions: Optional[list[:class:`str`]]
The preset reactions on the message
restrict_reactions: bool
Whether or not users can only react to the interaction's reactions
"""
__slots__ = ("reactions", "restrict_reactions")
def __init__(self, *, reactions: Optional[list[str]] = None, restrict_reactions: bool = False):
self.reactions: list[str] | None = reactions
self.restrict_reactions: bool = restrict_reactions
def to_dict(self) -> InteractionsPayload:
output: InteractionsPayload = {}
if reactions := self.reactions:
output["reactions"] = reactions
if restrict_reactions := self.restrict_reactions:
output["restrict_reactions"] = restrict_reactions
return output