Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
timeout-minutes: 15
strategy:
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]
database: ["mariadb", "sqlite", "mysql"]

steps:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Unofficial discord backend implementation in python.

# Setup
**Requirements:**
- Python 3.9+
- Python 3.10+
- Poetry (Optional)

**Setup**:
Expand Down
4 changes: 2 additions & 2 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,5 +67,5 @@
- [x] Create, edit, delete
- [ ] Commands:
- [x] Slash commands
- [ ] User commands (?)
- [ ] Message commands (?)
- [ ] User commands (?, I forgot)
- [ ] Message commands (?, I forgot)
1,079 changes: 537 additions & 542 deletions poetry.lock

Large diffs are not rendered by default.

30 changes: 14 additions & 16 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "yepcord-server"
version = "1.0.0b4"
version = "1.0.0b5"
description = "YEPCord - Free open source selfhostable fully discord-compatible chat"
authors = ["RuslanUC <dev_ruslan_uc@protonmail.com>"]
license = "AGPL-3.0"
Expand All @@ -22,7 +22,6 @@ classifiers = [
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
Expand All @@ -36,31 +35,30 @@ classifiers = [
yepcord = "yepcord.cli:main"

[tool.poetry.dependencies]
python = "^3.9"
quart = "0.19.5"
aiofiles = "23.2.1"
python = "^3.10"
quart = "0.19.6"
aiofiles = "24.1.0"
websockets = "12.0"
uvicorn = "0.29.0"
uvicorn = "0.30.3"
aiohttp = "3.9.5"
python-magic = "0.4.27"
pillow = "10.3.0"
pillow = "10.4.0"
protobuf = "4.25.3"
python-dateutil = "2.9.0.post0"
cryptography = "42.0.7"
emoji = "2.11.1"
six = "1.16.0"
bcrypt = "4.1.3"
quart-schema = "0.19.1"
pydantic = "2.7.1"
cryptography = "43.0.0"
emoji = "2.12.1"
bcrypt = "4.2.0"
quart-schema = "0.20.0"
pydantic = "2.8.2"
werkzeug = "3.0.3"
aioftp = "0.22.3"
orjson = "3.10.3"
orjson = "3.10.6"
mailers = {version = "3.0.5", extras = ["smtp"]}
redis = ">=4.6.0"
click = "8.1.7"
maxminddb = "2.6.1"
maxminddb = "2.6.2"
wget = "3.2"
tortoise-orm = {extras = ["aiosqlite", "asyncmy", "accel"], version = "^0.20.0"}
tortoise-orm = {extras = ["aiosqlite", "asyncmy", "accel"], version = "^0.21.5"}
uvloop = "0.19.0"
async-timeout = "^4.0.3"
aerich = "^0.7.2"
Expand Down
17 changes: 8 additions & 9 deletions tests/api/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from contextlib import asynccontextmanager
from datetime import datetime, timedelta
from hashlib import sha256
from typing import Optional, Union

from quart.typing import TestWebsocketConnectionProtocol

Expand All @@ -17,7 +16,7 @@
TestClientType = _app.test_client_class


async def create_user(app: TestClientType, email: str, password: str, username: str, *, exp_code: int=200) -> Optional[str]:
async def create_user(app: TestClientType, email: str, password: str, username: str, *, exp_code: int=200) -> str | None:
response = await app.post('/api/v9/auth/register', json={
"username": username,
"email": email,
Expand Down Expand Up @@ -254,9 +253,9 @@ class RemoteAuthClient:
def __init__(self, on_fingerprint=None, on_userdata=None, on_token=None, on_cancel=None, on_pending_login=None):
from cryptography.hazmat.primitives.asymmetric import rsa

self.privKey: Optional[rsa.RSAPrivateKey] = None
self.pubKey: Optional[rsa.RSAPublicKey] = None
self.pubKeyS: Optional[str] = None
self.privKey: rsa.RSAPrivateKey | None = None
self.pubKey: rsa.RSAPublicKey | None = None
self.pubKeyS: str | None = None

self.heartbeatTask = None

Expand All @@ -266,7 +265,7 @@ def __init__(self, on_fingerprint=None, on_userdata=None, on_token=None, on_canc
self.on_cancel = on_cancel
self.on_pending_login = on_pending_login

self.results: dict[str, Union[Optional[str], bool]] = {
self.results: dict[str, str | None | bool] = {
"fingerprint": None,
"userdata": None,
"token": None,
Expand Down Expand Up @@ -375,7 +374,7 @@ async def gateway_cm(gw_app):

class GatewayClient:
class EventListener:
def __init__(self, event: GatewayOp, dispatch_event: Optional[str], future: asyncio.Future, raw: bool):
def __init__(self, event: GatewayOp, dispatch_event: str | None, future: asyncio.Future, raw: bool):
self.event = event
self.dispatch_event = dispatch_event
self.future = future
Expand All @@ -387,8 +386,8 @@ def __init__(self, token: str):

self.running = True
self.loop = asyncio.get_event_loop()
self.heartbeatTask: Optional[asyncio.Task] = None
self.mainTask: Optional[asyncio.Task] = None
self.heartbeatTask: asyncio.Task | None = None
self.mainTask: asyncio.Task | None = None

self.handlers = {
GatewayOp.HELLO: self.handle_hello
Expand Down
5 changes: 2 additions & 3 deletions yepcord/gateway/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

import warnings
from json import dumps as jdumps
from typing import Optional, Union

from quart import Websocket
from redis.asyncio import Redis
Expand All @@ -47,7 +46,7 @@ def __init__(self, ws, gateway: Gateway):
self.z = getattr(ws, "zlib", None)
self.id = self.user_id = None
self.is_bot = False
self.cached_presence: Optional[Presence] = None
self.cached_presence: Presence | None = None

@property
def connected(self):
Expand Down Expand Up @@ -295,7 +294,7 @@ def __init__(self, core: Core):
self.presences = Presences(self)
self.ev = GatewayEvents(self)

self.redis: Union[Redis, FakeRedis, None] = None
self.redis: Redis | FakeRedis | None = None

async def init(self):
await self.broker.start()
Expand Down
4 changes: 2 additions & 2 deletions yepcord/gateway/presences.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from __future__ import annotations

from json import loads, dumps
from typing import Optional, TYPE_CHECKING
from typing import TYPE_CHECKING

from ..yepcord.config import Config

Expand Down Expand Up @@ -78,7 +78,7 @@ async def set_or_refresh(self, user_id: int, presence: Presence = None, overwrit
await pipe.expire(f"presence_{user_id}", int(Config.GATEWAY_KEEP_ALIVE_DELAY * 1.25))
await pipe.execute()

async def get(self, user_id: int) -> Optional[Presence]:
async def get(self, user_id: int) -> Presence | None:
if (presence := await self._gateway.redis.get(f"presence_{user_id}")) is None:
return
return Presence(user_id, **loads(presence))
5 changes: 2 additions & 3 deletions yepcord/gateway/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from enum import Enum, auto
from typing import Optional

from redis.asyncio import Redis

Expand All @@ -38,7 +37,7 @@ class TokenType(Enum):
BOT = auto()


def get_token_type(token: str) -> Optional[TokenType]:
def get_token_type(token: str) -> TokenType | None:
if not token:
return

Expand All @@ -55,7 +54,7 @@ def get_token_type(token: str) -> Optional[TokenType]:
return TokenType.USER if len(token) == 3 else TokenType.BOT


async def init_redis_pool() -> Optional[Redis]:
async def init_redis_pool() -> Redis | None:
if not Config.REDIS_URL:
return
return Redis.from_url(
Expand Down
5 changes: 2 additions & 3 deletions yepcord/remote_auth/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
from hashlib import sha256
from os import urandom
from time import time
from typing import Optional

from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric.padding import OAEP, MGF1
Expand All @@ -47,8 +46,8 @@ def __init__(self, ws: Websocket, version: int, gateway):
self.version = version
self.gw = gateway

self.pubkey: Optional[RSAPublicKey] = None
self.fingerprint: Optional[str] = None
self.pubkey: RSAPublicKey | None = None
self.fingerprint: str | None = None

def encrypt(self, data: bytes):
return self.pubkey.encrypt(data, OAEP(mgf=MGF1(algorithm=SHA256()), algorithm=SHA256(), label=None))
Expand Down
29 changes: 14 additions & 15 deletions yepcord/rest_api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"""

from time import time
from typing import Union, Optional, Callable, Awaitable, TypeVar
from typing import Callable, Awaitable, TypeVar

from fast_depends import Depends
from quart import request
Expand All @@ -31,12 +31,12 @@
from yepcord.yepcord.snowflake import Snowflake
from yepcord.yepcord.utils import b64decode

SessionsType = Union[Session, Authorization, Bot]
SessionsType = Session | Authorization | Bot
T = TypeVar("T")
P = ParamSpec("P")


def depRaise(func: Callable[P, Awaitable[Optional[T]]], status_code: int, error: dict) -> Callable[P, Awaitable[T]]:
def depRaise(func: Callable[P, Awaitable[T | None]], status_code: int, error: dict) -> Callable[P, Awaitable[T]]:
async def wrapper(res=Depends(func)):
if res is None:
raise InvalidDataErr(status_code, error)
Expand All @@ -45,7 +45,7 @@ async def wrapper(res=Depends(func)):
return wrapper


async def depSessionO() -> Optional[SessionsType]:
async def depSessionO() -> SessionsType | None:
if session := await getSessionFromToken(request.headers.get("Authorization", "")):
return session

Expand All @@ -57,7 +57,7 @@ async def _depUser_with_user(session: SessionsType = Depends(depSession)) -> Use
return session.user


async def _depUser_without_user(session: Optional[SessionsType] = Depends(depSessionO)) -> Optional[User]:
async def _depUser_without_user(session: SessionsType | None = Depends(depSessionO)) -> User | None:
if session:
return session.user

Expand All @@ -66,7 +66,7 @@ def depUser(allow_without_user: bool = False):
return _depUser_without_user if allow_without_user else _depUser_with_user


async def depChannelO(channel_id: Optional[int] = None, user: User = Depends(depUser())) -> Optional[Channel]:
async def depChannelO(channel_id: int | None = None, user: User = Depends(depUser())) -> Channel | None:
if (channel := await getCore().getChannel(channel_id)) is None:
return
if not await getCore().getUserByChannel(channel, user.id):
Expand All @@ -75,8 +75,7 @@ async def depChannelO(channel_id: Optional[int] = None, user: User = Depends(dep
return channel


async def depChannelONoAuth(channel_id: Optional[int] = None, user: Optional[User] = Depends(depUser(True))) \
-> Optional[Channel]:
async def depChannelONoAuth(channel_id: int | None = None, user: User | None = Depends(depUser(True))) -> Channel | None:
if user is None:
return
return await depChannelO(channel_id, user)
Expand All @@ -85,7 +84,7 @@ async def depChannelONoAuth(channel_id: Optional[int] = None, user: Optional[Use
depChannel = depRaise(depChannelO, 404, Errors.make(10003))


async def depWebhookO(webhook: Optional[int] = None, token: Optional[str] = None) -> Optional[Webhook]:
async def depWebhookO(webhook: int | None = None, token: str | None = None) -> Webhook | None:
if webhook is None or token is None:
return
webhook = await Webhook.get_or_none(id=webhook).select_related("channel")
Expand All @@ -99,10 +98,10 @@ async def depWebhookO(webhook: Optional[int] = None, token: Optional[str] = None

async def depMessageO(
message: int,
channel: Optional[Channel] = Depends(depChannelONoAuth),
webhook: Optional[Webhook] = Depends(depWebhookO),
user: Optional[User] = Depends(depUser(True)),
) -> Optional[Message]:
channel: Channel | None = Depends(depChannelONoAuth),
webhook: Webhook | None = Depends(depWebhookO),
user: User | None = Depends(depUser(True)),
) -> Message | None:
if webhook:
message = await Message.get_or_none(id=message, webhook_id=webhook.id).select_related(*Message.DEFAULT_RELATED)
elif channel is not None and user is not None:
Expand All @@ -119,7 +118,7 @@ async def depMessageO(
depMessage = depRaise(depMessageO, 404, Errors.make(10008))


async def depInvite(invite: Optional[str] = None) -> Invite:
async def depInvite(invite: str | None = None) -> Invite:
try:
invite_id = int.from_bytes(b64decode(invite), "big")
if not (inv := await getCore().getInvite(invite_id)):
Expand All @@ -132,7 +131,7 @@ async def depInvite(invite: Optional[str] = None) -> Invite:
return invite


async def depGuildO(guild: Optional[int] = None, user: User = Depends(depUser())) -> Optional[Guild]:
async def depGuildO(guild: int | None = None, user: User = Depends(depUser())) -> Guild | None:
if (guild := await getCore().getGuild(guild)) is None:
return
if not await GuildMember.filter(guild=guild, user=user).exists():
Expand Down
Loading