Genesis commit
This commit is contained in:
commit
5d75c332d6
4 changed files with 345 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
__pycache__/
|
||||||
|
venv/
|
||||||
111
libminecraft.py
Normal file
111
libminecraft.py
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
import asyncio
|
||||||
|
import atexit
|
||||||
|
from re import compile
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
|
||||||
|
|
||||||
|
class Minecraft:
|
||||||
|
def __init__(self, host: str, port: int, password: str):
|
||||||
|
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:
|
||||||
|
raise ConnectionError(
|
||||||
|
f"Error while sending {method} to Server: HTTP code {response.status}."
|
||||||
|
)
|
||||||
|
|
||||||
|
async def start(self):
|
||||||
|
return await self._get("/start")
|
||||||
|
|
||||||
|
async def status(self):
|
||||||
|
return await self._get("/status")
|
||||||
|
|
||||||
|
async def stop(self, countdown: int = 60, reason: str = "", timeout: int = 10):
|
||||||
|
return await self._get(
|
||||||
|
"/stop", countdown=countdown, reason=reason, timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
async def restart(self, countdown: int = 60, reason: str = "", timeout: int = 10):
|
||||||
|
return await self._get(
|
||||||
|
"/restart", countdown=countdown, reason=reason, timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
async def maintainance(
|
||||||
|
self, countdown: int = 60, reason: str = "", timeout: int = 10
|
||||||
|
):
|
||||||
|
return await self._get(
|
||||||
|
"/maintainance", countdown=countdown, reason=reason, timeout=timeout
|
||||||
|
)
|
||||||
|
|
||||||
|
async def command(self, command: str):
|
||||||
|
return await self._get("/command", command=command)
|
||||||
|
|
||||||
|
async def logs_stream(self):
|
||||||
|
async for chunk in self._stream("/logs/stream"):
|
||||||
|
yield chunk.decode().strip()
|
||||||
|
|
||||||
|
async def logs_tail(self, back: int = 10):
|
||||||
|
async for chunk in self._stream("/logs/tail", back=back):
|
||||||
|
yield chunk.decode().strip()
|
||||||
|
|
||||||
|
def onConsoleLog(self, pattern: str | None = None) -> Callable:
|
||||||
|
def wrapper(func: Callable):
|
||||||
|
compiled = compile(pattern) if pattern else None
|
||||||
|
|
||||||
|
async def callback(line: str):
|
||||||
|
if compiled is None:
|
||||||
|
func(line)
|
||||||
|
return
|
||||||
|
match = compiled.fullmatch(line)
|
||||||
|
if match is None:
|
||||||
|
return
|
||||||
|
groups = match.groups()
|
||||||
|
if groups:
|
||||||
|
func(*groups)
|
||||||
|
else:
|
||||||
|
func(line)
|
||||||
|
|
||||||
|
self._hooks.append(callback)
|
||||||
|
|
||||||
|
return func
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def onPlayerChat(self) -> Callable:
|
||||||
|
def wrapper(func: Callable):
|
||||||
|
return self.onConsoleLog(
|
||||||
|
r"\[[0-9]{2}:[0-9]{2}:[0-9]{2}\] \[Server thread\/INFO\]: <(.*)> (.*)"
|
||||||
|
)(func)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
async def loop(self):
|
||||||
|
async for line in self.logs_stream():
|
||||||
|
for callback in self._hooks:
|
||||||
|
await callback(line)
|
||||||
202
libsignal.py
Normal file
202
libsignal.py
Normal file
|
|
@ -0,0 +1,202 @@
|
||||||
|
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 sseclient import SSEClient
|
||||||
|
|
||||||
|
|
||||||
|
class User:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name: str,
|
||||||
|
number: str,
|
||||||
|
id: str,
|
||||||
|
first_nickname: str | None = None,
|
||||||
|
last_nickname: str | None = None,
|
||||||
|
note: 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.first_nickname = first_nickname
|
||||||
|
self.last_nickname = last_nickname
|
||||||
|
self.note = note
|
||||||
|
|
||||||
|
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
|
||||||
|
self.id = id
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<Group "{self.name}" at "{self.id}">'
|
||||||
|
|
||||||
|
|
||||||
|
class ReceivedMessage:
|
||||||
|
def __init__(self, message: str, user: User, group: Group | None):
|
||||||
|
"""Creates a new ReceivedMessage.
|
||||||
|
Args:
|
||||||
|
message: The Message's body.
|
||||||
|
user: The User that sent the Message.
|
||||||
|
group: The Group where the message was sent on, or None for a direct Message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.message = message
|
||||||
|
self.user = user
|
||||||
|
self.group = group
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f'<Message "{self.message}" by "{self.user.name}"{' on "' + self.group.name + '"' if self.group else ""}>'
|
||||||
|
|
||||||
|
|
||||||
|
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 def sendMessage(self, message: str, recipient: User | Group):
|
||||||
|
if isinstance(recipient, User):
|
||||||
|
return await self._post("send", message=message, recipient=recipient.id)
|
||||||
|
if isinstance(recipient, Group):
|
||||||
|
return await self._post("send", message=message, groupId=recipient.id)
|
||||||
|
|
||||||
|
async def listGroups(self) -> list[Group]:
|
||||||
|
return [
|
||||||
|
Group(group["name"], group["id"])
|
||||||
|
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"],
|
||||||
|
)
|
||||||
|
for user in (await self._post("listContacts"))["result"]
|
||||||
|
]
|
||||||
|
|
||||||
|
async def getUser(
|
||||||
|
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:
|
||||||
|
def wrapper(func: Callable):
|
||||||
|
async def callback(data: dict):
|
||||||
|
envelope = data.get("envelope", {})
|
||||||
|
dataMessage = envelope.get("dataMessage", {})
|
||||||
|
groupInfo = dataMessage.get("groupInfo", {})
|
||||||
|
|
||||||
|
msg_user = User(
|
||||||
|
envelope.get("sourceName"),
|
||||||
|
envelope.get("sourceNumber"),
|
||||||
|
envelope.get("sourceUuid"),
|
||||||
|
)
|
||||||
|
|
||||||
|
msg_group = None
|
||||||
|
if groupInfo:
|
||||||
|
msg_group = Group(
|
||||||
|
groupInfo.get("groupName"), groupInfo.get("groupId")
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
self._hooks[func.__name__] = callback
|
||||||
|
return func
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
def onMessageRaw(self) -> Callable:
|
||||||
|
def wrapper(func: Callable):
|
||||||
|
async def callback(data: dict):
|
||||||
|
await func(data)
|
||||||
|
|
||||||
|
self._hooks[func.__name__] = callback
|
||||||
|
return func
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
async def loop(self):
|
||||||
|
for msg in SSEClient(self.url + "/api/v1/events"):
|
||||||
|
try:
|
||||||
|
data = loads(msg.data)
|
||||||
|
except Exception:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
for callback in self._hooks.values():
|
||||||
|
await callback(data)
|
||||||
30
main.py
Normal file
30
main.py
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from libminecraft import Minecraft
|
||||||
|
from libsignal import Group, ReceivedMessage, Signal
|
||||||
|
|
||||||
|
RChat = Group("RS Chat", "5PlbXaPmWZQkhmuyyC/fkWTy8K+BqomjK7byVDyxmpo=")
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
minecraft = Minecraft("http://localhost", 42101, "1234")
|
||||||
|
signal = Signal("http://localhost", 42069)
|
||||||
|
|
||||||
|
##########################
|
||||||
|
# === RETARDS SERVER === #
|
||||||
|
##########################
|
||||||
|
|
||||||
|
@signal.onMessage(RChat)
|
||||||
|
async def onRChatMessage(message: ReceivedMessage):
|
||||||
|
await minecraft.command(
|
||||||
|
f'tellraw @a "<{message.user.last_nickname or message.user.name}> {message.message.replace('"', '\\"')}"'
|
||||||
|
)
|
||||||
|
|
||||||
|
@minecraft.onPlayerChat()
|
||||||
|
async def onRServerMessage(player: str, message: str):
|
||||||
|
await signal.sendMessage(f"<{player}> {message}", RChat)
|
||||||
|
|
||||||
|
await signal.loop()
|
||||||
|
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
Loading…
Add table
Add a link
Reference in a new issue