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 testing
|
||||||
dev
|
dev
|
||||||
|
.pytest_cache
|
||||||
_testing.py
|
_testing.py
|
||||||
|
|
||||||
# Packaging
|
# 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`
|
activity : :class:`~models.user.presence.BotActivity`
|
||||||
The Activity to set (on connecting)
|
The Activity to set (on connecting)
|
||||||
status : :class:`str`
|
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
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
@ -82,7 +83,8 @@ class Client:
|
||||||
"""
|
"""
|
||||||
inited_shard = Shard(self, 0, 1)
|
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()
|
self._loop.run_forever()
|
||||||
|
|
||||||
def run_shards(self, num_shards: int, *, shard_ids: List[int] = None):
|
def run_shards(self, num_shards: int, *, shard_ids: List[int] = None):
|
||||||
|
@ -102,7 +104,8 @@ class Client:
|
||||||
for shard_id in shard_ids:
|
for shard_id in shard_ids:
|
||||||
inited_shard = Shard(self, shard_id, num_shards)
|
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()
|
self._loop.run_forever()
|
||||||
|
|
||||||
def run_autosharded(self):
|
def run_autosharded(self):
|
||||||
|
@ -115,7 +118,8 @@ class Client:
|
||||||
for shard_id in shard_ids:
|
for shard_id in shard_ids:
|
||||||
inited_shard = Shard(self, shard_id, num_shards)
|
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()
|
self._loop.run_forever()
|
||||||
|
|
||||||
async def fetch_user(self, user_id: Union[Snowflake, str, int]):
|
async def fetch_user(self, user_id: Union[Snowflake, str, int]):
|
||||||
|
|
|
@ -69,24 +69,25 @@ class Gateway:
|
||||||
self._last_send = 0
|
self._last_send = 0
|
||||||
|
|
||||||
self.auth = {
|
self.auth = {
|
||||||
"token": self.client._token,
|
"token": self.client._token,
|
||||||
"intents": self.intents,
|
"intents": self.intents,
|
||||||
"properties": {
|
"properties": {
|
||||||
"$os": sys.platform,
|
"$os": sys.platform,
|
||||||
"$browser": "melisa",
|
"$browser": "Melisa Python Library",
|
||||||
"$device": "melisa"
|
"$device": "Melisa Python Library"
|
||||||
},
|
},
|
||||||
"compress": True,
|
"compress": True,
|
||||||
"shard": [shard_id, num_shards],
|
"shard": [shard_id, num_shards],
|
||||||
"presence": self.generate_presence(kwargs.get("start_activity"), kwargs.get("start_status"))
|
"presence": self.generate_presence(kwargs.get("start_activity"),
|
||||||
}
|
kwargs.get("start_status"))}
|
||||||
|
|
||||||
self._zlib: zlib._Decompress = zlib.decompressobj()
|
self._zlib: zlib._Decompress = zlib.decompressobj()
|
||||||
self._buffer: bytearray = bytearray()
|
self._buffer: bytearray = bytearray()
|
||||||
|
|
||||||
async def connect(self) -> None:
|
async def connect(self) -> None:
|
||||||
self.ws = await self.__session.ws_connect(
|
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:
|
if self.session_id is None:
|
||||||
await self.send_identify()
|
await self.send_identify()
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from typing import Dict, Optional, Union, Any
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from aiohttp import ClientSession, ClientResponse
|
from aiohttp import ClientSession, ClientResponse
|
||||||
|
|
||||||
|
@ -15,14 +15,16 @@ from melisa.exceptions import (NotModifiedError,
|
||||||
|
|
||||||
|
|
||||||
class HTTPClient:
|
class HTTPClient:
|
||||||
|
API_VERSION = 9
|
||||||
|
|
||||||
def __init__(self, token: str, *, ttl: int = 5):
|
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
|
self.max_ttl: int = ttl
|
||||||
|
|
||||||
headers: Dict[str, str] = {
|
headers: Dict[str, str] = {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"Authorization": f"Bot {token}",
|
"Authorization": f"Bot {token}",
|
||||||
"User-Agent": f"Melisa Python Library"
|
"User-Agent": "Melisa Python Library"
|
||||||
}
|
}
|
||||||
|
|
||||||
self.__http_exceptions: Dict[int, HTTPException] = {
|
self.__http_exceptions: Dict[int, HTTPException] = {
|
||||||
|
|
|
@ -14,7 +14,8 @@ class LoginFailure(ClientException):
|
||||||
|
|
||||||
|
|
||||||
class ConnectionClosed(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. """
|
internally. """
|
||||||
|
|
||||||
def __init__(self, socket, *, shard_id, code=None):
|
def __init__(self, socket, *, shard_id, code=None):
|
||||||
|
@ -26,14 +27,16 @@ class ConnectionClosed(ClientException):
|
||||||
|
|
||||||
|
|
||||||
class PrivilegedIntentsRequired(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/
|
Visit to https://discord.com/developers/applications/
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, shard_id):
|
def __init__(self, shard_id):
|
||||||
self.shard_id = 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/ "
|
"developer portal. Please visit to https://discord.com/developers/applications/ "
|
||||||
|
|
||||||
super().__init__(message.format(self.shard_id))
|
super().__init__(message.format(self.shard_id))
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
from .app import *
|
from .app import *
|
||||||
from .guild import *
|
from .guild import *
|
||||||
from .user import *
|
from .user import *
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from asyncio import create_task, Task, sleep
|
from asyncio import create_task, sleep
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from ...core.gateway import Gateway
|
from ...core.gateway import Gateway
|
||||||
from ..user import BotActivity
|
from ..user import BotActivity
|
||||||
|
@ -28,7 +27,8 @@ class Shard:
|
||||||
@property
|
@property
|
||||||
def latency(self) -> float:
|
def latency(self) -> float:
|
||||||
""":class:`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
|
return self._gateway.latency
|
||||||
|
|
||||||
async def launch(self, **kwargs) -> Shard:
|
async def launch(self, **kwargs) -> Shard:
|
||||||
|
|
|
@ -1,10 +1 @@
|
||||||
from __future__ import annotations
|
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
|
|
||||||
|
|
||||||
|
|
|
@ -24,13 +24,15 @@ class ActivityType(IntEnum):
|
||||||
GAME:
|
GAME:
|
||||||
Playing {name} (Playing Rocket League)
|
Playing {name} (Playing Rocket League)
|
||||||
STREAMING:
|
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:
|
||||||
Listening to {name} (Listening to Spotify)
|
Listening to {name} (Listening to Spotify)
|
||||||
WATCHING:
|
WATCHING:
|
||||||
Watching {name} (Watching YouTube Together)
|
Watching {name} (Watching YouTube Together)
|
||||||
CUSTOM:
|
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:
|
||||||
Competing in {name} (Competing in Arena World Champions)
|
Competing in {name} (Competing in Arena World Champions)
|
||||||
"""
|
"""
|
||||||
|
@ -100,11 +102,13 @@ class ActivityAssets(BasePresence, APIModelBase):
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
large_image: Optional[:class:`str`]
|
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`]
|
large_text: Optional[:class:`str`]
|
||||||
text displayed when hovering over the large image of the activity
|
text displayed when hovering over the large image of the activity
|
||||||
small_image: Optional[:class:`str`]
|
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`]
|
small_text: Optional[:class:`str`]
|
||||||
text displayed when hovering over the small image of the activity
|
text displayed when hovering over the small image of the activity
|
||||||
"""
|
"""
|
||||||
|
@ -153,8 +157,10 @@ class ActivityFlags(BasePresence, APIModelBase):
|
||||||
|
|
||||||
@dataclass(repr=False)
|
@dataclass(repr=False)
|
||||||
class ActivityButton(BasePresence, APIModelBase):
|
class ActivityButton(BasePresence, APIModelBase):
|
||||||
"""When received over the gateway, the buttons field is an array of strings, which are the button labels. Bots
|
"""When received over the gateway, the buttons field is an array of strings,
|
||||||
cannot access a user's activity button URLs. When sending, the buttons field must be an array of the below
|
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:
|
object:
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
|
|
|
@ -38,33 +38,33 @@ class UserFlags(IntEnum):
|
||||||
NONE:
|
NONE:
|
||||||
None
|
None
|
||||||
STAFF:
|
STAFF:
|
||||||
Discord Employee
|
Discord Employee
|
||||||
PARTNER:
|
PARTNER:
|
||||||
Partnered Server Owner
|
Partnered Server Owner
|
||||||
HYPESQUAD:
|
HYPESQUAD:
|
||||||
HypeSquad Events Coordinator
|
HypeSquad Events Coordinator
|
||||||
BUG_HUNTER_LEVEL_1:
|
BUG_HUNTER_LEVEL_1:
|
||||||
Bug Hunter Level 1
|
Bug Hunter Level 1
|
||||||
HYPESQUAD_ONLINE_HOUSE_1:
|
HYPESQUAD_ONLINE_HOUSE_1:
|
||||||
House Bravery Member
|
House Bravery Member
|
||||||
HYPESQUAD_ONLINE_HOUSE_2:
|
HYPESQUAD_ONLINE_HOUSE_2:
|
||||||
House Brilliance Member
|
House Brilliance Member
|
||||||
HYPESQUAD_ONLINE_HOUSE_3:
|
HYPESQUAD_ONLINE_HOUSE_3:
|
||||||
House Balance Member
|
House Balance Member
|
||||||
PREMIUM_EARLY_SUPPORTER:
|
PREMIUM_EARLY_SUPPORTER:
|
||||||
Early Nitro Supporter
|
Early Nitro Supporter
|
||||||
TEAM_PSEUDO_USER:
|
TEAM_PSEUDO_USER:
|
||||||
User is a team
|
User is a team
|
||||||
BUG_HUNTER_LEVEL_2:
|
BUG_HUNTER_LEVEL_2:
|
||||||
Bug Hunter Level 2
|
Bug Hunter Level 2
|
||||||
VERIFIED_BOT:
|
VERIFIED_BOT:
|
||||||
Verified Bot
|
Verified Bot
|
||||||
VERIFIED_DEVELOPER:
|
VERIFIED_DEVELOPER:
|
||||||
Early Verified Bot Developer
|
Early Verified Bot Developer
|
||||||
CERTIFIED_MODERATOR:
|
CERTIFIED_MODERATOR:
|
||||||
Discord Certified Moderator
|
Discord Certified Moderator
|
||||||
BOT_HTTP_INTERACTIONS:
|
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
|
NONE = 0
|
||||||
|
@ -93,9 +93,9 @@ class VisibilityTypes(IntEnum):
|
||||||
Attributes
|
Attributes
|
||||||
----------
|
----------
|
||||||
None:
|
None:
|
||||||
invisible to everyone except the user themselves
|
invisible to everyone except the user themselves
|
||||||
Everyone:
|
Everyone:
|
||||||
visible to everyone
|
visible to everyone
|
||||||
"""
|
"""
|
||||||
|
|
||||||
NONE = 0
|
NONE = 0
|
||||||
|
@ -199,5 +199,4 @@ class User(APIModelBase):
|
||||||
# ToDo: Add docstrings
|
# ToDo: Add docstrings
|
||||||
# ToDo: Add checking this channel in cache
|
# ToDo: Add checking this channel in cache
|
||||||
return await self._http.post(
|
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):
|
class Snowflake(int):
|
||||||
"""
|
"""
|
||||||
Discord utilizes Twitter's snowflake format for uniquely identifiable descriptors (IDs). These IDs are guaranteed
|
Discord utilizes Twitter's snowflake format for uniquely identifiable descriptors (IDs).
|
||||||
to be unique across all of Discord, except in some unique scenarios in which child objects share their parent's
|
These IDs are guaranteed to be unique across all of Discord,
|
||||||
ID. Because Snowflake IDs are up to 64 bits in size (e.g. a uint64), they are always returned as strings in the
|
except in some unique scenarios in which child objects share their parent's ID.
|
||||||
HTTP API to prevent integer overflows in some languages. See Gateway ETF/JSON for more information regarding
|
Because Snowflake IDs are up to 64 bits in size (e.g. a uint64),
|
||||||
Gateway encoding.
|
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
|
Read more here: https://discord.com/developers/docs/reference#snowflakes
|
||||||
"""
|
"""
|
||||||
|
|
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