Refactored

This commit is contained in:
Malasaur 2025-12-14 21:05:51 +01:00
parent 7a2948570b
commit e7248723f5
No known key found for this signature in database
5 changed files with 164 additions and 107 deletions

1
.gitignore vendored
View file

@ -1,2 +1,3 @@
__pycache__/ __pycache__/
venv/ venv/
.env

10
config.py Normal file
View file

@ -0,0 +1,10 @@
from dotenv import dotenv_values
class Config:
data = dotenv_values()
MINECRAFT_HOST: str = data.get("MINECRAFT_HOST") or "localhost"
MINECRAFT_PORT: int = int(data.get("MINECRAFT_PORT") or 25565)
MINECRAFT_PASSWORD: str = data.get("MINECRAFT_PASSWORD") or "1234"
SIGNAL_HOST: str = data.get("SIGNAL_HOST") or "localhost"
SIGNAL_PORT: int = int(data.get("SIGNAL_PORT") or 8000)

View file

@ -1,5 +1,3 @@
import asyncio
import atexit
from re import compile from re import compile
from typing import Any, Callable from typing import Any, Callable
@ -11,35 +9,32 @@ class Minecraft:
self.host = host.rstrip("/") self.host = host.rstrip("/")
self.port = port self.port = port
self.headers = {"Authorization": password} self.headers = {"Authorization": password}
self.session = ClientSession()
atexit.register(self.exit)
self._hooks: list[Callable] = [] self._hooks: list[Callable] = []
def exit(self):
asyncio.run(self.session.close())
async def _get(self, method: str, **json: Any): async def _get(self, method: str, **json: Any):
async with self.session.get( async with ClientSession() as session:
f"{self.host}:{self.port}{method}", json=json, headers=self.headers async with session.get(
) as response: f"{self.host}:{self.port}{method}", json=json, headers=self.headers
if response.status == 200: ) as response:
return await response.json() if response.status == 200:
raise ConnectionError( return await response.json()
f"Error while sending {method} to Server: HTTP code {response.status}."
)
async def _stream(self, method: str, **json: Any):
async with self.session.get(
f"{self.host}:{self.port}{method}", json=json, headers=self.headers
) as response:
if response.status == 200:
async for chunk, _ in response.content.iter_chunks():
yield chunk
else:
raise ConnectionError( raise ConnectionError(
f"Error while sending {method} to Server: HTTP code {response.status}." f"Error while sending {method} to Server: HTTP code {response.status}."
) )
async def _stream(self, method: str, **json: Any):
async with ClientSession() as session:
async with session.get(
f"{self.host}:{self.port}{method}", json=json, headers=self.headers
) as response:
if response.status == 200:
async for chunk, _ in response.content.iter_chunks():
yield chunk
else:
raise ConnectionError(
f"Error while sending {method} to Server: HTTP code {response.status}."
)
async def start(self): async def start(self):
return await self._get("/start") return await self._get("/start")

View file

