mirror of
https://github.com/MelisaDev/melisa.git
synced 2024-09-22 19:22:01 +03:00
some requirements and tests
This commit is contained in:
parent
9036393198
commit
d53b349e95
22 changed files with 120 additions and 78 deletions
5
.flake8
Normal file
5
.flake8
Normal file
|
@ -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
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -19,6 +19,7 @@ tools/*
|
|||
|
||||
# Dev testing
|
||||
dev
|
||||
.pytest_cache
|
||||
_testing.py
|
||||
|
||||
# Packaging
|
||||
|
|
21
LICENSE.txt
Normal file
21
LICENSE.txt
Normal file
|
@ -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.
|
|
@ -1 +1 @@
|
|||
from .client import Client
|
||||
from .client import *
|
||||
|
|
|
@ -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]):
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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] = {
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
from .app import *
|
||||
from .guild import *
|
||||
from .user import *
|
||||
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from .intents import *
|
||||
from .shard import *
|
||||
from .shard import *
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
from ...utils import Snowflake
|
||||
|
||||
|
||||
class Guild:
|
||||
pass
|
||||
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
from .presence import *
|
||||
from .user import *
|
||||
from .user import *
|
||||
|
|
|
@ -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
|
||||
----------
|
||||
|
|
|
@ -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})
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
|
|
@ -7,4 +7,4 @@ T = TypeVar("T")
|
|||
|
||||
Coro = TypeVar("Coro", bound=Callable[..., Coroutine[Any, Any, Any]])
|
||||
|
||||
APINullable = Union[T, None]
|
||||
APINullable = Union[T, None]
|
||||
|
|
2
packages/dev.txt
Normal file
2
packages/dev.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
flake8==4.0.0
|
||||
pytest==6.2.5
|
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
2
tests/requirements.txt
Normal file
2
tests/requirements.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
pytest
|
||||
pytest-asyncio
|
12
tests/test_snowflakes.py
Normal file
12
tests/test_snowflakes.py
Normal file
|
@ -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
|
Loading…
Reference in a new issue