12 Commits

Author SHA1 Message Date
Zomatree efce2aedac add archived notice 2025-12-11 01:22:41 +00:00
green. ca37846df7 fix: dont compare offset-naive and offset-aware datetimes (#89) 2024-11-29 21:54:44 +00:00
Zomatree 86c07a6d6b fix: cleanup docs to include missing things 2024-10-29 17:02:40 +00:00
Zomatree a8be358339 fix: various bugs
Closes #85
Closes #82
2024-10-29 15:57:26 +00:00
Zomatree e34ca0afda Greedy support, closes #77 2024-06-11 21:33:11 +01:00
IAmTomahawkx 060e6ea32a fix: prevent new space from appearing if buffer is depleted 2024-05-29 21:00:20 +01:00
Zomatree 2dabb0dbb8 fix patch for older python versions 2024-05-29 19:11:56 +01:00
Zomatree 48e0f0fb2e better handling of optional args 2024-05-29 00:18:00 +01:00
Zomatree bc8d650659 bump aiohttp version, fixes #73 2024-05-13 13:36:12 +01:00
Zomatree d8fde3d0a6 fix converters not catching errors 2024-05-13 13:32:21 +01:00
Zomatree d4568c02ea fix create_role 2024-05-13 13:27:11 +01:00
Zomatree ff49f8e3e5 fix pyright changing generics in metaclasses 2024-05-13 13:23:27 +01:00
25 changed files with 278 additions and 59 deletions
+2
View File
@@ -1,5 +1,7 @@
# Revolt.py
> # This project is archived and is no longer receiving updates.
An async library to interact with the https://revolt.chat API.
You can join the support server [here](https://rvlt.gg/FDXER6hr) and find the library's documentation [here](https://revoltpy.readthedocs.io/en/latest/).
View File
+8 -2
View File
@@ -148,7 +148,7 @@ API Reference
:members:
Enums
======
------
The api uses enums to say what variant of something is,
these represent those enums
@@ -278,7 +278,7 @@ All enums subclass `aenum.Enum`.
The embed is unknown
Utils
======
------
.. currentmodule:: revolt.utils
@@ -287,3 +287,9 @@ A collection a utility functions and classes to aid in making your bot
.. autofunction:: get
.. autofunction:: client_session
.. autoclass:: Ulid
:members:
.. autoclass:: Object
:members:
+91 -1
View File
@@ -19,6 +19,11 @@ Command
.. autoclass:: revolt.ext.commands.Command
:members:
Group
~~~~~~~~
.. autoclass:: revolt.ext.commands.Group
:members:
Cog
~~~~
.. autoclass:: revolt.ext.commands.Cog
@@ -28,6 +33,13 @@ command
~~~~~~~~
.. autodecorator:: revolt.ext.commands.command
group
~~~~~~~~
.. autodecorator:: revolt.ext.commands.group
Checks
-------
check
~~~~~~
.. autodecorator:: revolt.ext.commands.check
@@ -40,9 +52,62 @@ is_server_owner
~~~~~~~~~~~~~~~~
.. autodecorator:: revolt.ext.commands.is_server_owner
has_permissions
~~~~~~~~~~~~~~~~
.. autodecorator:: revolt.ext.commands.has_permissions
has_channel_permissions
~~~~~~~~~~~~~~~~~~~~~~~~
.. autodecorator:: revolt.ext.commands.has_channel_permissions
Converters
-----------
IntConverter
~~~~~~~~~~~~~
Converts the parameter to an int
BoolConverter
~~~~~~~~~~~~~~
Converts the parameter to a bool
CategoryConverter
~~~~~~~~~~~~~~~~~~
Converts the parameter to a category
UserConverter
~~~~~~~~~~~~~~
Converts the parameter to a category
MemberConverter
~~~~~~~~~~~~~~~~
Converts the parameter to a category
ChannelConverter
~~~~~~~~~~~~~~~~~
Converts the parameter to a category
Greedy
~~~~~~
Converts the parameter to a greedy parameter which will take as many arguments which convert successfully.
Allows you to have var-args in the middle of a signature.
Help Commands
--------------
HelpCommand
~~~~~~~~~~~~
.. autoclass:: revolt.ext.commands.HelpCommand
:members:
DefaultHelpCommand
~~~~~~~~~~~~~~~~~~~
.. autoclass:: revolt.ext.commands.DefaultHelpCommand
:members:
Exceptions
===========
-----------
CommandError
~~~~~~~~~~~~~
@@ -79,6 +144,11 @@ ServerOnly
.. autoexception:: revolt.ext.commands.ServerOnly
:members:
MissingPermissionsError
~~~~~~~~~~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.MissingPermissionsError
:members:
ConverterError
~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.ConverterError
@@ -99,6 +169,11 @@ CategoryConverterError
.. autoexception:: revolt.ext.commands.CategoryConverterError
:members:
ChannelConverterError
~~~~~~~~~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.ChannelConverterError
:members:
UserConverterError
~~~~~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.UserConverterError
@@ -108,3 +183,18 @@ MemberConverterError
~~~~~~~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.MemberConverterError
:members:
UnionConverterError
~~~~~~~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.UnionConverterError
:members:
MissingSetup
~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.MissingSetup
:members:
CommandOnCooldown
~~~~~~~~~~~~~~~~~~
.. autoexception:: revolt.ext.commands.CommandOnCooldown
:members:
+1 -1
View File
@@ -16,7 +16,7 @@ classifiers = [
"Programming Language :: Python :: 3 :: Only",
]
dependencies = [
"aiohttp==3.8.*",
"aiohttp==3.10.*",
"ulid-py==1.1.*",
"aenum==3.1.*",
"typing_extensions>=4.4.0"
+7 -2
View File
@@ -53,7 +53,8 @@ class EditableChannel:
if kwargs.get("icon", Missing) == None:
remove.append("Icon")
elif kwargs.get("description", Missing) == None:
if kwargs.get("description", Missing) == None:
remove.append("Description")
if icon := kwargs.get("icon"):
@@ -290,9 +291,10 @@ class ServerChannel(Channel):
async def set_default_permissions(self, permissions: PermissionsOverwrite) -> None:
"""Sets the default permissions for the channel.
Parameters
-----------
permissions: :class:`ChannelPermissions`
permissions: :class:`PermissionsOverwrite`
The new default channel permissions
"""
allow, deny = permissions.to_pair()
@@ -300,8 +302,11 @@ class ServerChannel(Channel):
async def set_role_permissions(self, role: Role, permissions: PermissionsOverwrite) -> None:
"""Sets the permissions for a role in the channel.
Parameters
-----------
role: :class:`Role`
The role to set permissions for
permissions: :class:`ChannelPermissions`
The new channel permissions
"""
+2
View File
@@ -52,6 +52,8 @@ class Client:
The api url for the revolt instance you are connecting to, by default it uses the offical instance hosted at revolt.chat
max_messages: :class:`int`
The max amount of messages stored in the cache, by default this is 5k
bot: :class:`bool`
Denotes whether the account used is a bot account or user account, by default this it assumes a bot account
"""
def __init__(self, session: aiohttp.ClientSession, token: str, *, api_url: str = "https://api.revolt.chat", max_messages: int = 5000, bot: bool = True):
+1 -1
View File
@@ -34,7 +34,7 @@ def check(check: Check[ClientT_D]) -> Callable[[T], T]:
checks.append(check)
func._checks = checks # type: ignore
return func
return func # type: ignore
return inner
+36 -6
View File
@@ -13,6 +13,8 @@ import revolt
if TYPE_CHECKING:
from .help import HelpCommand
import aiohttp
from .cog import Cog
from .command import Command
from .context import Context
@@ -42,8 +44,8 @@ class CommandsMeta(type):
for base in reversed(self.__mro__):
for value in base.__dict__.values():
if isinstance(value, Command) and value.parent is None:
commands.append(value)
if isinstance(value, Command) and value.parent is None: # type: ignore
commands.append(value) # type: ignore
self._commands = commands
@@ -79,14 +81,42 @@ class CaseInsensitiveDict(dict[str, V]):
class CommandsClient(revolt.Client, metaclass=CommandsMeta):
"""Main class that adds commands, this class should be subclassed along with `revolt.Client`."""
"""A subclass of :class:`~revolt.Client` which has support for commands.
Parameters
-----------
session: :class:`~aiohttp.ClientSession`
The aiohttp session to use for http request and the websocket
token: :class:`str`
The bots token
api_url: :class:`str`
The api url for the revolt instance you are connecting to, by default it uses the offical instance hosted at revolt.chat
max_messages: :class:`int`
The max amount of messages stored in the cache, by default this is 5k
bot: :class:`bool`
Denotes whether the account used is a bot account or user account, by default this it assumes a bot account
help_command: Optional[:class:`~revolt.ext.commands.HelpCommand`]
Sets the custom help command, or remove it if passed ``None``
case_insensitive: :class:`bool`
Whether or not commands should be case insensitive
"""
_commands: list[Command[Self]]
def __init__(self, *args: Any, help_command: Union[HelpCommand[Self], None, revolt.utils._Missing] = revolt.utils.Missing, case_insensitive: bool = False, **kwargs: Any):
def __init__(
self,
session: aiohttp.ClientSession,
token: str,
*,
api_url: str = "https://api.revolt.chat",
max_messages: int = 5000,
bot: bool = True,
help_command: Union[HelpCommand[Self], None, revolt.utils._Missing] = revolt.utils.Missing,
case_insensitive: bool = False
):
from .help import DefaultHelpCommand, HelpCommandImpl
self.all_commands: dict[str, Command[Self]] = {} if not case_insensitive else CaseInsensitiveDict()
self.all_commands: dict[str, Command[Self]] | CaseInsensitiveDict[Command[Self]] = {} if not case_insensitive else CaseInsensitiveDict()
self.cogs: dict[str, Cog[Self]] = {}
self.extensions: dict[str, ExtensionProtocol] = {}
@@ -104,7 +134,7 @@ class CommandsClient(revolt.Client, metaclass=CommandsMeta):
else:
self.help_command = None
super().__init__(*args, **kwargs)
super().__init__(session, token, api_url=api_url, max_messages=max_messages, bot=bot)
@property
def commands(self) -> list[Command[Self]]:
+27 -15
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from typing import Any, Callable, Coroutine, Generic, Optional, TypeVar, cast
from typing import Any, Callable, Coroutine, Generic, Optional, TypeVar
from typing_extensions import ParamSpec
from revolt.errors import RevoltError
@@ -13,13 +13,13 @@ R = TypeVar("R")
__all__ = ("Cog", "CogMeta")
class CogMeta(type, Generic[ClientT_D]):
_cog_commands: list[Command[ClientT_D]]
class CogMeta(type):
_cog_commands: list[Command[Any]]
_cog_listeners: dict[str, list[str]]
qualified_name: str
def __new__(cls, name: str, bases: tuple[type, ...], attrs: dict[str, Any], *, qualified_name: Optional[str] = None, extras: dict[str, Any] | None = None) -> Any:
commands: list[Command[ClientT_D]] = []
commands: list[Command[Any]] = []
listeners: dict[str, list[str]] = {}
self = super().__new__(cls, name, bases, attrs)
@@ -29,9 +29,9 @@ class CogMeta(type, Generic[ClientT_D]):
for key, value in base.__dict__.items():
if isinstance(value, Command):
for extra_key, extra_value in extras.items():
setattr(value, extra_key, extra_value)
setattr(value, extra_key, extra_value) # type: ignore
commands.append(cast(Command[ClientT_D], value)) # cant verify generic at runtime so must cast
commands.append(value) # type: ignore
elif event_name := getattr(value, "__listener_name", None):
listeners.setdefault(event_name, []).append(key)
@@ -57,26 +57,38 @@ class Cog(Generic[ClientT_D], metaclass=CogMeta):
def _inject(self, client: ClientT_D) -> None:
client.cogs[self.qualified_name] = self
for command in self._cog_commands:
command.cog = self
try:
for command in self._cog_commands:
command.cog = self
if command.parent is None:
client.add_command(command)
if command.parent is None:
client.add_command(command)
for key, listeners in self._cog_listeners.items():
for listener_name in listeners:
client.listeners.setdefault(key, []).append(getattr(self, listener_name))
for key, listeners in self._cog_listeners.items():
for listener_name in listeners:
client.listeners.setdefault(key, []).append(getattr(self, listener_name))
except Exception as e:
self._uninject(client)
raise e
self.cog_load()
def _uninject(self, client: ClientT_D) -> None:
for name, command in client.all_commands.copy().items():
if command in self._cog_commands:
del client.all_commands[name]
try:
del client.all_commands[name]
except KeyError:
pass
for key, listeners in self._cog_listeners.items():
for listener_name in listeners:
client.listeners[key].remove(getattr(self, listener_name))
try:
client.listeners[key].remove(getattr(self, listener_name))
except ValueError:
pass
self.cog_unload()
+35 -5
View File
@@ -6,8 +6,16 @@ from contextlib import suppress
from typing import (TYPE_CHECKING, Annotated, Any, Callable, Coroutine,
Generic, Literal, Optional, Union, get_args, get_origin)
from typing_extensions import ParamSpec
import sys
from revolt.utils import maybe_coroutine
if sys.version_info >= (3, 10):
from types import UnionType
UnionTypes: tuple[Any, ...] = (Union, UnionType)
else:
UnionTypes = (Union,)
from ...utils import maybe_coroutine
from .errors import CommandOnCooldown, InvalidLiteralArgument, UnionConverterError
from .utils import ClientT_Co_D, evaluate_parameters, ClientT_Co
@@ -126,7 +134,7 @@ class Command(Generic[ClientT_Co_D]):
@classmethod
async def handle_origin(cls, context: Context[ClientT_Co_D], origin: Any, annotation: Any, arg: str) -> Any:
if origin is Union:
if origin in UnionTypes:
for converter in get_args(annotation):
try:
return await cls.convert_argument(arg, converter, context)
@@ -140,8 +148,20 @@ class Command(Generic[ClientT_Co_D]):
elif origin is Annotated:
annotated_args = get_args(annotation)
if origin := get_origin(annotated_args[0]):
return await cls.handle_origin(context, origin, annotated_args[1], arg)
if annotated_args[1] == "_revolt_greedy_marker":
real_annotation = get_args(annotated_args[0])[0]
converted_args: list[Any] = []
converted_args.append(await cls.convert_argument(arg, real_annotation, context))
for arg in context.view:
try:
converted_args.append(await cls.convert_argument(arg, real_annotation, context))
except:
context.view.undo()
break
return converted_args
else:
return await cls.convert_argument(arg, annotated_args[1], context)
@@ -175,6 +195,10 @@ class Command(Generic[ClientT_Co_D]):
except StopIteration:
if parameter.default is not parameter.empty:
arg = parameter.default
elif is_optional(parameter.annotation):
arg = None
else:
raise
@@ -192,7 +216,10 @@ class Command(Generic[ClientT_Co_D]):
except StopIteration:
if parameter.default is not parameter.empty:
arg = parameter.default
context.view.undo()
elif is_optional(parameter.annotation):
arg = None
else:
raise
@@ -251,6 +278,9 @@ class Command(Generic[ClientT_Co_D]):
return f"{' '.join(parents[::-1])} {self.name} {' '.join(parameters)}"
def is_optional(arg: Any) -> bool:
return get_origin(arg) in UnionTypes and any(arg is NoneType for arg in get_args(arg))
def command(
*,
name: Optional[str] = None,
+9 -5
View File
@@ -13,7 +13,9 @@ from .errors import (BadBoolArgument, CategoryConverterError,
if TYPE_CHECKING:
from .client import CommandsClient
__all__: tuple[str, ...] = ("bool_converter", "category_converter", "channel_converter", "user_converter", "member_converter", "IntConverter", "BoolConverter", "CategoryConverter", "UserConverter", "MemberConverter", "ChannelConverter")
T = TypeVar("T")
__all__: tuple[str, ...] = ("bool_converter", "category_converter", "channel_converter", "user_converter", "member_converter", "IntConverter", "BoolConverter", "CategoryConverter", "UserConverter", "MemberConverter", "ChannelConverter", "Greedy")
channel_regex: re.Pattern[str] = re.compile("<#([A-z0-9]{26})>")
user_regex: re.Pattern[str] = re.compile("<@([A-z0-9]{26})>")
@@ -35,7 +37,7 @@ def category_converter(arg: str, context: Context[ClientT]) -> Category:
try:
return context.server.get_category(arg)
except KeyError:
except LookupError:
try:
return utils.get(context.server.categories, name=arg)
except LookupError:
@@ -50,7 +52,7 @@ def channel_converter(arg: str, context: Context[ClientT]) -> Channel:
try:
return context.server.get_channel(arg)
except KeyError:
except LookupError:
try:
return utils.get(context.server.channels, name=arg)
except LookupError:
@@ -62,7 +64,7 @@ def user_converter(arg: str, context: Context[ClientT]) -> User:
try:
return context.client.get_user(arg)
except KeyError:
except LookupError:
try:
parts = arg.split("#")
@@ -91,7 +93,7 @@ def member_converter(arg: str, context: Context[ClientT]) -> Member:
try:
return context.server.get_member(arg)
except KeyError:
except LookupError:
try:
parts = arg.split("#")
@@ -120,3 +122,5 @@ CategoryConverter = Annotated[Category, category_converter]
UserConverter = Annotated[User, user_converter]
MemberConverter = Annotated[Member, member_converter]
ChannelConverter = Annotated[Channel, channel_converter]
Greedy = Annotated[list[T], "_revolt_greedy_marker"]
+1 -1
View File
@@ -139,6 +139,6 @@ def cooldown(rate: int, per: int, *, bucket: BucketType | Callable[[Context[Clie
func._cooldown = CooldownMapping(rate, per) # type: ignore
func._bucket = bucket # type: ignore
return func
return func # type: ignore
return inner
+2
View File
@@ -8,6 +8,7 @@ __all__ = (
"NotBotOwner",
"NotServerOwner",
"ServerOnly",
"MissingPermissionsError",
"ConverterError",
"InvalidLiteralArgument",
"BadBoolArgument",
@@ -15,6 +16,7 @@ __all__ = (
"ChannelConverterError",
"UserConverterError",
"MemberConverterError",
"UnionConverterError",
"MissingSetup",
"CommandOnCooldown"
)
-1
View File
@@ -186,7 +186,6 @@ async def help_command_impl(client: ClientT_D, context: Context[ClientT_D], *arg
break
if isinstance(command, Group):
command = cast(Group[ClientT_D], command)
parent = command
else:
payload = await help_command.create_command_help(context, command)
+10 -1
View File
@@ -1,4 +1,6 @@
from typing import Iterator
from typing_extensions import Self
from .errors import NoClosingQuote
@@ -16,7 +18,8 @@ class StringView:
def get_rest(self) -> str:
if self.should_undo:
return f"{self.temp} {''.join(self.value)}"
return f"{self.temp} {''.join(self.value)}".rstrip()
# prevent a new space appearing at end if the buffer is depleted
return "".join(self.value)
@@ -51,3 +54,9 @@ class StringView:
self.temp = output
return output
def __iter__(self) -> Self:
return self
def __next__(self) -> str:
return self.get_next_word()
+5 -3
View File
@@ -29,7 +29,9 @@ if TYPE_CHECKING:
from .types import SendableEmbed as SendableEmbedPayload
from .types import User as UserPayload
from .types import (Server, ServerBans, TextChannel, UserProfile, VoiceChannel, Member, Invite, ApiInfo, Channel, SavedMessages,
DMChannel, EmojiParent, GetServerMembers, GroupDMChannel, MessageReplyPayload, MessageWithUserData, PartialInvite, Role)
DMChannel, EmojiParent, GetServerMembers, GroupDMChannel, MessageReplyPayload, MessageWithUserData, PartialInvite, CreateRole)
from aiohttp.client import _RequestOptions
__all__ = ("HttpClient",)
@@ -49,7 +51,7 @@ class HttpClient:
async def request(self, method: Literal["GET", "POST", "PUT", "DELETE", "PATCH"], route: str, *, json: Optional[dict[str, Any]] = None, nonce: bool = True, params: Optional[dict[str, Any]] = None) -> Any:
url = f"{self.api_url}{route}"
kwargs = {}
kwargs: _RequestOptions = {}
headers = {
"User-Agent": "Revolt.py (https://github.com/revoltchat/revolt.py)",
@@ -341,7 +343,7 @@ class HttpClient:
def fetch_bans(self, server_id: str) -> Request[ServerBans]:
return self.request("GET", f"/servers/{server_id}/bans")
def create_role(self, server_id: str, name: str) -> Request[Role]:
def create_role(self, server_id: str, name: str) -> Request[CreateRole]:
return self.request("POST", f"/servers/{server_id}/roles", json={"name": name}, nonce=False)
def delete_role(self, server_id: str, role_id: str) -> Request[None]:
+2 -2
View File
@@ -149,14 +149,14 @@ class Message(Ulid):
if self.server_id:
for mention in self.raw_mentions:
try:
self.mentions.append(self.server.get_member(mention))
mentions.append(self.server.get_member(mention))
except LookupError:
pass
else:
for mention in self.raw_mentions:
try:
self.mentions.append(self.state.get_user(mention))
mentions.append(self.state.get_user(mention))
except LookupError:
pass
+2 -2
View File
@@ -178,11 +178,11 @@ class PermissionsOverwrite:
def __setattr__(self, key: str, value: Any) -> None:
if key in Permissions.FLAG_NAMES:
if key is True:
if value is True:
setattr(self._allow, key, True)
super().__setattr__(key, True)
elif key is False:
elif value is False:
setattr(self._deny, key, True)
super().__setattr__(key, False)
+2 -2
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
from datetime import datetime
from datetime import datetime, timezone
from typing import TYPE_CHECKING, cast
from revolt.enums import ChannelType
@@ -28,7 +28,7 @@ def calculate_permissions(member: Member, target: Server | Channel) -> Permissio
for role in member.roles:
permissions = (permissions | role.permissions._allow) & (~role.permissions._deny)
if member.current_timeout and member.current_timeout > datetime.now():
if member.current_timeout and member.current_timeout > datetime.now(timezone.utc):
permissions = permissions & Permissions.default_view_only()
return permissions
+4 -5
View File
@@ -203,7 +203,7 @@ class Server(Ulid):
@property
def channels(self) -> list[Channel]:
"""list[:class:`Member`] Gets all channels in the server"""
"""list[:class:`Channel`] Gets all channels in the server"""
return list(self._channels.values())
@property
@@ -310,12 +310,11 @@ class Server(Ulid):
async def set_default_permissions(self, permissions: Permissions) -> None:
"""Sets the default server permissions.
Parameters
-----------
server_permissions: Optional[:class:`ServerPermissions`]
permissions: :class:`Permissions`
The new default server permissions
channel_permissions: Optional[:class:`ChannelPermissions`]
the new default channel permissions
"""
await self.state.http.set_server_default_permissions(self.id, permissions.value)
@@ -427,7 +426,7 @@ class Server(Ulid):
"""
payload = await self.state.http.create_role(self.id, name)
return Role(payload, name, self, self.state)
return Role(payload["role"], payload["id"], self, self.state)
async def create_emoji(self, name: str, file: File, *, nsfw: bool = False) -> Emoji:
"""Creates an emoji
-2
View File
@@ -61,8 +61,6 @@ class State:
raise LookupError from None
def add_user(self, payload: UserPayload) -> User:
user = User(payload, self)
if payload.get("relationship") == "User":
+6
View File
@@ -7,6 +7,7 @@ if TYPE_CHECKING:
from .member import Member
from .message import Message
from .user import User
from .role import Role
__all__ = (
@@ -15,6 +16,7 @@ __all__ = (
"Autumn",
"GetServerMembers",
"MessageWithUserData",
"CreateRole",
)
@@ -51,3 +53,7 @@ class MessageWithUserData(TypedDict):
messages: list[Message]
members: NotRequired[list[Member]]
users: list[User]
class CreateRole(TypedDict):
id: str
role: Role
+24 -1
View File
@@ -47,14 +47,37 @@ async def maybe_coroutine(func: Callable[P, Union[R_T, Coroutine[Any, Any, R_T]]
class Ulid:
"""Base class for any revolt object with an id
Attributes
-----------
id: :class:`str`
The id of the object
"""
id: str
@property
def created_at(self) -> datetime.datetime:
"""Returns a datetime for when the object was created according to the id
Returns
--------
:class:`datetime.datetime`
The datetime of the creation date and time
"""
return ulid.from_str(self.id).timestamp().datetime
class Object(Ulid):
"""Class to mock objects with an id"""
"""Class to mock objects with an id
.. note::
This does not validate or guarantee the id is correct, you must handle this yourself
Parameters
-----------
id: :class:`str`
The ULID id to mock
"""
def __init__(self, id: str):
self.id = id
+1 -1
View File
@@ -318,7 +318,7 @@ class WebsocketHandler:
self.dispatch("member_join", member)
async def handle_memberleave(self, payload: ServerMemberLeaveEventPayload) -> None:
async def handle_servermemberleave(self, payload: ServerMemberLeaveEventPayload) -> None:
await self._wait_for_server_ready(payload["id"])
server = self.state.get_server(payload["id"])