@ -1,55 +1,66 @@
import asyncio import asyncio
import atexit
from json import loads from json import loads
from time import time
from typing import Callable from typing import Callable
from uuid import uuid4 from uuid import uuid4
from aiohttp import ClientSession from aiohttp import ClientSession
from config import Config
from sseclient import SSEClient from sseclient import SSEClient
class User: class User:
def __init__( def __init__(
self, self,
name: str,
number: str,
id: str, id: str,
number: str | None = None,
name: str | None = None,
first_name: str | None = None,
last_name: str | None = None,
nickname: str | None = None,
first_nickname: str | None = None, first_nickname: str | None = None,
last_nickname: str | None = None, last_nickname: str | None = None,
note: str | None = None, note: str | None = None,
about: str | None = None,
emoji: str | None = None,
profile_first_name: str | None = None,
profile_last_name: str | None = None,
username: str | None = None,
): ):
"""Creates a new User.
Args:
name: The User's username.
number: The User's phone number.
id: The User's Signal UUID.
first_nickname:
last_nickname:
note:
"""
self.name = name
self.number = number
self.id = id self.id = id
self.number = number
self.name = name
self.first_name = first_name
self.last_name = last_name
self.nickname = nickname
self.first_nickname = first_nickname self.first_nickname = first_nickname
self.last_nickname = last_nickname self.last_nickname = last_nickname
self.note = note self.note = note
self.about = about
self.emoji = emoji
self.profile_first_name = profile_first_name
self.profile_last_name = profile_last_name
self.username = username
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<User "{self.name}" at "{self.number}">' return f'<User "{self.name}" at "{self.number}">'
class Group: class Group:
def __init__(self, name: str, id: str): def __init__(
"""Creates a new Group. self,
Args: id: str,
name: the Group's name. name: str | None = None,
id: the Group's Signal UUID. description: str | None = None,
""" members: list[User] = [],
admins: list[User] = [],
self.name = name banned: list[User] = [],
):
self.id = id self.id = id
self.name = name
self.description = description
self.members = members
self.admins = admins
self.banned = banned
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<Group "{self.name}" at "{self.id}">' return f'<Group "{self.name}" at "{self.id}">'
@ -69,37 +80,52 @@ class ReceivedMessage:
self.group = group self.group = group
def __repr__(self) -> str: def __repr__(self) -> str:
group = ' on "' + self.group.name + '"' if self.group else "" group = (
' on "' + (self.group.name or self.group.id) + '"'
if self.group and self.group
else ""
)
return f'<Message "{self.message}" by "{self.user.name}"{group}>' return f'<Message "{self.message}" by "{self.user.name}"{group}>'
class MessageMention: ...
class MessageStyles: ...
class Message:
def __init__(
self,
recipient: User | Group,
sticker: str | None = None,
mentions: list[MessageMention] = [],
styles: list[MessageStyles] = [],
view_once: bool = False,
): ...
class Signal: class Signal:
def __init__(self, host: str, port: int): def __init__(self, host: str, port: int):
self.url = host.rstrip("/") + ":" + str(port) self.url = host.rstrip("/") + ":" + str(port)
self.session = ClientSession()
atexit.register(self.exit)
self._hooks: dict[str, Callable] = {} self._hooks: dict[str, Callable] = {}
self.usersCache = {}
self.usersCacheTime = 0
def exit(self):
asyncio.run(self.session.close())
async def _post(self, method: str, **params): async def _post(self, method: str, **params):
async with self.session.post( async with ClientSession() as session:
self.url + "/api/v1/rpc", async with session.post(
json={ self.url + "/api/v1/rpc",
"jsonrpc": "2.0", json={
"method": method, "jsonrpc": "2.0",
"params": params, "method": method,
"id": str(uuid4()), "params": params,
}, "id": str(uuid4()),
) as response: },
if response.status == 200: ) as response:
return await response.json() if response.status == 200:
raise ConnectionError( return await response.json()
f"Error while sending {method} to Server: HTTP code {response.status}." raise ConnectionError(
) f"Error while sending {method} to Server: HTTP code {response.status}."
)
async def sendMessage(self, message: str, recipient: User | Group): async def sendMessage(self, message: str, recipient: User | Group):
if isinstance(recipient, User): if isinstance(recipient, User):
@ -109,44 +135,44 @@ class Signal:
async def listGroups(self) -> list[Group]: async def listGroups(self) -> list[Group]:
return [ return [
Group(group["name"], group["id"]) Group(
id=group["id"],
name=group["name"],
description=group["description"],
# members=[self.getUser(member["id"]) for member in group["members"]],
# admins=[self.getUser(admin["id"]) for admin in group["admins"]],
# banned=[self.getUser(ban["id"]) for ban in group["banned"]],
)
for group in (await self._post("listGroups"))["result"] for group in (await self._post("listGroups"))["result"]
] ]
async def listUsers(self) -> list[User]: async def listUsers(self) -> list[User]:
return [ return [
User( User(
user["name"], id=user["uuid"],
user["number"], number=user["number"],
user["uuid"], name=user["name"],
user["nickGivenName"], first_name=user["givenName"],
user["nickFamilyName"], last_name=user["familyName"],
user["note"], nickname=user["nickName"],
first_nickname=user["nickGivenName"],
last_nickname=user["nickFamilyName"],
note=user["note"],
about=user["profile"]["about"],
emoji=user["profile"]["aboutEmoji"],
profile_first_name=user["profile"]["familyName"],
profile_last_name=user["profile"]["givenName"],
username=user["username"],
) )
for user in (await self._post("listContacts"))["result"] for user in (await self._post("listContacts"))["result"]
] ]
async def getUser( def onMessage(
self, self,
*, *,
number: str | None = None, user: list[User] | User | None = None,
id: str | None = None, group: list[Group] | Group | None = None,
first_nickname: str | None = None, ) -> Callable:
last_nickname: str | None = None,
) -> User | None:
for user in await self.listUsers():
if number and user.number == number:
return user
if id and user.id == id:
return user
if first_nickname and user.first_nickname == first_nickname:
return user
if last_nickname and user.last_nickname == last_nickname:
return user
return None
def onMessage(self, group: Group | None = None) -> Callable:
def wrapper(func: Callable): def wrapper(func: Callable):
async def callback(data: dict): async def callback(data: dict):
envelope = data.get("envelope", {}) envelope = data.get("envelope", {})
@ -165,17 +191,33 @@ class Signal:
groupInfo.get("groupName"), groupInfo.get("groupId") groupInfo.get("groupName"), groupInfo.get("groupId")
) )
msg = None
msg_body = envelope.get("dataMessage", {}).get("message") msg_body = envelope.get("dataMessage", {}).get("message")
if msg_body is not None: if msg_body is not None:
if group is None or (msg_group and msg_group.id == group.id): msg = ReceivedMessage(msg_body, msg_user, msg_group)
await func(
ReceivedMessage( if not user and not group:
msg_body, return func(msg)
(await self.getUser(number=msg_user.number))
or msg_user, if isinstance(user, User) and user.id == msg_user.id:
msg_group, return func(msg)
)
) if (
isinstance(group, Group)
and msg_group
and group.id == msg_group.id
):
return func(msg)
if isinstance(user, list):
for usr in user:
if usr.id == msg_user.id:
return func(msg)
if isinstance(group, list) and msg_group:
for grp in group:
if grp.id == msg_group.id:
return func(msg)
self._hooks[func.__name__] = callback self._hooks[func.__name__] = callback
return func return func
@ -185,7 +227,7 @@ class Signal:
def onMessageRaw(self) -> Callable: def onMessageRaw(self) -> Callable:
def wrapper(func: Callable): def wrapper(func: Callable):
async def callback(data: dict): async def callback(data: dict):
await func(data) func(data)
self._hooks[func.__name__] = callback self._hooks[func.__name__] = callback
return func return func
@ -195,9 +237,16 @@ class Signal:
async def loop(self): async def loop(self):
for msg in SSEClient(self.url + "/api/v1/events"): for msg in SSEClient(self.url + "/api/v1/events"):
try: try:
data = loads(msg.data) data = loads(msg.data.encode("latin1").decode())
except Exception: except Exception:
data = {} data = {}
for callback in self._hooks.values(): for callback in self._hooks.values():
await callback(data) await callback(data)
signal = Signal(Config.SIGNAL_HOST, Config.SIGNAL_PORT)
user = asyncio.run(signal.listGroups())[1]
print(user.__dict__)

View file

@ -4,10 +4,12 @@ aiosignal==1.4.0
attrs==25.4.0 attrs==25.4.0
certifi==2025.11.12 certifi==2025.11.12
charset-normalizer==3.4.4 charset-normalizer==3.4.4
dotenv==0.9.9
frozenlist==1.8.0 frozenlist==1.8.0
idna==3.11 idna==3.11
multidict==6.7.0 multidict==6.7.0
propcache==0.4.1 propcache==0.4.1
python-dotenv==1.2.1
requests==2.32.5 requests==2.32.5
six==1.17.0 six==1.17.0
sseclient==0.0.27 sseclient==0.0.27