Refactored

This commit is contained in:
Malasaur 2025-12-16 01:15:17 +01:00
parent e7248723f5
commit 715d0527bf
No known key found for this signature in database
6 changed files with 327 additions and 106 deletions

8
libbot.py Normal file
View file

@ -0,0 +1,8 @@
from config import Config
from libminecraft import Minecraft
from libsignal import Signal
signal = Signal(Config.SIGNAL_HOST, Config.SIGNAL_PORT)
minecraft = Minecraft(
Config.MINECRAFT_HOST, Config.MINECRAFT_PORT, Config.MINECRAFT_PASSWORD
)

View file

@ -82,9 +82,9 @@ class Minecraft:
return return
groups = match.groups() groups = match.groups()
if groups: if groups:
func(*groups) await func(*groups)
else: else:
func(line) await func(line)
self._hooks.append(callback) self._hooks.append(callback)

View file

@ -1,11 +1,11 @@
import asyncio import asyncio
from json import loads from json import loads
from typing import Callable from pathlib import Path
from typing import Callable, Literal
from uuid import uuid4 from uuid import uuid4
from aiohttp import ClientSession from aiohttp import ClientSession
from config import Config from config import Config
from sseclient import SSEClient
class User: class User:
@ -41,6 +41,10 @@ class User:
self.profile_last_name = profile_last_name self.profile_last_name = profile_last_name
self.username = username self.username = username
@classmethod
def self(cls):
return cls("", number="+393406838100")
def __repr__(self) -> str: def __repr__(self) -> str:
return f'<User "{self.name}" at "{self.number}">' return f'<User "{self.name}" at "{self.number}">'
@ -66,49 +70,93 @@ class Group:
return f'<Group "{self.name}" at "{self.id}">' return f'<Group "{self.name}" at "{self.id}">'
class ReceivedMessage: class Sticker:
def __init__(self, message: str, user: User, group: Group | None): def __init__(self, pack_id: str, id: int):
"""Creates a new ReceivedMessage. self.pack_id = pack_id
Args: self.id = id
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: class MessageMention:
group = ( def __init__(self, start: int, length: int, recipient: User):
' on "' + (self.group.name or self.group.id) + '"' self.start = start
if self.group and self.group self.length = length
else "" self.recipient = recipient
def get(self) -> str:
return (
f"{self.start}:{self.length}:{self.recipient.number or self.recipient.id}"
) )
return f'<Message "{self.message}" by "{self.user.name}"{group}>'
class MessageMention: ... class MessageStyle:
def __init__(
self,
start: int,
length: int,
style: Literal["BOLD", "ITALIC", "SPOILER", "STRIKETHROUGH", "MONOSPACE"],
):
self.start = start
self.length = length
self.style = style
def get(self) -> str:
class MessageStyles: ... return f"{self.start}:{self.length}:{self.style}"
class Message: class Message:
def __init__( def __init__(
self, self,
recipient: User | Group, text: str | None = None,
sticker: str | None = None, user: User | None = None,
group: Group | None = None,
timestamp: int | None = None,
attachments: list[Path] = [],
view_once: bool = False, # TODO: fix
sticker: Sticker | None = None,
mentions: list[MessageMention] = [], mentions: list[MessageMention] = [],
styles: list[MessageStyles] = [], styles: list[MessageStyle] = [],
view_once: bool = False, quote: "Message | Poll | None" = None,
): ... edit: "Message | None" = None,
):
self.text = text
self.user = user
self.group = group
self.timestamp = timestamp
self.attachments = attachments
self.view_once = view_once
self.sticker = sticker
self.mentions = mentions
self.styles = styles
self.quote = quote
self.edit = edit
class Poll:
def __init__(
self,
question: str,
options: list[str],
user: User | None = None,
group: Group | None = None,
timestamp: int | None = None,
multiple: bool = True,
):
self.question = question
self.options = options
self.user = user
self.group = group
self.timestamp = timestamp
self.multiple = multiple
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._hooks: dict[str, Callable] = {} self._hooks: list[Callable] = []
async def _post(self, method: str, **params): async def _post(self, method: str, **params):
async with ClientSession() as session: async with ClientSession() as session:
@ -127,11 +175,129 @@ class Signal:
f"Error while sending {method} to Server: HTTP code {response.status}." 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: Message, recipient: User | Group):
params: dict = {}
if isinstance(recipient, User): if isinstance(recipient, User):
return await self._post("send", message=message, recipient=recipient.id) params["recipient"] = recipient.id
if isinstance(recipient, Group): if isinstance(recipient, Group):
return await self._post("send", message=message, groupId=recipient.id) params["groupId"] = recipient.id
if message.attachments:
params["attachment"] = [
str(file.absolute()) for file in message.attachments
]
if message.view_once:
params["viewOnce"] = message.view_once
if message.text:
params["message"] = message.text
if message.mentions:
params["mention"] = [mention.get() for mention in message.mentions]
if message.styles:
params["textStyle"] = [style.get() for style in message.styles]
if message.sticker:
params["sticker"] = message.sticker.pack_id + ":" + str(message.sticker.id)
if message.quote:
if message.quote.timestamp:
params["quoteTimestamp"] = message.quote.timestamp
if message.quote.user:
params["quoteAuthor"] = message.quote.user.id
if isinstance(message.quote, Message):
if message.quote.mentions:
params["quoteMention"] = [
mention.get() for mention in message.quote.mentions
]
if message.quote.styles:
params["quoteTextStyle"] = [
style.get() for style in message.quote.styles
]
if message.quote.attachments:
params["quoteAttachment"] = [
str(file.absolute()) for file in message.quote.attachments
]
if message.edit:
params["editTimestamp"] = message.edit.timestamp
result = await self._post("send", **params)
timestamp = result.get("result", {}).get("timestamp")
if timestamp:
message.timestamp = timestamp
message.user = User.self()
return result
async def sendReaction(self, emoji: str, message: Message, recipient: User | Group):
params: dict = {"emoji": emoji}
if isinstance(recipient, User):
params["recipient"] = recipient.id
if isinstance(recipient, Group):
params["groupId"] = recipient.id
if message.user:
params["targetAuthor"] = message.user.id
if message.timestamp:
params["targetTimestamp"] = message.timestamp
return await self._post("sendReaction", **params)
async def startTyping(self, recipient: User | Group):
params: dict = {}
if isinstance(recipient, User):
params["recipient"] = recipient.id
if isinstance(recipient, Group):
params["groupId"] = recipient.id
return await self._post("sendTyping", **params)
async def stopTyping(self, recipient: User | Group):
params: dict = {"stop": True}
if isinstance(recipient, User):
params["recipient"] = recipient.id
if isinstance(recipient, Group):
params["groupId"] = recipient.id
return await self._post("sendTyping", **params)
async def deleteMessage(self, message: Message, recipient: User | Group):
params: dict = {}
if isinstance(recipient, User):
params["recipient"] = recipient.id
if isinstance(recipient, Group):
params["groupId"] = recipient.id
params["targetTimestamp"] = message.timestamp
return await self._post("remoteDelete", **params)
# TODO: implement polls
"""
async def sendPoll(self, poll: Poll, recipient: User | Group):
params: dict = {}
if isinstance(recipient, User):
params["recipient"] = recipient.id
if isinstance(recipient, Group):
params["groupId"] = recipient.id
params["question"] = poll.question
params["options"] = poll.options
if not poll.multiple:
params["noMulti"] = not poll.multiple
result = await self._post("sendPollCreate", **params)
timestamp = result.get("result", {}).get("timestamp")
if timestamp:
poll.timestamp = timestamp
poll.user = User.self()
return result
"""
async def listGroups(self) -> list[Group]: async def listGroups(self) -> list[Group]:
return [ return [
@ -139,9 +305,11 @@ class Signal:
id=group["id"], id=group["id"],
name=group["name"], name=group["name"],
description=group["description"], description=group["description"],
# members=[self.getUser(member["id"]) for member in group["members"]], members=[
# admins=[self.getUser(admin["id"]) for admin in group["admins"]], await self.getUser(member["uuid"]) for member in group["members"]
# banned=[self.getUser(ban["id"]) for ban in group["banned"]], ],
admins=[await self.getUser(admin["uuid"]) for admin in group["admins"]],
banned=[await self.getUser(ban["uuid"]) for ban in group["banned"]],
) )
for group in (await self._post("listGroups"))["result"] for group in (await self._post("listGroups"))["result"]
] ]
@ -167,6 +335,18 @@ class Signal:
for user in (await self._post("listContacts"))["result"] for user in (await self._post("listContacts"))["result"]
] ]
async def getGroup(self, id: str) -> Group:
for group in await self.listGroups():
if group.id == id:
return group
return Group(id)
async def getUser(self, id: str) -> User:
for user in await self.listUsers():
if user.id == id:
return user
return User(id)
def onMessage( def onMessage(
self, self,
*, *,
@ -176,50 +356,74 @@ class Signal:
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", {})
dataMessage = envelope.get("dataMessage", {}) if envelope and envelope.get("dataMessage"):
groupInfo = dataMessage.get("groupInfo", {}) dataMessage = envelope["dataMessage"]
msg_user = User( message: str | None = dataMessage["message"]
envelope.get("sourceName"),
envelope.get("sourceNumber"),
envelope.get("sourceUuid"),
)
msg_group = None groupId: str | None = None
if groupInfo:
msg_group = Group( if dataMessage.get("groupInfo"):
groupInfo.get("groupName"), groupInfo.get("groupId") groupId = dataMessage["groupInfo"]["groupId"]
sticker: Sticker | None = None
if dataMessage.get("sticker"):
s = dataMessage["sticker"]
sticker = Sticker(s["packId"], s["stickerId"])
# attachments
# contacts
viewOnce: bool = dataMessage["viewOnce"]
sourceUuid: str = envelope["sourceUuid"]
timestamp: int = envelope["timestamp"]
msg_user: User = await self.getUser(sourceUuid)
msg_group: Group | None = (
(await self.getGroup(groupId)) if groupId else None
) )
msg = None msg = Message(
msg_body = envelope.get("dataMessage", {}).get("message") text=message,
if msg_body is not None: user=msg_user,
msg = ReceivedMessage(msg_body, msg_user, msg_group) group=msg_group,
timestamp=timestamp,
sticker=sticker,
view_once=viewOnce,
)
await self._post(
"sendReceipt",
recipient=msg_user.id,
targetTimestamp=timestamp,
type="viewed",
)
if not user and not group: if not user and not group:
return func(msg) return await func(msg)
if isinstance(user, User) and user.id == msg_user.id: if isinstance(user, User) and user.id == msg_user.id:
return func(msg) return await func(msg)
if ( if (
isinstance(group, Group) isinstance(group, Group)
and msg_group and msg_group
and group.id == msg_group.id and group.id == msg_group.id
): ):
return func(msg) return await func(msg)
if isinstance(user, list): if isinstance(user, list):
for usr in user: for usr in user:
if usr.id == msg_user.id: if usr.id == msg_user.id:
return func(msg) return await func(msg)
if isinstance(group, list) and msg_group: if isinstance(group, list) and msg_group:
for grp in group: for grp in group:
if grp.id == msg_group.id: if grp.id == msg_group.id:
return func(msg) return await func(msg)
self._hooks[func.__name__] = callback self._hooks.append(callback)
return func return func
return wrapper return wrapper
@ -227,26 +431,24 @@ 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):
func(data) await func(data)
self._hooks[func.__name__] = callback self._hooks.append(callback)
return func return func
return wrapper return wrapper
async def loop(self): async def loop(self):
for msg in SSEClient(self.url + "/api/v1/events"): async with ClientSession() as session:
try: async with session.get(self.url + "/api/v1/events") as response:
data = loads(msg.data.encode("latin1").decode()) if response.status == 200:
except Exception: async for msg in response.content:
data = {} msg_data = msg.decode().strip()
if msg_data.startswith("data"):
try:
data = loads(msg_data.lstrip("data:"))
except Exception:
data = {}
for callback in self._hooks.values(): for callback in self._hooks:
await callback(data) asyncio.create_task(callback(data))
signal = Signal(Config.SIGNAL_HOST, Config.SIGNAL_PORT)
user = asyncio.run(signal.listGroups())[1]
print(user.__dict__)

42
main.py
View file

@ -1,41 +1,17 @@
import asyncio import asyncio
import os
from importlib import import_module
from libminecraft import Minecraft from libbot import minecraft, signal
from libsignal import Group, ReceivedMessage, Signal
RChat = Group("RS Chat", "5PlbXaPmWZQkhmuyyC/fkWTy8K+BqomjK7byVDyxmpo=") for module in os.listdir("modules"):
if module.endswith(".py"):
import_module("modules." + module.rstrip(".py"))
async def main(): async def main():
minecraft = Minecraft("http://localhost", 42101, "1234") await asyncio.gather(minecraft.loop(), signal.loop())
signal = Signal("http://localhost", 42069)
##########################
# === RETARDS SERVER === #
##########################
@signal.onMessage(RChat)
async def onRChatMessage(message: ReceivedMessage):
username = message.user.last_nickname or message.user.name
msg = message.message.replace('"', '\\"')
await minecraft.command(f'tellraw @a "<{username}> {msg}"')
@minecraft.onPlayerChat()
async def onRServerMessage(player: str, message: str):
await signal.sendMessage(f"<{player}> {message}", RChat)
##################
# === GLOBAL === #
##################
@signal.onMessage()
async def onMessage(message: ReceivedMessage):
if "clanker" in message.message.lower():
await signal.sendMessage(
"Don't use the c-word nigga", message.group or message.user
)
await signal.loop()
asyncio.run(main()) if __name__ == "__main__":
asyncio.run(main())

11
modules/everyone.py Normal file
View file

@ -0,0 +1,11 @@
from libbot import signal
from libsignal import Message
@signal.onMessage()
async def onMessage(message: Message):
if message.user and message.text and "@everyone" in message.text:
await signal.sendMessage(
Message("EVERYBODY LISTEN!!!!", quote=message),
message.group or message.user,
)

View file

@ -0,0 +1,24 @@
import asyncio
from libbot import minecraft, signal
from libsignal import Message, MessageStyle
RSChat = asyncio.run(signal.getGroup("5PlbXaPmWZQkhmuyyC/fkWTy8K+BqomjK7byVDyxmpo="))
@minecraft.onPlayerChat()
async def onMinecraftMessage(player: str, message: str):
await signal.sendMessage(
Message(
f"<{player}> {message}", styles=[MessageStyle(0, len(player) + 2, "BOLD")]
),
RSChat,
)
await minecraft.command("say hi")
@signal.onMessage()
async def onSignalMessage(message: Message):
if message.user and message.user.note and "!rserver" in message.user.note:
username = message.user.last_nickname
await minecraft.command(f"tellraw @a <{username}> {message.text}")