Rename some internal functions; Update db and start work on message filters

This commit is contained in:
2024-10-21 00:26:08 +02:00
parent 5ba0df43c4
commit 9220c95636
28 changed files with 213 additions and 62 deletions

View File

@ -14,19 +14,64 @@ class BaseModel(Model):
class EntitySettings(BaseModel): class EntitySettings(BaseModel):
language = CharField(null=True) language = CharField(null=True)
#country = ...
#timezone = ...
class Entity(BaseModel): class Entity(BaseModel):
id = CharField(null=True) id = CharField(null=True)
id_hash = CharField() id_hash = CharField()
settings = ForeignKeyField(EntitySettings, backref="entity", null=True) settings = ForeignKeyField(EntitySettings, backref="entity", null=True)
class User(Entity): class File(BaseModel):
pass path = CharField()
content = BlobField()
owner = ForeignKeyField(Entity, backref="files")
class Meta:
indexes = (
(('path', 'owner'), True),
)
#class BaseFilter(BaseModel):
# name = CharField(null=True)
# owner = ForeignKeyField(Entity, backref="filters")
#class ScriptFilter(BaseFilter):
# script = TextField()
#class StaticFilter(BaseFilter):
# response = TextField()
class Filter(BaseModel):
name = CharField(null=True)
trigger = CharField(null=True)
output = CharField(null=True)
owner = ForeignKeyField(Entity, backref="filters")
class Room(Entity): class Room(Entity):
pass pass
Db.create_tables([EntitySettings, User, Room], safe=True) UserToRoomDeferred = DeferredThroughModel()
FilterToRoomDeferred = DeferredThroughModel()
class User(Entity):
rooms = ManyToManyField(Room, backref="users", through_model=UserToRoomDeferred)
class UserToRoom(BaseModel):
user = ForeignKeyField(User, backref="room_links")
room = ForeignKeyField(Room, backref="user_links")
class FilterToRoom(BaseModel):
filter = ForeignKeyField(Filter, backref="room_links")
room = ForeignKeyField(Room, backref="filter_links")
UserToRoomDeferred.set_model(UserToRoom)
FilterToRoomDeferred.set_model(FilterToRoom)
Db.create_tables([
EntitySettings, File, Filter,
User, Room,
FilterToRoom, UserToRoom,
], safe=True)
class UserSettingsData(): class UserSettingsData():
def __new__(cls, user_id:str=None) -> SafeNamespace: def __new__(cls, user_id:str=None) -> SafeNamespace:

View File

@ -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"])
OnInputMessageParsed(data) on_input_message_parsed(data)
call_endpoint(EventContext(platform="mastodon", event=event, manager=Mastodon), data) call_endpoint(EventContext(platform="mastodon", event=event, manager=Mastodon), data)
def MastodonSender(context:EventContext, data:OutputMessageData) -> None: def MastodonSender(context:EventContext, data:OutputMessageData) -> None:
@ -67,5 +67,5 @@ def MastodonSender(context:EventContext, data:OutputMessageData) -> None:
visibility=('direct' if context.event['status']['visibility'] == 'direct' else 'unlisted'), visibility=('direct' if context.event['status']['visibility'] == 'direct' else 'unlisted'),
) )
RegisterPlatform(name="Mastodon", main=MastodonMain, sender=MastodonSender, manager_class=mastodon.Mastodon) register_platform(name="Mastodon", main=MastodonMain, sender=MastodonSender, manager_class=mastodon.Mastodon)

View File

@ -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)
OnInputMessageParsed(data) on_input_message_parsed(data)
call_endpoint(EventContext(platform="matrix", event=SafeNamespace(room=room, event=event), manager=MatrixClient), data) call_endpoint(EventContext(platform="matrix", event=SafeNamespace(room=room, event=event), manager=MatrixClient), data)
def MatrixSender(context:EventContext, data:OutputMessageData): def MatrixSender(context:EventContext, data:OutputMessageData):
@ -97,5 +97,5 @@ def MatrixSender(context:EventContext, data:OutputMessageData):
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}))
RegisterPlatform(name="Matrix", main=MatrixMain, sender=MatrixSender, manager_class=(lambda:MatrixClient)) register_platform(name="Matrix", main=MatrixMain, sender=MatrixSender, manager_class=(lambda:MatrixClient))

View File

@ -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)
OnInputMessageParsed(data) on_input_message_parsed(data)
call_endpoint(EventContext(platform="telegram", event=update, manager=context), data) call_endpoint(EventContext(platform="telegram", event=update, manager=context), data)
Thread(target=handler).start() Thread(target=handler).start()
@ -107,7 +107,7 @@ def TelegramLinker(data:InputMessageData) -> SafeNamespace:
linked.message = f"https://t.me/c/{room_id}/{message_id}" linked.message = f"https://t.me/c/{room_id}/{message_id}"
return linked return linked
RegisterPlatform( register_platform(
name="Telegram", name="Telegram",
main=TelegramMain, main=TelegramMain,
sender=TelegramSender, sender=TelegramSender,

View File

@ -55,7 +55,7 @@ class WebServerClass(BaseHTTPRequestHandler):
def init_new_room(self, room_id:str=None): def init_new_room(self, room_id:str=None):
if not room_id: if not room_id:
room_id = str(uuid7().hex) room_id = str(uuid7().hex)
WebQueues[room_id] = {}#{"0": queue.Queue()} WebQueues[room_id] = {}
#WebPushEvent(room_id, ".start", self.headers) #WebPushEvent(room_id, ".start", self.headers)
#Thread(target=lambda:WebAntiDropEnqueue(room_id)).start() #Thread(target=lambda:WebAntiDropEnqueue(room_id)).start()
self.do_redirect(f"/{room_id}") self.do_redirect(f"/{room_id}")
@ -65,24 +65,25 @@ class WebServerClass(BaseHTTPRequestHandler):
self.send_header("Content-Type", "text/html; charset=UTF-8") self.send_header("Content-Type", "text/html; charset=UTF-8")
self.send_header("Content-Encoding", "chunked") self.send_header("Content-Encoding", "chunked")
self.end_headers() self.end_headers()
target = f"/{room_id}/{user_id}?page-target=1#load-target"
if not is_redirected: if not is_redirected:
return self.wfile.write(f'''{web_html_prefix(head_extra=f'<meta http-equiv="refresh" content="0; url={target}">')} target = f"/{room_id}/{user_id}?page-target=1#load-target"
self.wfile.write(f'''{web_html_prefix(head_extra=f'<meta http-equiv="refresh" content="0; url={target}">')}
<h3><a href="/" target="_parent">WinDog 🐶️</a></h3> <h3><a href="/" target="_parent">WinDog 🐶️</a></h3>
<p>Initializing... <a href="{target}">Click here</a> if you are not automatically redirected.</p>'''.encode()) <p>Initializing... <a href="{target}">Click here</a> if you are not automatically redirected.</p>'''.encode())
self.wfile.write(f'''{web_html_prefix()} else:
self.wfile.write(f'''{web_html_prefix()}
<h3><a href="/" target="_parent">WinDog 🐶️</a></h3> <h3><a href="/" target="_parent">WinDog 🐶️</a></h3>
<div class="sticky-box"> <div class="sticky-box">
<p id="load-target" style="display: none;"><span style="color: red;">Background loading seems to have stopped...</span> Please open a new chat or <a href="{target}">reload this one</a> if you can't send new messages.</p> <p id="load-target" style="display: none;"><span style="color: red;">Background loading seems to have stopped...</span> Please open a new chat or <a href="/{room_id}">reload this current one</a> if you can't send new messages.</p>
<div class="input-frame"><iframe src="/form/{room_id}/{user_id}"></iframe></div> <div class="input-frame"><iframe src="/form/{room_id}/{user_id}"></iframe></div>
</div> </div>
<div style="display: flex; flex-direction: column-reverse;">'''.encode()) <div style="display: flex; flex-direction: column-reverse;">'''.encode())
while True: while True:
# TODO this apparently makes us lose threads, we should handle dropped connections? # TODO this apparently makes us lose threads, we should handle dropped connections?
try: try:
self.wfile.write(WebMakeMessageHtml(WebQueues[room_id][user_id].get(block=False), user_id).encode()) self.wfile.write(WebMakeMessageHtml(WebQueues[room_id][user_id].get(block=False), user_id).encode())
except queue.Empty: except queue.Empty:
time.sleep(0.01) time.sleep(0.01)
def send_form_html(self, room_id:str, user_id:str): def send_form_html(self, room_id:str, user_id:str):
self.send_text_content((f'''{web_html_prefix("form no-margin")} self.send_text_content((f'''{web_html_prefix("form no-margin")}
@ -158,7 +159,7 @@ def WebPushEvent(room_id:str, user_id:str, text:str, headers:dict[str:str]):
settings = UserSettingsData(), settings = UserSettingsData(),
), ),
) )
OnInputMessageParsed(data) on_input_message_parsed(data)
WebSender(context, ObjectUnion(data, {"from_user": True})) WebSender(context, ObjectUnion(data, {"from_user": True}))
call_endpoint(context, data) call_endpoint(context, data)
@ -183,7 +184,6 @@ def WebMain(path:str) -> bool:
return True return True
def WebSender(context:EventContext, data:OutputMessageData) -> None: def WebSender(context:EventContext, data:OutputMessageData) -> None:
#WebQueues[context.event.room_id][context.event.user_id].put(data)
for user_id in (room := WebQueues[context.event.room_id]): for user_id in (room := WebQueues[context.event.room_id]):
room[user_id].put(data) room[user_id].put(data)
@ -194,5 +194,5 @@ def WebAntiDropEnqueue(room_id:str, user_id:str):
WebQueues[room_id][user_id].put(OutputMessageData()) WebQueues[room_id][user_id].put(OutputMessageData())
time.sleep(WebConfig["anti_drop_interval"]) time.sleep(WebConfig["anti_drop_interval"])
RegisterPlatform(name="Web", main=WebMain, sender=WebSender) register_platform(name="Web", main=WebMain, sender=WebSender)

