diff --git a/revolt/channel.py b/revolt/channel.py index ac6e3be..833cf10 100755 --- a/revolt/channel.py +++ b/revolt/channel.py @@ -88,7 +88,7 @@ class Channel(Ulid): async def _get_channel_id(self) -> str: return self.id - def _update(self, **_): + def _update(self, **_: Any): pass async def delete(self): diff --git a/revolt/embed.py b/revolt/embed.py index 6a27c43..3e69489 100755 --- a/revolt/embed.py +++ b/revolt/embed.py @@ -1,6 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Optional, Union +from typing import TYPE_CHECKING, Optional, TypedDict, Union + +from typing_extensions import Unpack, NotRequired from .asset import Asset from .enums import EmbedType @@ -69,8 +71,16 @@ def to_embed(payload: EmbedPayload, state: State) -> Embed: else: return NoneEmbed() +class EmbedParameters(TypedDict): + title: NotRequired[str] + description: NotRequired[str] + media: NotRequired[str] + icon_url: NotRequired[str] + colour: NotRequired[str] + url: NotRequired[str] + class SendableEmbed: - def __init__(self, **attrs: dict[str, Any]): + def __init__(self, **attrs: Unpack[EmbedParameters]): self.title: Optional[str] = None self.description: Optional[str] = None self.media: Optional[str] = None diff --git a/revolt/ext/commands/checks.py b/revolt/ext/commands/checks.py index 830c0be..454db3c 100755 --- a/revolt/ext/commands/checks.py +++ b/revolt/ext/commands/checks.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Callable, Coroutine, TypeVar, Union +from typing import Any, Callable, Coroutine, TypeVar, Union, cast from .command import Command from .context import Context @@ -14,7 +14,6 @@ T = TypeVar("T", Callable[..., Any], Command) Check = Callable[[Context[ClientT]], Union[Any, Coroutine[Any, Any, Any]]] - def check(check: Check[ClientT]): """A decorator for adding command checks @@ -25,7 +24,8 @@ def check(check: Check[ClientT]): """ def inner(func: T) -> T: if isinstance(func, Command): - func.checks.append(check) + command = cast(Command[ClientT], func) # cant verify generic at runtime so must cast + command.checks.append(check) else: checks = getattr(func, "_checks", []) checks.append(check) diff --git a/revolt/ext/commands/client.py b/revolt/ext/commands/client.py index 23be5b4..8f51b2c 100755 --- a/revolt/ext/commands/client.py +++ b/revolt/ext/commands/client.py @@ -56,8 +56,11 @@ class CaseInsensitiveDict(dict[str, V]): def __getitem__(self, key: str) -> V: return super().__getitem__(key.casefold()) - def __contains__(self, key: str) -> bool: - return super().__contains__(key.casefold()) + def __contains__(self, key: object) -> bool: + if isinstance(key, str): + return super().__contains__(key.casefold()) + else: + return False @overload def get(self, key: str) -> V | None: @@ -67,7 +70,7 @@ class CaseInsensitiveDict(dict[str, V]): def get(self, key: str, default: V | T) -> V | T: ... - def get(self, key: str, default: Optional[T] = None) -> V | T: + def get(self, key: str, default: Optional[T] = None) -> V | T | None: return super().get(key.casefold(), default) def __delitem__(self, key: str) -> None: @@ -92,9 +95,11 @@ class CommandsClient(revolt.Client, metaclass=CommandsMeta): for alias in command.aliases: self.all_commands[alias] = command - if not isinstance(help_command, revolt.utils._Missing): + if help_command is not None: self.help_command = help_command or DefaultHelpCommand[Self]() self.add_command(HelpCommandImpl(self)) + else: + self.help_command = None super().__init__(*args, **kwargs) diff --git a/revolt/ext/commands/cog.py b/revolt/ext/commands/cog.py index 7d0ec3e..6a4946c 100755 --- a/revolt/ext/commands/cog.py +++ b/revolt/ext/commands/cog.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import Any, Generic, Optional +from typing import Any, Generic, Optional, cast from .command import Command from .utils import ClientT @@ -19,7 +19,7 @@ class CogMeta(type, Generic[ClientT]): for base in reversed(self.__mro__): for value in base.__dict__.values(): if isinstance(value, Command): - commands.append(value) + commands.append(cast(Command[ClientT], value)) # cant verify generic at runtime so must cast self._commands = commands diff --git a/revolt/ext/commands/help.py b/revolt/ext/commands/help.py index 5647143..7f05012 100755 --- a/revolt/ext/commands/help.py +++ b/revolt/ext/commands/help.py @@ -165,11 +165,16 @@ class HelpCommandImpl(Command[ClientT]): async def help_command_impl(self: ClientT, context: Context[ClientT], *arguments: str): - filtered_commands = await context.client.help_command.filter_commands(context, self.commands) - commands = await self.help_command.group_commands(context, filtered_commands) + help_command = self.help_command + + if not help_command: + return + + filtered_commands = await help_command.filter_commands(context, self.commands) + commands = await help_command.group_commands(context, filtered_commands) if not arguments: - payload = await self.help_command.create_bot_help(context, commands) + payload = await help_command.create_bot_help(context, commands) else: command_name = arguments[0] @@ -178,14 +183,14 @@ async def help_command_impl(self: ClientT, context: Context[ClientT], *arguments except KeyError: cog = self.cogs.get(command_name) if cog: - payload = await self.help_command.create_cog_help(context, cog) + payload = await help_command.create_cog_help(context, cog) else: - return await self.help_command.handle_no_command_found(context, command_name) + return await help_command.handle_no_command_found(context, command_name) else: if isinstance(command, Group): - payload = await self.help_command.create_group_help(context, command) + payload = await help_command.create_group_help(context, command) else: - payload = await self.help_command.create_command_help(context, command) + payload = await help_command.create_command_help(context, command) msg_payload: MessagePayload @@ -196,4 +201,4 @@ async def help_command_impl(self: ClientT, context: Context[ClientT], *arguments else: msg_payload = payload - await self.help_command.send_help_command(context, msg_payload) + await help_command.send_help_command(context, msg_payload) diff --git a/revolt/member.py b/revolt/member.py index db08bce..bb9e1d8 100755 --- a/revolt/member.py +++ b/revolt/member.py @@ -38,9 +38,8 @@ class Member(User): user = state.get_user(data["_id"]["user"]) # due to not having a user payload and only a user object we have to manually add all the attributes instead of calling User.__init__ - self._members = [] flattern_user(self, user) - user._members.append(self) + user._members.add(self) self.state = state diff --git a/revolt/types/__init__.py b/revolt/types/__init__.py index 203ef74..cdde982 100755 --- a/revolt/types/__init__.py +++ b/revolt/types/__init__.py @@ -7,7 +7,7 @@ from .http import * from .invite import * from .member import * from .message import * -from .permissions import Overwrite as Overwrite +from .permissions import * from .role import * from .server import * from .user import * diff --git a/revolt/types/member.py b/revolt/types/member.py index 9f1d0b0..0acf70b 100755 --- a/revolt/types/member.py +++ b/revolt/types/member.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from .file import File -__all__ = ("Member",) +__all__ = ("Member", "MemberID") class MemberID(TypedDict): server: str diff --git a/revolt/user.py b/revolt/user.py index 5abe012..447257f 100755 --- a/revolt/user.py +++ b/revolt/user.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING, NamedTuple, Optional, Union +from weakref import WeakSet from .asset import Asset, PartialAsset from .channel import DMChannel @@ -65,7 +66,7 @@ class User(Messageable, Ulid): def __init__(self, data: UserPayload, state: State): self.state = state - self._members: list[Member] = [] # we store all member versions of this user to avoid having to check every guild when needing to update. + self._members: WeakSet[Member] = WeakSet() # we store all member versions of this user to avoid having to check every guild when needing to update. self.id = data["_id"] self.original_name = data["username"] self.dm_channel = None @@ -160,8 +161,9 @@ class User(Messageable, Ulid): # update user infomation for all members - for member in self._members: - User._update(member, status=status, profile=profile, avatar=avatar, online=online) + if self.__class__ is User: + for member in self._members: + User._update(member, status=status, profile=profile, avatar=avatar, online=online) async def default_avatar(self) -> bytes: """Returns the default avatar for this user diff --git a/revolt/utils.py b/revolt/utils.py index 5e1e4ee..921187c 100755 --- a/revolt/utils.py +++ b/revolt/utils.py @@ -2,7 +2,7 @@ import inspect import datetime from contextlib import asynccontextmanager from operator import attrgetter -from typing import Any, Callable, Coroutine, Iterable, TypeVar, Union +from typing import Any, Callable, Coroutine, Iterable, Literal, TypeVar, Union import ulid from aiohttp import ClientSession @@ -14,6 +14,9 @@ class _Missing: def __repr__(self): return "" + def __bool__(self) -> Literal[False]: + return False + Missing = _Missing() T = TypeVar("T") diff --git a/revolt/websocket.py b/revolt/websocket.py index ee52c4c..6de780e 100755 --- a/revolt/websocket.py +++ b/revolt/websocket.py @@ -1,17 +1,16 @@ from __future__ import annotations import asyncio +import time import logging from copy import copy -from traceback import print_exception from typing import TYPE_CHECKING, Callable, cast from .channel import GroupDMChannel, TextChannel, VoiceChannel from .enums import RelationshipType -from .types import (ChannelCreateEventPayload, ChannelDeleteEventPayload, - ChannelDeleteTypingEventPayload, - ChannelStartTypingEventPayload, ChannelUpdateEventPayload) +from .types import Member as MemberPayload from .types import Message as MessagePayload +from .types import MemberID as MemberIDPayload from .types import (MessageDeleteEventPayload, MessageUpdateEventPayload, ServerDeleteEventPayload, ServerMemberJoinEventPayload, ServerMemberLeaveEventPayload, @@ -19,7 +18,9 @@ from .types import (MessageDeleteEventPayload, MessageUpdateEventPayload, ServerMemberUpdateEventPayload, ServerRoleDeleteEventPayload, ServerRoleUpdateEventPayload, ServerUpdateEventPayload, UserRelationshipEventPayload, - UserUpdateEventPayload, MessageReactEventPayload, MessageUnreactEventPayload, MessageRemoveReactionEventPayload) + UserUpdateEventPayload, MessageReactEventPayload, MessageUnreactEventPayload, MessageRemoveReactionEventPayload, ChannelCreateEventPayload, ChannelDeleteEventPayload, + ChannelDeleteTypingEventPayload, + ChannelStartTypingEventPayload, ChannelUpdateEventPayload) from .user import Status, UserProfile from . import utils @@ -290,7 +291,7 @@ class WebsocketHandler: self.dispatch("member_update", old_member, member) async def handle_servermemberjoin(self, payload: ServerMemberJoinEventPayload): - member = self.state.add_member(payload["id"], {"_id": {"server": payload["id"], "user": payload["user"]}}) + member = self.state.add_member(payload["id"], MemberPayload(_id=MemberIDPayload(server=payload["id"], user=payload["user"]), joined_at=int(time.time()))) # revolt doesnt give us the joined at time user = await self.state.http.fetch_user(member.id) self.state.add_user(user) @@ -423,13 +424,12 @@ class WebsocketHandler: async for msg in self.websocket: if use_msgpack: - payload = msgpack.unpackb(msg.data) + data: bytes = cast(bytes, msg.data) + + payload = msgpack.unpackb(data) else: - payload = json.loads(msg.data) + data = cast(str, msg.data) + + payload = json.loads(data) self.loop.create_task(self.handle_event(payload)) - # task.add_done_callback(task_done) - -def task_done(task: asyncio.Task[None]): - if exception := task.exception(): - print_exception(type(exception), exception, exception.__traceback__)