From d53b349e955a9406a4d8a75c9969a8beb00e2d6f Mon Sep 17 00:00:00 2001 From: grey-cat-1908 Date: Fri, 18 Mar 2022 14:57:47 +0300 Subject: [PATCH] some requirements and tests --- .flake8 | 5 +++++ .gitignore | 1 + LICENSE.txt | 21 ++++++++++++++++++ melisa/__init__.py | 2 +- melisa/client.py | 12 +++++++---- melisa/core/gateway.py | 25 +++++++++++----------- melisa/core/http.py | 8 ++++--- melisa/exceptions.py | 9 +++++--- melisa/models/__init__.py | 2 -- melisa/models/app/__init__.py | 2 +- melisa/models/app/shard.py | 6 +++--- melisa/models/guild/channel.py | 9 -------- melisa/models/guild/guild.py | 7 ------ melisa/models/user/__init__.py | 2 +- melisa/models/user/presence.py | 18 ++++++++++------ melisa/models/user/user.py | 39 +++++++++++++++++----------------- melisa/utils/snowflake.py | 12 ++++++----- melisa/utils/types.py | 2 +- packages/dev.txt | 2 ++ tests/__init__.py | 0 tests/requirements.txt | 2 ++ tests/test_snowflakes.py | 12 +++++++++++ 22 files changed, 120 insertions(+), 78 deletions(-) create mode 100644 .flake8 create mode 100644 LICENSE.txt create mode 100644 packages/dev.txt create mode 100644 tests/__init__.py create mode 100644 tests/requirements.txt create mode 100644 tests/test_snowflakes.py diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..4f3f4e6 --- /dev/null +++ b/.flake8 @@ -0,0 +1,5 @@ +[flake8] +ignore = D203, H501, F403, F401, C901, W503 +exclude = .git, .idea, __pycache__, docs, build, dev, dist, venv, .history, .github, examples +max-complexity = 10 +max-line-length = 100 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 10b5603..c8a73a7 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ tools/* # Dev testing dev +.pytest_cache _testing.py # Packaging diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..3c12e5b --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 - Present MelisaDev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/melisa/__init__.py b/melisa/__init__.py index 3ff722b..421945b 100644 --- a/melisa/__init__.py +++ b/melisa/__init__.py @@ -1 +1 @@ -from .client import Client +from .client import * diff --git a/melisa/client.py b/melisa/client.py index 4ce050f..615ca6f 100644 --- a/melisa/client.py +++ b/melisa/client.py @@ -25,7 +25,8 @@ class Client: activity : :class:`~models.user.presence.BotActivity` The Activity to set (on connecting) status : :class:`str` - The Status to set (on connecting). Can be generated using :class:`~models.user.presence.StatusType` + The Status to set (on connecting). + Can be generated using :class:`~models.user.presence.StatusType` Attributes ---------- @@ -82,7 +83,8 @@ class Client: """ inited_shard = Shard(self, 0, 1) - asyncio.ensure_future(inited_shard.launch(activity=self._activity, status=self._status), loop=self._loop) + asyncio.ensure_future(inited_shard.launch(activity=self._activity, + status=self._status), loop=self._loop) self._loop.run_forever() def run_shards(self, num_shards: int, *, shard_ids: List[int] = None): @@ -102,7 +104,8 @@ class Client: for shard_id in shard_ids: inited_shard = Shard(self, shard_id, num_shards) - asyncio.ensure_future(inited_shard.launch(activity=self._activity, status=self._status), loop=self._loop) + asyncio.ensure_future(inited_shard.launch(activity=self._activity, + status=self._status), loop=self._loop) self._loop.run_forever() def run_autosharded(self): @@ -115,7 +118,8 @@ class Client: for shard_id in shard_ids: inited_shard = Shard(self, shard_id, num_shards) - asyncio.ensure_future(inited_shard.launch(activity=self._activity, status=self._status), loop=self._loop) + asyncio.ensure_future(inited_shard.launch(activity=self._activity, + status=self._status), loop=self._loop) self._loop.run_forever() async def fetch_user(self, user_id: Union[Snowflake, str, int]): diff --git a/melisa/core/gateway.py b/melisa/core/gateway.py index b54bf8c..30382dd 100644 --- a/melisa/core/gateway.py +++ b/melisa/core/gateway.py @@ -69,24 +69,25 @@ class Gateway: self._last_send = 0 self.auth = { - "token": self.client._token, - "intents": self.intents, - "properties": { - "$os": sys.platform, - "$browser": "melisa", - "$device": "melisa" - }, - "compress": True, - "shard": [shard_id, num_shards], - "presence": self.generate_presence(kwargs.get("start_activity"), kwargs.get("start_status")) - } + "token": self.client._token, + "intents": self.intents, + "properties": { + "$os": sys.platform, + "$browser": "Melisa Python Library", + "$device": "Melisa Python Library" + }, + "compress": True, + "shard": [shard_id, num_shards], + "presence": self.generate_presence(kwargs.get("start_activity"), + kwargs.get("start_status"))} self._zlib: zlib._Decompress = zlib.decompressobj() self._buffer: bytearray = bytearray() async def connect(self) -> None: self.ws = await self.__session.ws_connect( - f'wss://gateway.discord.gg/?v={self.GATEWAY_VERSION}&encoding=json&compress=zlib-stream') + f'wss://gateway.discord.gg/?v={self.GATEWAY_VERSION}&encoding=json&compress=zlib-stream' + ) if self.session_id is None: await self.send_identify() diff --git a/melisa/core/http.py b/melisa/core/http.py index b6fcb54..2517004 100644 --- a/melisa/core/http.py +++ b/melisa/core/http.py @@ -1,7 +1,7 @@ from __future__ import annotations import asyncio -from typing import Dict, Optional, Union, Any +from typing import Dict, Optional from aiohttp import ClientSession, ClientResponse @@ -15,14 +15,16 @@ from melisa.exceptions import (NotModifiedError, class HTTPClient: + API_VERSION = 9 + def __init__(self, token: str, *, ttl: int = 5): - self.url: str = f"https://discord.com/api/v9" + self.url: str = f"https://discord.com/api/v{self.API_VERSION}" self.max_ttl: int = ttl headers: Dict[str, str] = { "Content-Type": "application/json", "Authorization": f"Bot {token}", - "User-Agent": f"Melisa Python Library" + "User-Agent": "Melisa Python Library" } self.__http_exceptions: Dict[int, HTTPException] = { diff --git a/melisa/exceptions.py b/melisa/exceptions.py index 3985905..6ad0ba3 100644 --- a/melisa/exceptions.py +++ b/melisa/exceptions.py @@ -14,7 +14,8 @@ class LoginFailure(ClientException): class ConnectionClosed(ClientException): - """Exception that's thrown when the gateway connection is closed for reasons that could not be handled + """Exception that's thrown when the gateway connection is closed + for reasons that could not be handled internally. """ def __init__(self, socket, *, shard_id, code=None): @@ -26,14 +27,16 @@ class ConnectionClosed(ClientException): class PrivilegedIntentsRequired(ClientException): - """Occurs when the gateway requests privileged intents, but they are not yet marked on the developer page. + """Occurs when the gateway requests privileged intents, + but they are not yet marked on the developer page. Visit to https://discord.com/developers/applications/ """ def __init__(self, shard_id): self.shard_id = shard_id - message = "Shard ID {} is requesting privileged intents that have not been explicitly enabled in the " \ + message = "Shard ID {} is requesting privileged intents " \ + "that have not been explicitly enabled in the " \ "developer portal. Please visit to https://discord.com/developers/applications/ " super().__init__(message.format(self.shard_id)) diff --git a/melisa/models/__init__.py b/melisa/models/__init__.py index 6b25cdd..8b6d316 100644 --- a/melisa/models/__init__.py +++ b/melisa/models/__init__.py @@ -1,5 +1,3 @@ from .app import * from .guild import * from .user import * - - diff --git a/melisa/models/app/__init__.py b/melisa/models/app/__init__.py index b92455f..ca13f1e 100644 --- a/melisa/models/app/__init__.py +++ b/melisa/models/app/__init__.py @@ -1,2 +1,2 @@ from .intents import * -from .shard import * \ No newline at end of file +from .shard import * diff --git a/melisa/models/app/shard.py b/melisa/models/app/shard.py index 2f1936e..e5f68a4 100644 --- a/melisa/models/app/shard.py +++ b/melisa/models/app/shard.py @@ -1,7 +1,6 @@ from __future__ import annotations -from asyncio import create_task, Task, sleep -from typing import Optional +from asyncio import create_task, sleep from ...core.gateway import Gateway from ..user import BotActivity @@ -28,7 +27,8 @@ class Shard: @property def latency(self) -> float: """:class:`float`: - Measures latency between a HEARTBEAT command and a HEARTBEAT_ACK event in seconds for this shard""" + Measures latency between a HEARTBEAT command + and a HEARTBEAT_ACK event in seconds for this shard""" return self._gateway.latency async def launch(self, **kwargs) -> Shard: diff --git a/melisa/models/guild/channel.py b/melisa/models/guild/channel.py index 4010d93..9d48db4 100644 --- a/melisa/models/guild/channel.py +++ b/melisa/models/guild/channel.py @@ -1,10 +1 @@ from __future__ import annotations - -from enum import IntEnum -from dataclasses import dataclass -from typing import Optional - -from ...utils.api_model import APIModelBase -from ...utils.types import APINullable -from ...utils.snowflake import Snowflake - diff --git a/melisa/models/guild/guild.py b/melisa/models/guild/guild.py index 712e9c6..e69de29 100644 --- a/melisa/models/guild/guild.py +++ b/melisa/models/guild/guild.py @@ -1,7 +0,0 @@ -from ...utils import Snowflake - - -class Guild: - pass - - diff --git a/melisa/models/user/__init__.py b/melisa/models/user/__init__.py index 1d86638..2ef620a 100644 --- a/melisa/models/user/__init__.py +++ b/melisa/models/user/__init__.py @@ -1,2 +1,2 @@ from .presence import * -from .user import * \ No newline at end of file +from .user import * diff --git a/melisa/models/user/presence.py b/melisa/models/user/presence.py index 81a9ece..3d018e0 100644 --- a/melisa/models/user/presence.py +++ b/melisa/models/user/presence.py @@ -24,13 +24,15 @@ class ActivityType(IntEnum): GAME: Playing {name} (Playing Rocket League) STREAMING: - Streaming {details} (Streaming Rocket League) It supports only YouTube and Twitch + Streaming {details} (Streaming Rocket League) + It supports only YouTube and Twitch LISTENING: Listening to {name} (Listening to Spotify) WATCHING: Watching {name} (Watching YouTube Together) CUSTOM: - {emoji} {name} (":smiley: I am cool") (THIS ACTIVITY IS NOT SUPPORTED FOR BOTS) + {emoji} {name} (":smiley: I am cool") + (THIS ACTIVITY IS NOT SUPPORTED FOR BOTS) COMPETING: Competing in {name} (Competing in Arena World Champions) """ @@ -100,11 +102,13 @@ class ActivityAssets(BasePresence, APIModelBase): Attributes ---------- large_image: Optional[:class:`str`] - (Large Image) Activity asset images are arbitrary strings which usually contain snowflake IDs + (Large Image) Activity asset images are arbitrary strings + which usually contain snowflake IDs large_text: Optional[:class:`str`] text displayed when hovering over the large image of the activity small_image: Optional[:class:`str`] - (Small Image) Activity asset images are arbitrary strings which usually contain snowflake IDs + (Small Image) Activity asset images are arbitrary strings + which usually contain snowflake IDs small_text: Optional[:class:`str`] text displayed when hovering over the small image of the activity """ @@ -153,8 +157,10 @@ class ActivityFlags(BasePresence, APIModelBase): @dataclass(repr=False) class ActivityButton(BasePresence, APIModelBase): - """When received over the gateway, the buttons field is an array of strings, which are the button labels. Bots - cannot access a user's activity button URLs. When sending, the buttons field must be an array of the below + """When received over the gateway, the buttons field is an array of strings, + which are the button labels. Bots + cannot access a user's activity button URLs. + When sending, the buttons field must be an array of the below object: Attributes ---------- diff --git a/melisa/models/user/user.py b/melisa/models/user/user.py index b0db4d6..e1e48cc 100644 --- a/melisa/models/user/user.py +++ b/melisa/models/user/user.py @@ -11,7 +11,7 @@ from ...utils.snowflake import Snowflake class PremiumTypes(IntEnum): """Premium types denote the level of premium a user has. - + Attributes ---------- NITRO: @@ -32,39 +32,39 @@ class PremiumTypes(IntEnum): class UserFlags(IntEnum): """Profile Icons - + Attributes ---------- NONE: None STAFF: - Discord Employee + Discord Employee PARTNER: Partnered Server Owner HYPESQUAD: - HypeSquad Events Coordinator + HypeSquad Events Coordinator BUG_HUNTER_LEVEL_1: Bug Hunter Level 1 HYPESQUAD_ONLINE_HOUSE_1: - House Bravery Member + House Bravery Member HYPESQUAD_ONLINE_HOUSE_2: - House Brilliance Member + House Brilliance Member HYPESQUAD_ONLINE_HOUSE_3: - House Balance Member + House Balance Member PREMIUM_EARLY_SUPPORTER: - Early Nitro Supporter + Early Nitro Supporter TEAM_PSEUDO_USER: - User is a team + User is a team BUG_HUNTER_LEVEL_2: - Bug Hunter Level 2 + Bug Hunter Level 2 VERIFIED_BOT: - Verified Bot + Verified Bot VERIFIED_DEVELOPER: - Early Verified Bot Developer + Early Verified Bot Developer CERTIFIED_MODERATOR: - Discord Certified Moderator + Discord Certified Moderator BOT_HTTP_INTERACTIONS: - Bot uses only HTTP interactions and is shown in the online member list + Bot uses only HTTP interactions and is shown in the online member list """ NONE = 0 @@ -89,13 +89,13 @@ class UserFlags(IntEnum): class VisibilityTypes(IntEnum): """The type of connection visibility. - + Attributes ---------- None: - invisible to everyone except the user themselves + invisible to everyone except the user themselves Everyone: - visible to everyone + visible to everyone """ NONE = 0 @@ -108,7 +108,7 @@ class VisibilityTypes(IntEnum): @dataclass(repr=False) class User(APIModelBase): """User Structure - + Attributes ---------- id: :class:`~melisa.utils.types.Snowflake` @@ -199,5 +199,4 @@ class User(APIModelBase): # ToDo: Add docstrings # ToDo: Add checking this channel in cache return await self._http.post( - "/users/@me/channels", data={"recipient_id": self.id} - ) + "/users/@me/channels", data={"recipient_id": self.id}) diff --git a/melisa/utils/snowflake.py b/melisa/utils/snowflake.py index fef1922..f30e6fe 100644 --- a/melisa/utils/snowflake.py +++ b/melisa/utils/snowflake.py @@ -3,11 +3,13 @@ from __future__ import annotations class Snowflake(int): """ - Discord utilizes Twitter's snowflake format for uniquely identifiable descriptors (IDs). These IDs are guaranteed - to be unique across all of Discord, except in some unique scenarios in which child objects share their parent's - ID. Because Snowflake IDs are up to 64 bits in size (e.g. a uint64), they are always returned as strings in the - HTTP API to prevent integer overflows in some languages. See Gateway ETF/JSON for more information regarding - Gateway encoding. + Discord utilizes Twitter's snowflake format for uniquely identifiable descriptors (IDs). + These IDs are guaranteed to be unique across all of Discord, + except in some unique scenarios in which child objects share their parent's ID. + Because Snowflake IDs are up to 64 bits in size (e.g. a uint64), + they are always returned as strings in the HTTP API + to prevent integer overflows in some languages. + See Gateway ETF/JSON for more information regarding Gateway encoding. Read more here: https://discord.com/developers/docs/reference#snowflakes """ diff --git a/melisa/utils/types.py b/melisa/utils/types.py index d3e5214..248a11c 100644 --- a/melisa/utils/types.py +++ b/melisa/utils/types.py @@ -7,4 +7,4 @@ T = TypeVar("T") Coro = TypeVar("Coro", bound=Callable[..., Coroutine[Any, Any, Any]]) -APINullable = Union[T, None] \ No newline at end of file +APINullable = Union[T, None] diff --git a/packages/dev.txt b/packages/dev.txt new file mode 100644 index 0000000..2cbec30 --- /dev/null +++ b/packages/dev.txt @@ -0,0 +1,2 @@ +flake8==4.0.0 +pytest==6.2.5 \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..ee4ba01 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,2 @@ +pytest +pytest-asyncio diff --git a/tests/test_snowflakes.py b/tests/test_snowflakes.py new file mode 100644 index 0000000..e648f45 --- /dev/null +++ b/tests/test_snowflakes.py @@ -0,0 +1,12 @@ +from melisa.utils import Snowflake + + +class TestSnowflakes: + def test_assertions(self): + assert Snowflake(2) == 2 + assert Snowflake("2") == 2 + + def test_timestamps(self): + sflake = Snowflake(175928847299117063) + assert sflake.timestamp == 41944705796 + assert sflake.unix == 1462015105796