0
LibWinDog/Platforms/Web/requirements.txt Normal file → Executable file
View File

0
LibWinDog/Platforms/Web/windog.css Normal file → Executable file
View File

0
LibWinDog/Platforms/Web/windog.js Normal file → Executable file
View File

View File

@ -52,7 +52,7 @@ def cPing(context:EventContext, data:InputMessageData):
#def cEval(context:EventContext, data:InputMessageData) -> None: #def cEval(context:EventContext, data:InputMessageData) -> None:
# send_message(context, {"Text": choice(Locale.__('eval'))}) # send_message(context, {"Text": choice(Locale.__('eval'))})
RegisterModule(name="Base", endpoints=[ register_module(name="Base", endpoints=[
SafeNamespace(names=["source"], handler=cSource), SafeNamespace(names=["source"], handler=cSource),
SafeNamespace(names=["config", "settings"], handler=cConfig, body=False, arguments={ SafeNamespace(names=["config", "settings"], handler=cConfig, body=False, arguments={
"get": True, "get": True,

View File

@ -12,10 +12,10 @@ def cBroadcast(context:EventContext, data:InputMessageData):
if not (destination and text): if not (destination and text):
return send_status_400(context, language) return send_status_400(context, language)
result = send_message(context, {"text_plain": text, "room": SafeNamespace(id=destination)}) result = send_message(context, {"text_plain": text, "room": SafeNamespace(id=destination)})
send_message(context, {"text_plain": "Executed."}) send_status(context, 201, language)
return result return result
RegisterModule(name="Broadcast", endpoints=[ register_module(name="Broadcast", endpoints=[
SafeNamespace(names=["broadcast"], handler=cBroadcast, body=True, arguments={ SafeNamespace(names=["broadcast"], handler=cBroadcast, body=True, arguments={
"destination": True, "destination": True,
}), }),

View File

@ -41,7 +41,7 @@ def mCodings(context:EventContext, data:InputMessageData):
except Exception: except Exception:
return send_status_error(context, language) return send_status_error(context, language)
RegisterModule(name="Codings", group="Geek", endpoints=[ register_module(name="Codings", group="Geek", endpoints=[
SafeNamespace(names=["encode", "decode"], handler=mCodings, body=False, quoted=False, arguments={ SafeNamespace(names=["encode", "decode"], handler=mCodings, body=False, quoted=False, arguments={
"algorithm": True, "algorithm": True,
}, help_extra=(lambda endpoint, lang: f'{endpoint.module.get_string("algorithms", lang)}: <code>{"</code>, <code>".join(CodingsAlgorithms)}</code>.')), }, help_extra=(lambda endpoint, lang: f'{endpoint.module.get_string("algorithms", lang)}: <code>{"</code>, <code>".join(CodingsAlgorithms)}</code>.')),

View File

@ -6,13 +6,14 @@
from json import dumps as json_dumps from json import dumps as json_dumps
# TODO work with links to messages # TODO work with links to messages
# TODO remove "wrong" objects like callables
def cDump(context:EventContext, data:InputMessageData): def cDump(context:EventContext, data:InputMessageData):
if not (message := data.quoted): if not (message := data.quoted):
return send_status_400(context, data.user.settings.language) return send_status_400(context, data.user.settings.language)
text = json_dumps(message, default=(lambda obj: obj.__dict__), indent=" ") text = json_dumps(message, default=(lambda obj: (obj.__dict__ if not callable(obj) else None)), indent=" ")
return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'}) return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'})
RegisterModule(name="Dumper", group="Geek", endpoints=[ register_module(name="Dumper", group="Geek", endpoints=[
SafeNamespace(names=["dump"], handler=cDump, quoted=True), SafeNamespace(names=["dump"], handler=cDump, quoted=True),
]) ])

View File

@ -20,7 +20,7 @@ def cEcho(context:EventContext, data:InputMessageData):
prefix = '' prefix = ''
return send_message(context, {"text_html": (prefix + html_escape(text))}) return send_message(context, {"text_html": (prefix + html_escape(text))})
RegisterModule(name="Echo", endpoints=[ register_module(name="Echo", endpoints=[
SafeNamespace(names=["echo"], handler=cEcho, body=True), SafeNamespace(names=["echo"], handler=cEcho, body=True),
]) ])

69
ModWinDog/Filters/Filters.py Executable file
View File

@ -0,0 +1,69 @@
# ==================================== #
# WinDog multi-purpose chatbot #
# Licensed under AGPLv3 by OctoSpacc #
# ==================================== #
def cFilters(context:EventContext, data:InputMessageData):
language = data.user.settings.language
if not check_room_admin(data.user):
return send_status(context, 403, language)
# action: create, delete, toggle <chatid, name/filterid>
# * (input) handle, ignore <...>
# * (output) setscript <..., script>
# * (output) insert, remove <..., groupid, message>
#arguments = data.command.parse_arguments(4)
if not (action := data.command.arguments.action) or (action not in ["list", "create", "delete"]):
return send_status_400(context, language)
[room_id, filter_id, command_data] = ((None,) * 3)
for token in data.command.tokens[2:]:
if (not room_id) and (':' in token):
room_id = token
elif (not filter_id):
filter_id = token
elif (not command_data):
command_data = token
if not room_id:
room_id = data.room.id
if (action in ["delete"]) and (not filter_id):
return send_status_400(context, language)
match action:
case "list":
filters_list = ""
for filter in Filter.select().where(Filter.owner == room_id).namedtuples():
filters_list += f"\n* <code>{filter.id}</code> — {filter.name or '[?]'}"
if filters_list:
return send_message(context, {"text_html": f"Filters for room <code>{room_id}</code>:{filters_list}"})
else:
return send_status(context, 404, language, f"No filters found for the room <code>{room_id}</code>.", summary=False)
case "create":
...
# TODO error message on name constraint violation
# TODO filter name validation (no spaces or special symbols, no only numbers)
if filter_id and (len(Filter.select().where((Filter.owner == room_id) & (Filter.name == filter_id)).tuples()) > 0):
return
else:
filter_id = Filter.create(name=filter_id, owner=room_id)
return send_status(context, 201, language, f"Filter with id <code>{filter_id}</code> in room <code>{room_id}</code> created successfully.", summary=False)
case "delete":
#try:
Filter.delete().where((Filter.owner == room_id) & ((Filter.id == filter_id) | (Filter.name == filter_id))).execute()
return send_status(context, 200, language)
#return send_status(context, 200, language, f"Filter <code>{filter_id}</code> for room <code>{room_id}</code> deleted successfully.", summary=False)
#except Exception:
# ... # TODO error and success message, actually check for if the item to delete existed
case "insert": # TODO
#output = Filter.select().where((Filter.owner == room_id) & ((Filter.id == filter_id) | (Filter.name == filter_id))).namedtuples().get().output
Filter.update(output=data.quoted).where((Filter.owner == room_id) & ((Filter.id == filter_id) | (Filter.name == filter_id))).execute()
case "remove":
...
case "handle":
...
case "ignore":
...
register_module(name="Filters", endpoints=[
SafeNamespace(names=["filters"], handler=cFilters, quoted=False, arguments={
"action": True,
}),
])

3
ModWinDog/Filters/Filters.yaml Executable file
View File

@ -0,0 +1,3 @@
summary:
en: Tools for triggering actions on received messages.

View File

@ -17,7 +17,7 @@ def cGpt(context:EventContext, data:InputMessageData):
output += (completion.choices[0].delta.content or "") output += (completion.choices[0].delta.content or "")
return send_message(context, {"text_plain": f"[🤖️ GPT]\n\n{output}"}) return send_message(context, {"text_plain": f"[🤖️ GPT]\n\n{output}"})
RegisterModule(name="GPT", endpoints=[ register_module(name="GPT", endpoints=[
SafeNamespace(names=["gpt", "chatgpt"], handler=cGpt, body=True), SafeNamespace(names=["gpt", "chatgpt"], handler=cGpt, body=True),
]) ])

View File

@ -13,7 +13,7 @@ def cHash(context:EventContext, data:InputMessageData):
return send_message(context, { return send_message(context, {
"text_html": f"<pre>{html_escape(hashlib.new(algorithm, text_input.encode()).hexdigest())}</pre>"}) "text_html": f"<pre>{html_escape(hashlib.new(algorithm, text_input.encode()).hexdigest())}</pre>"})
RegisterModule(name="Hashing", group="Geek", endpoints=[ register_module(name="Hashing", group="Geek", endpoints=[
SafeNamespace(names=["hash"], handler=cHash, body=False, quoted=False, arguments={ SafeNamespace(names=["hash"], handler=cHash, body=False, quoted=False, arguments={
"algorithm": True, "algorithm": True,
}, help_extra=(lambda endpoint, lang: f'{endpoint.get_string("algorithms", lang)}: <code>{"</code>, <code>".join(hashlib.algorithms_available)}</code>.')), }, help_extra=(lambda endpoint, lang: f'{endpoint.get_string("algorithms", lang)}: <code>{"</code>, <code>".join(hashlib.algorithms_available)}</code>.')),

View File

@ -25,7 +25,7 @@ def cHelp(context:EventContext, data:InputMessageData) -> None:
text = text.strip() text = text.strip()
return send_message(context, {"text_html": text}) return send_message(context, {"text_html": text})
RegisterModule(name="Help", group="Basic", endpoints=[ register_module(name="Help", group="Basic", endpoints=[
SafeNamespace(names=["help"], handler=cHelp, arguments={ SafeNamespace(names=["help"], handler=cHelp, arguments={
"endpoint": False, "endpoint": False,
}), }),

View File

@ -142,7 +142,7 @@ def cSafebooru(context:EventContext, data:InputMessageData):
except Exception: except Exception:
return send_status_error(context, language) return send_status_error(context, language)
RegisterModule(name="Internet", endpoints=[ register_module(name="Internet", endpoints=[
SafeNamespace(names=["embedded"], handler=cEmbedded, body=False, quoted=False), SafeNamespace(names=["embedded"], handler=cEmbedded, body=False, quoted=False),
SafeNamespace(names=["web"], handler=cWeb, body=True), SafeNamespace(names=["web"], handler=cWeb, body=True),
SafeNamespace(names=["translate"], handler=cTranslate, body=False, quoted=False, arguments={ SafeNamespace(names=["translate"], handler=cTranslate, body=False, quoted=False, arguments={

View File

@ -21,7 +21,7 @@ def mMultifun(context:EventContext, data:InputMessageData):
text = choice(fun_strings["empty"]) text = choice(fun_strings["empty"])
return send_message(context, {"text_html": text, "ReplyTo": reply_to}) return send_message(context, {"text_html": text, "ReplyTo": reply_to})
RegisterModule(name="Multifun", endpoints=[ register_module(name="Multifun", endpoints=[
SafeNamespace(names=["hug", "pat", "poke", "cuddle", "hands", "floor", "sessocto"], handler=mMultifun), SafeNamespace(names=["hug", "pat", "poke", "cuddle", "hands", "floor", "sessocto"], handler=mMultifun),
]) ])

View File

@ -15,7 +15,7 @@ def mPercenter(context:EventContext, data:InputMessageData):
) or context.endpoint.get_help_text(data.user.settings.language) ) or context.endpoint.get_help_text(data.user.settings.language)
).format(RandomPercentString(), data.command.body)}) ).format(RandomPercentString(), data.command.body)})
RegisterModule(name="Percenter", endpoints=[ register_module(name="Percenter", endpoints=[
SafeNamespace(names=["wish", "level"], handler=mPercenter, body=True), SafeNamespace(names=["wish", "level"], handler=mPercenter, body=True),
]) ])

View File

@ -123,7 +123,7 @@ def cCraiyonSelenium(context:EventContext, data:InputMessageData):
closeSelenium(driver_index, driver) closeSelenium(driver_index, driver)
return result return result
RegisterModule(name="Scrapers", endpoints=[ register_module(name="Scrapers", endpoints=[
SafeNamespace(names=["dalle"], handler=cDalleSelenium, body=True), SafeNamespace(names=["dalle"], handler=cDalleSelenium, body=True),
SafeNamespace(names=["craiyon", "crayion"], handler=cCraiyonSelenium, body=True), SafeNamespace(names=["craiyon", "crayion"], handler=cCraiyonSelenium, body=True),
]) ])

View File

@ -50,7 +50,7 @@ end)()"""))})
except (LuaError, LuaSyntaxError): except (LuaError, LuaSyntaxError):
return send_status_error(context, data.user.settings.language) return send_status_error(context, data.user.settings.language)
RegisterModule(name="Scripting", group="Geek", endpoints=[ register_module(name="Scripting", group="Geek", endpoints=[
SafeNamespace(names=["lua"], handler=cLua, body=False, quoted=False), SafeNamespace(names=["lua"], handler=cLua, body=False, quoted=False),
]) ])

View File

@ -8,7 +8,7 @@ def cStart(context:EventContext, data:InputMessageData):
text_html=context.endpoint.get_string( text_html=context.endpoint.get_string(
"start", data.user.settings.language).format(data.user.name))) "start", data.user.settings.language).format(data.user.name)))
RegisterModule(name="Start", endpoints=[ register_module(name="Start", endpoints=[
SafeNamespace(names=["start"], handler=cStart), SafeNamespace(names=["start"], handler=cStart),
]) ])

View File

@ -28,12 +28,12 @@ def cExec(context:EventContext, data:InputMessageData):
return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'}) return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'})
def cRestart(context:EventContext, data:InputMessageData): def cRestart(context:EventContext, data:InputMessageData):
if (data.user.id not in AdminIds) and (data.user.tag not in AdminIds): if not check_bot_admin(data.user):
return send_status(context, 403, data.user.settings.language) return send_status(context, 403, data.user.settings.language)
open("./.WinDog.Restart.lock", 'w').close() open("./.WinDog.Restart.lock", 'w').close()
return send_message(context, {"text_plain": "Bot restart queued."}) return send_message(context, {"text_plain": "Bot restart queued."})
RegisterModule(name="System", endpoints=[ register_module(name="System", endpoints=[
SafeNamespace(names=["exec"], handler=cExec, body=True), SafeNamespace(names=["exec"], handler=cExec, body=True),
SafeNamespace(names=["restart"], handler=cRestart), SafeNamespace(names=["restart"], handler=cRestart),
]) ])

View File

@ -11,6 +11,7 @@ The officially-hosted instances of this bot are, respectively:
* [@WinDogBot](https://t.me/WinDogBot) on Telegram * [@WinDogBot](https://t.me/WinDogBot) on Telegram
* [@windog:matrix.org](https://matrix.to/#/@windog:matrix.org) on Matrix * [@windog:matrix.org](https://matrix.to/#/@windog:matrix.org) on Matrix
* [@WinDog@botsin.space](https://botsin.space/@WinDog) on Mastodon (can also be used from any other Fediverse platform) * [@WinDog@botsin.space](https://botsin.space/@WinDog) on Mastodon (can also be used from any other Fediverse platform)
* [WinDog.octt.eu.org](https://windog.octt.eu.org) as a web chat
In case you want to run your own instance: In case you want to run your own instance:

View File

@ -127,6 +127,22 @@ 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 parse_command_arguments(command, endpoint, count:int=None):
arguments = SafeNamespace()
body = command.body
index = 1
for key in (endpoint.arguments or range(count)):
if (not count) and (endpoint.body != None) and (endpoint.arguments[key] == False):
continue # skip optional (False) arguments for now if command expects a body, they will be implemented later
try:
value = command.tokens[index]
body = body[len(value):].strip()
except IndexError:
value = None
arguments[str(key)] = value
index += 1
return [arguments, body]
def TextCommandData(text:str, platform:str) -> CommandData|None: def TextCommandData(text:str, platform:str) -> CommandData|None:
if not text: if not text:
return None return None
@ -145,27 +161,17 @@ def TextCommandData(text:str, platform:str) -> CommandData|None:
command.body = text[len(command.tokens[0]):].strip() command.body = text[len(command.tokens[0]):].strip()
if not (endpoint := obj_get(Endpoints, command.name)): if not (endpoint := obj_get(Endpoints, command.name)):
return command # TODO shouldn't this return None? return command # TODO shouldn't this return None?
if (endpoint.arguments): command.parse_arguments = (lambda count=None: parse_command_arguments(command, endpoint, count))
command.arguments = SafeNamespace() if endpoint.arguments:
index = 1 [command.arguments, command.body] = command.parse_arguments()
for key in endpoint.arguments:
if (endpoint.body != None) and (endpoint.arguments[key] == False):
continue # skip optional (False) arguments for now if command expects a body, they will be implemented later
try:
value = command.tokens[index]
command.body = command.body[len(value):].strip()
except IndexError:
value = None
command.arguments[key] = value
index += 1
return command return command
def OnInputMessageParsed(data:InputMessageData) -> None: def on_input_message_parsed(data:InputMessageData) -> None:
dump_message(data, prefix='> ') dump_message(data, prefix='> ')
handle_bridging(send_message, data, from_sent=False) handle_bridging(send_message, data, from_sent=False)
update_user_db(data.user) update_user_db(data.user)
def OnOutputMessageSent(output_data:OutputMessageData, input_data:InputMessageData, from_sent:bool) -> None: def on_output_message_sent(output_data:OutputMessageData, input_data:InputMessageData, from_sent:bool) -> None:
if (not from_sent) and input_data: if (not from_sent) and input_data:
output_data = ObjectUnion(output_data, {"room": input_data.room}) output_data = ObjectUnion(output_data, {"room": input_data.room})
dump_message(output_data, prefix=f'<{"*" if from_sent else " "}') dump_message(output_data, prefix=f'<{"*" if from_sent else " "}')
@ -189,6 +195,14 @@ def handle_bridging(method:callable, data:MessageData, from_sent:bool):
ObjectUnion(data, {"room": SafeNamespace(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=True) from_sent=True)
def check_bot_admin(data:InputMessageData|UserData) -> bool:
user = (data.user or data)
return ((user.id in AdminIds) or (user.tag in AdminIds))
# TODO make this real
def check_room_admin(data:InputMessageData|UserData) -> bool:
return check_bot_admin(data)
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):
return return
@ -255,23 +269,23 @@ def send_message(context:EventContext, data:OutputMessageData, *, from_sent:bool
if (not context.manager) and (manager := platform.manager_class): if (not context.manager) and (manager := platform.manager_class):
context.manager = call_or_return(manager) context.manager = call_or_return(manager)
result = platform.sender(context, data) result = platform.sender(context, data)
OnOutputMessageSent(data, context.data, from_sent) on_output_message_sent(data, context.data, from_sent)
return result return result
def send_notice(context:EventContext, data): def send_notice(context:EventContext, data):
pass ...
def edit_message(context:EventContext, data:MessageData): def edit_message(context:EventContext, data:MessageData):
pass ...
def delete_message(context:EventContext, data:MessageData): def delete_message(context:EventContext, data:MessageData):
pass ...
def RegisterPlatform(name:str, main:callable, sender:callable, linker:callable=None, *, event_class=None, manager_class=None, agent_info=None) -> None: def register_platform(name:str, main:callable, sender:callable, linker:callable=None, *, event_class=None, manager_class=None, agent_info=None) -> None:
Platforms[name.lower()] = SafeNamespace(name=name, main=main, sender=sender, linker=linker, event_class=event_class, manager_class=manager_class, agent_info=agent_info) Platforms[name.lower()] = SafeNamespace(name=name, main=main, sender=sender, linker=linker, event_class=event_class, manager_class=manager_class, agent_info=agent_info)
app_log(f"{name}, ", inline=True) app_log(f"{name}, ", inline=True)
def RegisterModule(name:str, endpoints:dict, *, group:str|None=None) -> None: def register_module(name:str, endpoints:dict, *, group:str|None=None) -> None:
module = SafeNamespace(group=group, endpoints=endpoints, get_string=(lambda query, lang=None: None)) module = SafeNamespace(group=group, endpoints=endpoints, get_string=(lambda query, lang=None: None))
if isfile(file := f"./ModWinDog/{name}/{name}.yaml"): if isfile(file := f"./ModWinDog/{name}/{name}.yaml"):
module.strings = good_yaml_load(open(file, 'r').read()) module.strings = good_yaml_load(open(file, 'r').read())

View File

@ -17,7 +17,25 @@ statuses:
102: 102:
title: title:
en: Processing en: Processing
summary:
en: Your request has been accepted and is currently being processed.
it: La tua richiesta è stata accolta e sta venendo processata.
icon: ⚙️ icon: ⚙️
200:
title:
en: OK
summary:
en: Your request has completed successfully.
it: La tua richiesta è terminata con successo.
icon: 👌️
201:
title:
en: Created
it: Creato
summary:
en: The specified resource has been successfully created.
it: La risorsa specificata è stata creata con successo.
icon: 🪙️
403: 403:
title: title:
en: Forbidden en: Forbidden