From e7248723f5402af2b3163b642e1df793c969c740 Mon Sep 17 00:00:00 2001 From: Malasaur Date: Sun, 14 Dec 2025 21:05:51 +0100 Subject: [PATCH] Refactored --- .gitignore | 1 + config.py | 10 +++ libminecraft.py | 43 +++++----- libsignal.py | 215 +++++++++++++++++++++++++++++------------------ requirements.txt | 2 + 5 files changed, 164 insertions(+), 107 deletions(-) create mode 100644 config.py diff --git a/.gitignore b/.gitignore index 92afa22..bb66c94 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__/ venv/ +.env diff --git a/config.py b/config.py new file mode 100644 index 0000000..443978b --- /dev/null +++ b/config.py @@ -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) diff --git a/libminecraft.py b/libminecraft.py index 4b5918f..f75a204 100644 --- a/libminecraft.py +++ b/libminecraft.py @@ -1,5 +1,3 @@ -import asyncio -import atexit from re import compile from typing import Any, Callable @@ -11,35 +9,32 @@ class Minecraft: self.host = host.rstrip("/") self.port = port self.headers = {"Authorization": password} - self.session = ClientSession() - atexit.register(self.exit) self._hooks: list[Callable] = [] - def exit(self): - asyncio.run(self.session.close()) - async def _get(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: - return await response.json() - raise ConnectionError( - 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: + 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: + return await response.json() raise ConnectionError( 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): return await self._get("/start") diff --git a/libsignal.py b/libsignal.py index 53a4683..413551f 100644 --- a/libsignal.py +++ b/libsignal.py @@ -1,55 +1,66 @@ import asyncio -import atexit from json import loads -from time import time from typing import Callable from uuid import uuid4 from aiohttp import ClientSession +from config import Config from sseclient import SSEClient class User: def __init__( self, - name: str, - number: 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, last_nickname: 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.number = number + self.name = name + self.first_name = first_name + self.last_name = last_name + self.nickname = nickname self.first_nickname = first_nickname self.last_nickname = last_nickname 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: return f'' class Group: - def __init__(self, name: str, id: str): - """Creates a new Group. - Args: - name: the Group's name. - id: the Group's Signal UUID. - """ - - self.name = name + def __init__( + self, + id: str, + name: str | None = None, + description: str | None = None, + members: list[User] = [], + admins: list[User] = [], + banned: list[User] = [], + ): self.id = id + self.name = name + self.description = description + self.members = members + self.admins = admins + self.banned = banned def __repr__(self) -> str: return f'' @@ -69,37 +80,52 @@ class ReceivedMessage: self.group = group 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'' +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: def __init__(self, host: str, port: int): self.url = host.rstrip("/") + ":" + str(port) - self.session = ClientSession() - atexit.register(self.exit) 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 with self.session.post( - self.url + "/api/v1/rpc", - json={ - "jsonrpc": "2.0", - "method": method, - "params": params, - "id": str(uuid4()), - }, - ) as response: - if response.status == 200: - return await response.json() - raise ConnectionError( - f"Error while sending {method} to Server: HTTP code {response.status}." - ) + async with ClientSession() as session: + async with session.post( + self.url + "/api/v1/rpc", + json={ + "jsonrpc": "2.0", + "method": method, + "params": params, + "id": str(uuid4()), + }, + ) as response: + if response.status == 200: + return await response.json() + raise ConnectionError( + f"Error while sending {method} to Server: HTTP code {response.status}." + ) async def sendMessage(self, message: str, recipient: User | Group): if isinstance(recipient, User): @@ -109,44 +135,44 @@ class Signal: async def listGroups(self) -> list[Group]: 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"] ] async def listUsers(self) -> list[User]: return [ User( - user["name"], - user["number"], - user["uuid"], - user["nickGivenName"], - user["nickFamilyName"], - user["note"], + id=user["uuid"], + number=user["number"], + name=user["name"], + first_name=user["givenName"], + last_name=user["familyName"], + 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"] ] - async def getUser( + def onMessage( self, *, - number: str | None = None, - id: str | None = None, - first_nickname: str | None = None, - 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: + user: list[User] | User | None = None, + group: list[Group] | Group | None = None, + ) -> Callable: def wrapper(func: Callable): async def callback(data: dict): envelope = data.get("envelope", {}) @@ -165,17 +191,33 @@ class Signal: groupInfo.get("groupName"), groupInfo.get("groupId") ) + msg = None msg_body = envelope.get("dataMessage", {}).get("message") if msg_body is not None: - if group is None or (msg_group and msg_group.id == group.id): - await func( - ReceivedMessage( - msg_body, - (await self.getUser(number=msg_user.number)) - or msg_user, - msg_group, - ) - ) + msg = ReceivedMessage(msg_body, msg_user, msg_group) + + if not user and not group: + return func(msg) + + if isinstance(user, User) and user.id == msg_user.id: + 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 return func @@ -185,7 +227,7 @@ class Signal: def onMessageRaw(self) -> Callable: def wrapper(func: Callable): async def callback(data: dict): - await func(data) + func(data) self._hooks[func.__name__] = callback return func @@ -195,9 +237,16 @@ class Signal: async def loop(self): for msg in SSEClient(self.url + "/api/v1/events"): try: - data = loads(msg.data) + data = loads(msg.data.encode("latin1").decode()) except Exception: data = {} for callback in self._hooks.values(): await callback(data) + + +signal = Signal(Config.SIGNAL_HOST, Config.SIGNAL_PORT) + +user = asyncio.run(signal.listGroups())[1] + +print(user.__dict__) diff --git a/requirements.txt b/requirements.txt index f2a0394..8ab03b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,12 @@ aiosignal==1.4.0 attrs==25.4.0 certifi==2025.11.12 charset-normalizer==3.4.4 +dotenv==0.9.9 frozenlist==1.8.0 idna==3.11 multidict==6.7.0 propcache==0.4.1 +python-dotenv==1.2.1 requests==2.32.5 six==1.17.0 sseclient==0.0.27