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__/
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 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")

View file

@ -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'<User "{self.name}" at "{self.number}">'
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'<Group "{self.name}" at "{self.id}">'
@ -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'<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:
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__)

View file

@ -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