Refactored
This commit is contained in:
parent
7a2948570b
commit
e7248723f5
5 changed files with 164 additions and 107 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,2 +1,3 @@
|
||||||
__pycache__/
|
__pycache__/
|
||||||
venv/
|
venv/
|
||||||
|
.env
|
||||||
|
|
|
||||||
10
config.py
Normal file
10
config.py
Normal 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)
|
||||||
|
|
@ -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,15 +9,11 @@ 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:
|
||||||
|
async with session.get(
|
||||||
f"{self.host}:{self.port}{method}", json=json, headers=self.headers
|
f"{self.host}:{self.port}{method}", json=json, headers=self.headers
|
||||||
) as response:
|
) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
|
|
@ -29,7 +23,8 @@ class Minecraft:
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _stream(self, method: str, **json: Any):
|
async def _stream(self, method: str, **json: Any):
|
||||||
async with self.session.get(
|
async with ClientSession() as session:
|
||||||
|
async with session.get(
|
||||||
f"{self.host}:{self.port}{method}", json=json, headers=self.headers
|
f"{self.host}:{self.port}{method}", json=json, headers=self.headers
|
||||||
) as response:
|
) as response:
|
||||||
if response.status == 200:
|
if response.status == 200:
|
||||||
|
|
|
||||||
189
libsignal.py
189
libsignal.py
|
|
@ -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,24 +80,39 @@ 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:
|
||||||
|
async with session.post(
|
||||||
self.url + "/api/v1/rpc",
|
self.url + "/api/v1/rpc",
|
||||||
json={
|
json={
|
||||||
"jsonrpc": "2.0",
|
"jsonrpc": "2.0",
|
||||||
|
|
@ -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__)
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue