mirror of
https://gitlab.com/octospacc/WinDog.git
synced 2025-06-05 22:09:20 +02:00
Remove rest of legacy code, fix bridges, complete Codings module
This commit is contained in:
@ -35,7 +35,7 @@ def MastodonMakeInputMessageData(status:dict) -> InputMessageData:
|
|||||||
command_tokens = data.text_plain.strip().replace("\t", " ").split(" ")
|
command_tokens = data.text_plain.strip().replace("\t", " ").split(" ")
|
||||||
while command_tokens[0].strip().startswith('@') or not command_tokens[0]:
|
while command_tokens[0].strip().startswith('@') or not command_tokens[0]:
|
||||||
command_tokens.pop(0)
|
command_tokens.pop(0)
|
||||||
data.command = ParseCommand(" ".join(command_tokens))
|
data.command = ParseCommand(" ".join(command_tokens), "mastodon")
|
||||||
data.user = UserData(
|
data.user = UserData(
|
||||||
id = ("mastodon:" + strip_url_scheme(status["account"]["uri"])),
|
id = ("mastodon:" + strip_url_scheme(status["account"]["uri"])),
|
||||||
name = status["account"]["display_name"],
|
name = status["account"]["display_name"],
|
||||||
@ -46,7 +46,7 @@ def MastodonMakeInputMessageData(status:dict) -> InputMessageData:
|
|||||||
def MastodonHandler(event, Mastodon):
|
def MastodonHandler(event, Mastodon):
|
||||||
if event["type"] == "mention":
|
if event["type"] == "mention":
|
||||||
data = MastodonMakeInputMessageData(event["status"])
|
data = MastodonMakeInputMessageData(event["status"])
|
||||||
OnMessageParsed(data)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
if (command := ObjGet(data, "command.name")):
|
||||||
CallEndpoint(command, EventContext(platform="mastodon", event=event, manager=Mastodon), data)
|
CallEndpoint(command, EventContext(platform="mastodon", event=event, manager=Mastodon), data)
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ def MatrixMakeInputMessageData(room:nio.MatrixRoom, event:nio.RoomMessage) -> In
|
|||||||
if (mxc_url := ObjGet(data, "media.url")) and mxc_url.startswith("mxc://"):
|
if (mxc_url := ObjGet(data, "media.url")) and mxc_url.startswith("mxc://"):
|
||||||
_, _, server_name, media_id = mxc_url.split('/')
|
_, _, server_name, media_id = mxc_url.split('/')
|
||||||
data.media["url"] = ("https://" + server_name + nio.Api.download(server_name, media_id)[1])
|
data.media["url"] = ("https://" + server_name + nio.Api.download(server_name, media_id)[1])
|
||||||
data.command = ParseCommand(data.text_plain)
|
data.command = ParseCommand(data.text_plain, "matrix")
|
||||||
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -83,7 +83,7 @@ async def MatrixMessageHandler(room:nio.MatrixRoom, event:nio.RoomMessage) -> No
|
|||||||
if MatrixUsername == event.sender:
|
if MatrixUsername == event.sender:
|
||||||
return # ignore messages that come from the bot itself
|
return # ignore messages that come from the bot itself
|
||||||
data = MatrixMakeInputMessageData(room, event)
|
data = MatrixMakeInputMessageData(room, event)
|
||||||
OnMessageParsed(data)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
if (command := ObjGet(data, "command.name")):
|
||||||
CallEndpoint(command, EventContext(platform="matrix", event=SafeNamespace(room=room, event=event), manager=MatrixClient), data)
|
CallEndpoint(command, EventContext(platform="matrix", event=SafeNamespace(room=room, event=event), manager=MatrixClient), data)
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ def MatrixSender(context:EventContext, data:OutputMessageData):
|
|||||||
MatrixQueue.put((context, data))
|
MatrixQueue.put((context, data))
|
||||||
return None
|
return None
|
||||||
asyncio.create_task(context.manager.room_send(
|
asyncio.create_task(context.manager.room_send(
|
||||||
room_id=(data.room_id or ObjGet(context, "event.room.room_id")),
|
room_id=((data.room and data.room.id) or ObjGet(context, "event.room.room_id")),
|
||||||
message_type="m.room.message",
|
message_type="m.room.message",
|
||||||
content={"msgtype": "m.text", "body": data.text_plain}))
|
content={"msgtype": "m.text", "body": data.text_plain}))
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ def TelegramMakeInputMessageData(message:telegram.Message) -> InputMessageData:
|
|||||||
name = (message.chat.title or message.chat.first_name),
|
name = (message.chat.title or message.chat.first_name),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
data.command = ParseCommand(data.text_plain)
|
data.command = ParseCommand(data.text_plain, "telegram")
|
||||||
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
||||||
linked = TelegramLinker(data)
|
linked = TelegramLinker(data)
|
||||||
data.message_url = linked.message
|
data.message_url = linked.message
|
||||||
@ -68,7 +68,7 @@ def TelegramHandler(update:telegram.Update, context:CallbackContext=None) -> Non
|
|||||||
data = TelegramMakeInputMessageData(update.message)
|
data = TelegramMakeInputMessageData(update.message)
|
||||||
if (quoted := update.message.reply_to_message):
|
if (quoted := update.message.reply_to_message):
|
||||||
data.quoted = TelegramMakeInputMessageData(quoted)
|
data.quoted = TelegramMakeInputMessageData(quoted)
|
||||||
OnMessageParsed(data)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
if (command := ObjGet(data, "command.name")):
|
||||||
CallEndpoint(command, EventContext(platform="telegram", event=update, manager=context), data)
|
CallEndpoint(command, EventContext(platform="telegram", event=update, manager=context), data)
|
||||||
Thread(target=handler).start()
|
Thread(target=handler).start()
|
||||||
@ -76,8 +76,8 @@ def TelegramHandler(update:telegram.Update, context:CallbackContext=None) -> Non
|
|||||||
def TelegramSender(context:EventContext, data:OutputMessageData):
|
def TelegramSender(context:EventContext, data:OutputMessageData):
|
||||||
result = None
|
result = None
|
||||||
# TODO clean this
|
# TODO clean this
|
||||||
if data.room_id:
|
if data.room and (room_id := data.room.id):
|
||||||
result = context.manager.bot.send_message(data.room_id, text=data.text_plain)
|
result = context.manager.bot.send_message(room_id, text=data.text_plain)
|
||||||
else:
|
else:
|
||||||
replyToId = (data.ReplyTo or context.event.message.message_id)
|
replyToId = (data.ReplyTo or context.event.message.message_id)
|
||||||
if data.media:
|
if data.media:
|
||||||
@ -98,9 +98,9 @@ def TelegramSender(context:EventContext, data:OutputMessageData):
|
|||||||
# TODO support usernames
|
# TODO support usernames
|
||||||
def TelegramLinker(data:InputMessageData) -> SafeNamespace:
|
def TelegramLinker(data:InputMessageData) -> SafeNamespace:
|
||||||
linked = SafeNamespace()
|
linked = SafeNamespace()
|
||||||
if data.room.id:
|
if (room_id := data.room.id):
|
||||||
# prefix must be dropped for groups and channels, while direct chats apparently can never be linked
|
# prefix must be dropped for groups and channels, while direct chats apparently can never be linked
|
||||||
if (room_id := "100".join(data.room.id.split("telegram:")[1].split("100")[1:])):
|
if (room_id := "100".join(room_id.split("telegram:")[1].split("100")[1:])):
|
||||||
# apparently Telegram doesn't really support links to rooms by id without a message id, so we just use a null one
|
# apparently Telegram doesn't really support links to rooms by id without a message id, so we just use a null one
|
||||||
linked.room = f"https://t.me/c/{room_id}/0"
|
linked.room = f"https://t.me/c/{room_id}/0"
|
||||||
if data.message_id:
|
if data.message_id:
|
||||||
|
@ -59,14 +59,14 @@ class WebServerClass(BaseHTTPRequestHandler):
|
|||||||
self.send_header("Location", f"/form/{uuid}")
|
self.send_header("Location", f"/form/{uuid}")
|
||||||
self.end_headers()
|
self.end_headers()
|
||||||
data = WebMakeInputMessageData(text, uuid)
|
data = WebMakeInputMessageData(text, uuid)
|
||||||
OnMessageParsed(data)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
if (command := ObjGet(data, "command.name")):
|
||||||
CallEndpoint(command, EventContext(platform="web", event=SafeNamespace(room_id=uuid)), data)
|
CallEndpoint(command, EventContext(platform="web", event=SafeNamespace(room_id=uuid)), data)
|
||||||
|
|
||||||
def WebMakeInputMessageData(text:str, uuid:str) -> InputMessageData:
|
def WebMakeInputMessageData(text:str, uuid:str) -> InputMessageData:
|
||||||
return InputMessageData(
|
return InputMessageData(
|
||||||
text_plain = text,
|
text_plain = text,
|
||||||
command = ParseCommand(text),
|
command = ParseCommand(text, "web"),
|
||||||
room = SafeNamespace(
|
room = SafeNamespace(
|
||||||
id = f"web:{uuid}",
|
id = f"web:{uuid}",
|
||||||
),
|
),
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
# ================================== #
|
# ================================== #
|
||||||
|
|
||||||
def cSource(context:EventContext, data:InputMessageData) -> None:
|
def cSource(context:EventContext, data:InputMessageData) -> None:
|
||||||
SendMessage(context, {"TextPlain": ("""\
|
SendMessage(context, {"text_plain": ("""\
|
||||||
* Original Code: {https://gitlab.com/octospacc/WinDog}
|
* Original Code: {https://gitlab.com/octospacc/WinDog}
|
||||||
* Mirror: {https://github.com/octospacc/WinDog}
|
* Mirror: {https://github.com/octospacc/WinDog}
|
||||||
""" + (f"* Modified Code: {{{ModifiedSourceUrl}}}" if ModifiedSourceUrl else ""))})
|
""" + (f"* Modified Code: {{{ModifiedSourceUrl}}}" if ModifiedSourceUrl else ""))})
|
||||||
|
@ -10,8 +10,8 @@ def cBroadcast(context:EventContext, data:InputMessageData) -> None:
|
|||||||
text = data.command.body
|
text = data.command.body
|
||||||
if not (destination and text):
|
if not (destination and text):
|
||||||
return SendMessage(context, OutputMessageData(text_plain="Bad usage."))
|
return SendMessage(context, OutputMessageData(text_plain="Bad usage."))
|
||||||
SendMessage(context, OutputMessageData(text_plain=text, room_id=destination))
|
SendMessage(context, {"text_plain": text, "room": SafeNamespace(id=destination)})
|
||||||
SendMessage(context, OutputMessageData(text_plain="Executed."))
|
SendMessage(context, {"text_plain": "Executed."})
|
||||||
|
|
||||||
RegisterModule(name="Broadcast", endpoints=[
|
RegisterModule(name="Broadcast", endpoints=[
|
||||||
SafeNamespace(names=["broadcast"], handler=cBroadcast, body=True, arguments={
|
SafeNamespace(names=["broadcast"], handler=cBroadcast, body=True, arguments={
|
||||||
|
@ -1,7 +1,28 @@
|
|||||||
import base64
|
import base64
|
||||||
|
from binascii import Error as binascii_Error
|
||||||
|
|
||||||
#RegisterModule(name="Codings", group="Geek", endpoints={
|
def mCodings(context:EventContext, data:InputMessageData):
|
||||||
# "Encode": CreateEndpoint(["encode"], summary="", handler=cEncode),
|
algorithms = ["base64"]
|
||||||
# "Decode": CreateEndpoint(["decode"], summary="", handler=cDecode),
|
methods = {
|
||||||
#})
|
"encode_base64": base64.b64encode,
|
||||||
|
"decode_base64": base64.b64decode,
|
||||||
|
"encode_b64": base64.b64encode,
|
||||||
|
"decode_b64": base64.b64decode,
|
||||||
|
}
|
||||||
|
if (method := ObjGet(methods, f"{data.command.name}_{data.command.arguments.algorithm}")):
|
||||||
|
try:
|
||||||
|
result = method((data.command.body or (data.quoted and data.quoted.text_plain)).encode()).decode()
|
||||||
|
SendMessage(context, {"text_html": f"<pre>{html_escape(result)}</pre>"})
|
||||||
|
except binascii_Error:
|
||||||
|
SendMessage(context, {"text_plain": f"An error occurred."})
|
||||||
|
else:
|
||||||
|
language = data.user.settings.language
|
||||||
|
SendMessage(context, {
|
||||||
|
"text_html": f'{context.endpoint.help_text(language)}\n\n{context.module.get_string("algorithms", language)}: {algorithms}'})
|
||||||
|
|
||||||
|
RegisterModule(name="Codings", group="Geek", endpoints=[
|
||||||
|
SafeNamespace(names=["encode", "decode"], handler=mCodings, body=False, quoted=False, arguments={
|
||||||
|
"algorithm": True,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
|
||||||
|
20
ModWinDog/Codings/Codings.yaml
Normal file
20
ModWinDog/Codings/Codings.yaml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
summary:
|
||||||
|
en: Functions for encoding content.
|
||||||
|
it: Funzioni per codificare contenuti.
|
||||||
|
endpoints:
|
||||||
|
encode:
|
||||||
|
body:
|
||||||
|
en: Text to encode
|
||||||
|
it: Testo da codificare
|
||||||
|
decode:
|
||||||
|
body:
|
||||||
|
en: Text to decode
|
||||||
|
it: Testo da decodificare
|
||||||
|
algorithms:
|
||||||
|
en: Available algorithms
|
||||||
|
it: Algoritmi disponibili
|
||||||
|
arguments:
|
||||||
|
algorithm:
|
||||||
|
en: Algorithm
|
||||||
|
it: Algoritmo
|
||||||
|
|
@ -6,9 +6,10 @@
|
|||||||
from json import dumps as json_dumps
|
from json import dumps as json_dumps
|
||||||
|
|
||||||
def cDump(context:EventContext, data:InputMessageData):
|
def cDump(context:EventContext, data:InputMessageData):
|
||||||
|
if (message := data.quoted):
|
||||||
|
dump_text = json_dumps(message, default=(lambda obj: obj.__dict__), indent=" ")
|
||||||
SendMessage(context, {
|
SendMessage(context, {
|
||||||
"text_html": (f'<pre>{json_dumps(message, default=(lambda obj: obj.__dict__), indent=" ")}</pre>'
|
"text_html": (f'<pre>{html_escape(dump_text)}</pre>' if message
|
||||||
if (message := ObjGet(data, "quoted"))
|
|
||||||
else context.endpoint.help_text(data.user.settings.language))})
|
else context.endpoint.help_text(data.user.settings.language))})
|
||||||
|
|
||||||
RegisterModule(name="Dumper", group="Geek", endpoints=[
|
RegisterModule(name="Dumper", group="Geek", endpoints=[
|
||||||
|
@ -9,13 +9,13 @@ g4fClient = G4FClient()
|
|||||||
|
|
||||||
def cGpt(context:EventContext, data:InputMessageData) -> None:
|
def cGpt(context:EventContext, data:InputMessageData) -> None:
|
||||||
if not (prompt := data.command.body):
|
if not (prompt := data.command.body):
|
||||||
return SendMessage(context, {"Text": "You must type some text."})
|
return SendMessage(context, {"text_plain": "You must type some text."})
|
||||||
output = None
|
output = None
|
||||||
while not output or output.startswith("sorry, 您的ip已由于触发防滥用检测而被封禁,本服务网址是"): # quick fix for a strange ratelimit message
|
while not output or output.startswith("sorry, 您的ip已由于触发防滥用检测而被封禁,本服务网址是"): # quick fix for a strange ratelimit message
|
||||||
output = ""
|
output = ""
|
||||||
for completion in g4fClient.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], stream=True):
|
for completion in g4fClient.chat.completions.create(model="gpt-3.5-turbo", messages=[{"role": "user", "content": prompt}], stream=True):
|
||||||
output += (completion.choices[0].delta.content or "")
|
output += (completion.choices[0].delta.content or "")
|
||||||
return SendMessage(context, {"TextPlain": f"[🤖️ GPT]\n\n{output}"})
|
return SendMessage(context, {"text_plain": f"[🤖️ GPT]\n\n{output}"})
|
||||||
|
|
||||||
RegisterModule(name="GPT", endpoints=[
|
RegisterModule(name="GPT", endpoints=[
|
||||||
SafeNamespace(names=["gpt", "chatgpt"], handler=cGpt, body=True),
|
SafeNamespace(names=["gpt", "chatgpt"], handler=cGpt, body=True),
|
||||||
|
@ -5,6 +5,7 @@ endpoints:
|
|||||||
hash:
|
hash:
|
||||||
summary:
|
summary:
|
||||||
en: Responds with the hash-sum of the included or quoted text.
|
en: Responds with the hash-sum of the included or quoted text.
|
||||||
|
it: Risponde con la somma hash del testo incluso o citato.
|
||||||
algorithms:
|
algorithms:
|
||||||
en: Available algorithms
|
en: Available algorithms
|
||||||
it: Algoritmi disponibili
|
it: Algoritmi disponibili
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
# TODO: implement /help <commandname> feature
|
# TODO: implement /help <commandname> feature
|
||||||
def cHelp(context:EventContext, data:InputMessageData) -> None:
|
def cHelp(context:EventContext, data:InputMessageData) -> None:
|
||||||
text = (context.endpoint.get_string(lang=data.user.settings.language) or '')
|
text = (context.endpoint.get_string(lang=data.user.settings.language) or '').strip()
|
||||||
language = data.user.settings.language
|
language = data.user.settings.language
|
||||||
for module in Modules:
|
for module in Modules:
|
||||||
summary = Modules[module].get_string("summary", language)
|
summary = Modules[module].get_string("summary", language)
|
||||||
@ -14,6 +14,7 @@ def cHelp(context:EventContext, data:InputMessageData) -> None:
|
|||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
summary = Modules[module].get_string(f"endpoints.{endpoint.names[0]}.summary", language)
|
summary = Modules[module].get_string(f"endpoints.{endpoint.names[0]}.summary", language)
|
||||||
text += (f"\n* /{', /'.join(endpoint.names)}" + (f": {summary}" if summary else ''))
|
text += (f"\n* /{', /'.join(endpoint.names)}" + (f": {summary}" if summary else ''))
|
||||||
|
text = text.strip()
|
||||||
SendMessage(context, {"text_html": text})
|
SendMessage(context, {"text_html": text})
|
||||||
|
|
||||||
RegisterModule(name="Help", group="Basic", endpoints=[
|
RegisterModule(name="Help", group="Basic", endpoints=[
|
||||||
|
@ -27,7 +27,7 @@ def cLua(context:EventContext, data:InputMessageData) -> None:
|
|||||||
# TODO update quoted api getting
|
# TODO update quoted api getting
|
||||||
scriptText = (data.command.body or (data.quoted and data.quoted.text_plain))
|
scriptText = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
if not scriptText:
|
if not scriptText:
|
||||||
return SendMessage(context, {"Text": "You must provide some Lua code to execute."})
|
return SendMessage(context, {"text_plain": "You must provide some Lua code to execute."})
|
||||||
luaRuntime = NewLuaRuntime(max_memory=LuaMemoryLimit, register_eval=False, register_builtins=False, attribute_filter=luaAttributeFilter)
|
luaRuntime = NewLuaRuntime(max_memory=LuaMemoryLimit, register_eval=False, register_builtins=False, attribute_filter=luaAttributeFilter)
|
||||||
luaRuntime.eval(f"""(function()
|
luaRuntime.eval(f"""(function()
|
||||||
_windog = {{ stdout = "" }}
|
_windog = {{ stdout = "" }}
|
||||||
|
@ -15,7 +15,7 @@ from re import compile as re_compile
|
|||||||
|
|
||||||
def cExec(context:EventContext, data:InputMessageData) -> None:
|
def cExec(context:EventContext, data:InputMessageData) -> None:
|
||||||
if not (len(data.command.tokens) >= 2 and data.command.tokens[1].lower() in ExecAllowed):
|
if not (len(data.command.tokens) >= 2 and data.command.tokens[1].lower() in ExecAllowed):
|
||||||
return SendMessage(context, {"Text": "This feature is not implemented [Security Issue]."})
|
return SendMessage(context, {"text_plain": "This feature is not implemented [Security Issue]."})
|
||||||
command = data.command.tokens[1].lower()
|
command = data.command.tokens[1].lower()
|
||||||
output = subprocess.run(
|
output = subprocess.run(
|
||||||
("sh", "-c", f"export PATH=$PATH:/usr/games; {command}"),
|
("sh", "-c", f"export PATH=$PATH:/usr/games; {command}"),
|
||||||
|
132
WinDog.py
132
WinDog.py
@ -5,7 +5,6 @@
|
|||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
import json, time
|
import json, time
|
||||||
from binascii import hexlify
|
|
||||||
from glob import glob
|
from glob import glob
|
||||||
from hashlib import new as hashlib_new
|
from hashlib import new as hashlib_new
|
||||||
from html import escape as html_escape, unescape as html_unescape
|
from html import escape as html_escape, unescape as html_unescape
|
||||||
@ -17,26 +16,20 @@ from traceback import format_exc, format_exc as traceback_format_exc
|
|||||||
from urllib import parse as urlparse, parse as urllib_parse
|
from urllib import parse as urlparse, parse as urllib_parse
|
||||||
from yaml import load as yaml_load, BaseLoader as yaml_BaseLoader
|
from yaml import load as yaml_load, BaseLoader as yaml_BaseLoader
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from markdown import markdown
|
|
||||||
from LibWinDog.Types import *
|
from LibWinDog.Types import *
|
||||||
from LibWinDog.Config import *
|
from LibWinDog.Config import *
|
||||||
from LibWinDog.Database import *
|
from LibWinDog.Database import *
|
||||||
|
|
||||||
# <https://daringfireball.net/projects/markdown/syntax#backslash>
|
|
||||||
MdEscapes = '\\`*_{}[]()<>#+-.!|='
|
|
||||||
|
|
||||||
def ObjectUnion(*objects:object, clazz:object=None):
|
def ObjectUnion(*objects:object, clazz:object=None):
|
||||||
dikt = {}
|
dikt = {}
|
||||||
auto_clazz = None
|
auto_clazz = objects[0].__class__
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
obj_clazz = obj.__class__
|
|
||||||
if not obj:
|
if not obj:
|
||||||
continue
|
continue
|
||||||
if type(obj) == dict:
|
if type(obj) == dict:
|
||||||
obj = (clazz or SafeNamespace)(**obj)
|
obj = (clazz or SafeNamespace)(**obj)
|
||||||
for key, value in tuple(obj.__dict__.items()):
|
for key, value in tuple(obj.__dict__.items()):
|
||||||
dikt[key] = value
|
dikt[key] = value
|
||||||
auto_clazz = obj_clazz
|
|
||||||
return (clazz or auto_clazz)(**dikt)
|
return (clazz or auto_clazz)(**dikt)
|
||||||
|
|
||||||
def Log(text:str, level:str="?", *, newline:bool|None=None, inline:bool=False) -> None:
|
def Log(text:str, level:str="?", *, newline:bool|None=None, inline:bool=False) -> None:
|
||||||
@ -49,15 +42,12 @@ def Log(text:str, level:str="?", *, newline:bool|None=None, inline:bool=False) -
|
|||||||
if LogToFile:
|
if LogToFile:
|
||||||
open("./Log.txt", 'a').write(text + endline)
|
open("./Log.txt", 'a').write(text + endline)
|
||||||
|
|
||||||
|
def call_or_return(obj:any) -> any:
|
||||||
|
return (obj() if callable(obj) else obj)
|
||||||
|
|
||||||
def SureArray(array:any) -> list|tuple:
|
def SureArray(array:any) -> list|tuple:
|
||||||
return (array if type(array) in [list, tuple] else [array])
|
return (array if type(array) in [list, tuple] else [array])
|
||||||
|
|
||||||
def InDict(dikt:dict, key:str, /) -> any:
|
|
||||||
return (dikt[key] if key in dikt else None)
|
|
||||||
|
|
||||||
def DictGet(dikt:dict, key:str, /) -> any:
|
|
||||||
return (dikt[key] if key in dikt else None)
|
|
||||||
|
|
||||||
def ObjGet(node:object, query:str, /) -> any:
|
def ObjGet(node:object, query:str, /) -> any:
|
||||||
for key in query.split('.'):
|
for key in query.split('.'):
|
||||||
if hasattr(node, "__getitem__") and node.__getitem__:
|
if hasattr(node, "__getitem__") and node.__getitem__:
|
||||||
@ -74,11 +64,6 @@ def ObjGet(node:object, query:str, /) -> any:
|
|||||||
return None
|
return None
|
||||||
return node
|
return node
|
||||||
|
|
||||||
def isinstanceSafe(clazz:any, instance:any, /) -> bool:
|
|
||||||
if instance != None:
|
|
||||||
return isinstance(clazz, instance)
|
|
||||||
return False
|
|
||||||
|
|
||||||
def good_yaml_load(text:str):
|
def good_yaml_load(text:str):
|
||||||
return yaml_load(text.replace("\t", " "), Loader=yaml_BaseLoader)
|
return yaml_load(text.replace("\t", " "), Loader=yaml_BaseLoader)
|
||||||
|
|
||||||
@ -94,8 +79,8 @@ def help_text(endpoint, lang:str=None) -> str:
|
|||||||
if endpoint.arguments:
|
if endpoint.arguments:
|
||||||
for argument in endpoint.arguments:
|
for argument in endpoint.arguments:
|
||||||
if endpoint.arguments[argument]:
|
if endpoint.arguments[argument]:
|
||||||
text += f' <{endpoint.get_string(f"arguments.{argument}", lang) or argument}>'
|
text += f' <{endpoint.get_string(f"arguments.{argument}", lang) or endpoint.module.get_string(f"arguments.{argument}", lang) or argument}>'
|
||||||
body_help = endpoint.get_string("body", lang)
|
body_help = (endpoint.get_string("body", lang) or endpoint.module.get_string("body", lang))
|
||||||
quoted_help = (global_string("quoted_message") + (f': {body_help}' if body_help else ''))
|
quoted_help = (global_string("quoted_message") + (f': {body_help}' if body_help else ''))
|
||||||
if not body_help:
|
if not body_help:
|
||||||
body_help = global_string("text")
|
body_help = global_string("text")
|
||||||
@ -116,40 +101,6 @@ def strip_url_scheme(url:str) -> str:
|
|||||||
tokens = urlparse.urlparse(url)
|
tokens = urlparse.urlparse(url)
|
||||||
return f"{tokens.netloc}{tokens.path}"
|
return f"{tokens.netloc}{tokens.path}"
|
||||||
|
|
||||||
def CharEscape(String:str, Escape:str='') -> str:
|
|
||||||
if Escape == 'MARKDOWN':
|
|
||||||
return escape_markdown(String, version=2)
|
|
||||||
else:
|
|
||||||
if Escape == 'MARKDOWN_SPEECH':
|
|
||||||
Escape = '+-_.!()[]{}<>'
|
|
||||||
elif Escape == 'MARKDOWN_SPEECH_FORMAT':
|
|
||||||
Escape = '+-_.!()[]<>'
|
|
||||||
for c in Escape:
|
|
||||||
String = String.replace(c, '\\'+c)
|
|
||||||
return String
|
|
||||||
|
|
||||||
def InferMdEscape(raw:str, plain:str) -> str:
|
|
||||||
chars = ''
|
|
||||||
for char in MdEscapes:
|
|
||||||
if char in raw and char in plain:
|
|
||||||
chars += char
|
|
||||||
return chars
|
|
||||||
|
|
||||||
def MdToTxt(md:str) -> str:
|
|
||||||
return BeautifulSoup(markdown(md), 'html.parser').get_text(' ')
|
|
||||||
|
|
||||||
def HtmlEscapeFull(Raw:str) -> str:
|
|
||||||
New = ''
|
|
||||||
Hex = hexlify(Raw.encode()).decode()
|
|
||||||
for i in range(0, len(Hex), 2):
|
|
||||||
New += f'&#x{Hex[i] + Hex[i+1]};'
|
|
||||||
return New
|
|
||||||
|
|
||||||
def GetWeightedText(*texts) -> str|None:
|
|
||||||
for text in texts:
|
|
||||||
if text:
|
|
||||||
return text
|
|
||||||
|
|
||||||
def RandPercent() -> int:
|
def RandPercent() -> int:
|
||||||
num = randint(0,100)
|
num = randint(0,100)
|
||||||
return (f'{num}.00' if num == 100 else f'{num}.{randint(0,9)}{randint(0,9)}')
|
return (f'{num}.00' if num == 100 else f'{num}.{randint(0,9)}{randint(0,9)}')
|
||||||
@ -166,7 +117,7 @@ def GetUserSettings(user_id:str) -> SafeNamespace|None:
|
|||||||
except EntitySettings.DoesNotExist:
|
except EntitySettings.DoesNotExist:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def ParseCommand(text:str) -> SafeNamespace|None:
|
def ParseCommand(text:str, platform:str) -> SafeNamespace|None:
|
||||||
if not text:
|
if not text:
|
||||||
return None
|
return None
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
@ -177,9 +128,8 @@ def ParseCommand(text:str) -> SafeNamespace|None:
|
|||||||
return None
|
return None
|
||||||
command = SafeNamespace()
|
command = SafeNamespace()
|
||||||
command.tokens = text.split()
|
command.tokens = text.split()
|
||||||
command.name, command_target = (command.tokens[0][1:].lower().split('@') + [''])
|
command.name, command_target = (command.tokens[0][1:].lower().split('@') + [''])[:2]
|
||||||
# TODO ignore tagged commands when they are not directed to the bot's username
|
if command_target and not (command_target == call_or_return(Platforms[platform].agent_info).tag.lower()):
|
||||||
if command_target and not command_target:
|
|
||||||
return None
|
return None
|
||||||
command.body = text[len(command.tokens[0]):].strip()
|
command.body = text[len(command.tokens[0]):].strip()
|
||||||
if command.name not in Endpoints:
|
if command.name not in Endpoints:
|
||||||
@ -199,14 +149,17 @@ def ParseCommand(text:str) -> SafeNamespace|None:
|
|||||||
index += 1
|
index += 1
|
||||||
return command
|
return command
|
||||||
|
|
||||||
def OnMessageParsed(data:InputMessageData) -> None:
|
def OnInputMessageParsed(data:InputMessageData) -> None:
|
||||||
dump_message(data, prefix='>')
|
dump_message(data, prefix='> ')
|
||||||
#handle_bridging(SendMessage, data, from_sent=False)
|
handle_bridging(SendMessage, data, from_sent=False)
|
||||||
update_user_db(data.user)
|
update_user_db(data.user)
|
||||||
|
|
||||||
def OnMessageSent(data:OutputMessageData) -> None:
|
def OnOutputMessageSent(output_data:OutputMessageData, input_data:InputMessageData, from_sent:bool) -> None:
|
||||||
dump_message(data, prefix='<')
|
if (not from_sent) and input_data:
|
||||||
#handle_bridging(SendMessage, data, from_sent=True) # TODO fix duplicate messages lol
|
output_data = ObjectUnion(output_data, {"room": input_data.room})
|
||||||
|
dump_message(output_data, prefix=f'<{"*" if from_sent else " "}')
|
||||||
|
if not from_sent:
|
||||||
|
handle_bridging(SendMessage, output_data, from_sent=True)
|
||||||
|
|
||||||
# TODO: fix to send messages to different rooms, this overrides destination data but that gives problems with rebroadcasting the bot's own messages
|
# TODO: fix to send messages to different rooms, this overrides destination data but that gives problems with rebroadcasting the bot's own messages
|
||||||
def handle_bridging(method:callable, data:MessageData, from_sent:bool):
|
def handle_bridging(method:callable, data:MessageData, from_sent:bool):
|
||||||
@ -223,8 +176,8 @@ def handle_bridging(method:callable, data:MessageData, from_sent:bool):
|
|||||||
for room_id in rooms:
|
for room_id in rooms:
|
||||||
method(
|
method(
|
||||||
SafeNamespace(platform=room_id.split(':')[0]),
|
SafeNamespace(platform=room_id.split(':')[0]),
|
||||||
ObjectUnion(data, {"room_id": room_id}, ({"text_plain": text_plain, "text_markdown": None, "text_html": text_html} if data.user else None)),
|
ObjectUnion(data, {"room": SafeNamespace(id=room_id)}, ({"text_plain": text_plain, "text_markdown": None, "text_html": text_html} if data.user else None)),
|
||||||
from_sent)
|
from_sent=True)
|
||||||
|
|
||||||
def update_user_db(user:SafeNamespace) -> None:
|
def update_user_db(user:SafeNamespace) -> None:
|
||||||
if not (user and user.id):
|
if not (user and user.id):
|
||||||
@ -251,43 +204,29 @@ def dump_message(data:InputMessageData, prefix:str='') -> None:
|
|||||||
|
|
||||||
def SendMessage(context:EventContext, data:OutputMessageData, from_sent:bool=False) -> None:
|
def SendMessage(context:EventContext, data:OutputMessageData, from_sent:bool=False) -> None:
|
||||||
data = (OutputMessageData(**data) if type(data) == dict else data)
|
data = (OutputMessageData(**data) if type(data) == dict else data)
|
||||||
|
if data.text_html and not data.text_plain:
|
||||||
# TODO remove this after all modules are changed
|
data.text_plain = BeautifulSoup(data.text_html, "html.parser").get_text()
|
||||||
if data.Text and not data.text:
|
elif data.text_markdown and not data.text_plain:
|
||||||
data.text = data.Text
|
data.text_plain = data.text_markdown
|
||||||
if data.TextPlain and not data.text_plain:
|
elif data.text_plain and not data.text_html:
|
||||||
data.text_plain = data.TextPlain
|
data.text_html = html_escape(data.text_plain)
|
||||||
if data.text and not data.text_plain:
|
|
||||||
data.text_plain = data.text
|
|
||||||
|
|
||||||
if data.text_plain or data.text_markdown or data.text_html:
|
|
||||||
if data.text_html and not data.text_plain:
|
|
||||||
data.text_plain = BeautifulSoup(data.text_html, "html.parser").get_text()
|
|
||||||
elif data.text_markdown and not data.text_plain:
|
|
||||||
data.text_plain = data.text_markdown
|
|
||||||
elif data.text_plain and not data.text_html:
|
|
||||||
data.text_html = html_escape(data.text_plain)
|
|
||||||
elif data.text:
|
|
||||||
# our old system attempts to always receive Markdown and retransform when needed
|
|
||||||
data.text_plain = MdToTxt(data.text)
|
|
||||||
data.text_markdown = CharEscape(html_unescape(data.text), InferMdEscape(html_unescape(data.text), data.text_plain))
|
|
||||||
#data.text_html = ???
|
|
||||||
if data.media:
|
if data.media:
|
||||||
data.media = SureArray(data.media)
|
data.media = SureArray(data.media)
|
||||||
if data.room_id:
|
if data.room and (room_id := data.room.id):
|
||||||
tokens = data.room_id.split(':')
|
tokens = room_id.split(':')
|
||||||
if tokens[0] != context.platform:
|
if tokens[0] != context.platform:
|
||||||
context.platform = tokens[0]
|
context.platform = tokens[0]
|
||||||
context.manager = context.event = None
|
context.manager = context.event = None
|
||||||
data.room_id = ':'.join(tokens[1:])
|
data.room.id = ':'.join(tokens[1:])
|
||||||
|
if data.ReplyTo: # TODO decide if this has to be this way
|
||||||
|
data.ReplyTo = ':'.join(data.ReplyTo.split(':')[1:])
|
||||||
if context.platform not in Platforms:
|
if context.platform not in Platforms:
|
||||||
return None
|
return None
|
||||||
platform = Platforms[context.platform]
|
platform = Platforms[context.platform]
|
||||||
if (not context.manager) and (manager := platform.manager_class):
|
if (not context.manager) and (manager := platform.manager_class):
|
||||||
context.manager = (manager() if callable(manager) else manager)
|
context.manager = call_or_return(manager)
|
||||||
result = platform.sender(context, data)
|
result = platform.sender(context, data)
|
||||||
if not from_sent:
|
OnOutputMessageSent(data, context.data, from_sent)
|
||||||
OnMessageSent(data)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def SendNotice(context:EventContext, data) -> None:
|
def SendNotice(context:EventContext, data) -> None:
|
||||||
@ -314,6 +253,7 @@ def RegisterModule(name:str, endpoints:dict, *, group:str|None=None) -> None:
|
|||||||
|
|
||||||
def CallEndpoint(name:str, context:EventContext, data:InputMessageData):
|
def CallEndpoint(name:str, context:EventContext, data:InputMessageData):
|
||||||
endpoint = Endpoints[name]
|
endpoint = Endpoints[name]
|
||||||
|
context.data = data
|
||||||
context.module = endpoint.module
|
context.module = endpoint.module
|
||||||
context.endpoint = endpoint
|
context.endpoint = endpoint
|
||||||
context.endpoint.get_string = (lambda query=data.command.name, lang=None:
|
context.endpoint.get_string = (lambda query=data.command.name, lang=None:
|
||||||
@ -373,10 +313,6 @@ if __name__ == '__main__':
|
|||||||
for file in files:
|
for file in files:
|
||||||
if file.endswith(".py"):
|
if file.endswith(".py"):
|
||||||
exec(open(f"{path}/{name}.py", 'r').read())
|
exec(open(f"{path}/{name}.py", 'r').read())
|
||||||
# TODO load locales
|
|
||||||
#for name in listdir(path):
|
|
||||||
# if name.lower().endswith('.json'):
|
|
||||||
#
|
|
||||||
Log("...Done. ✅️", inline=True, newline=True)
|
Log("...Done. ✅️", inline=True, newline=True)
|
||||||
|
|
||||||
Log("💽️ Loading Configuration... ", newline=False)
|
Log("💽️ Loading Configuration... ", newline=False)
|
||||||
|
Reference in New Issue
Block a user