mirror of
https://gitlab.com/octospacc/WinDog.git
synced 2025-06-05 22:09:20 +02:00
Legacy removals, code restructuring, add send_... functions and better help
This commit is contained in:
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,8 +1,5 @@
|
|||||||
/Config.py
|
/Data/
|
||||||
/Database.sqlite
|
|
||||||
/Dump.txt
|
|
||||||
/Log.txt
|
|
||||||
/Selenium-WinDog/
|
|
||||||
/downloaded_files/
|
/downloaded_files/
|
||||||
/session.txt
|
|
||||||
*.pyc
|
*.pyc
|
||||||
|
*.lock
|
||||||
|
/RunWinDog.py
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
# If you have modified the bot's code, you should set this.
|
# If you have modified the bot's code, you should set this.
|
||||||
ModifiedSourceUrl = ""
|
ModifiedSourceUrl = ""
|
||||||
|
|
||||||
# Logging of system information and runtime errors. Recommended to be on at least for console.
|
# Logging of system information and runtime errors. Recommended to be on in some way to diagnose errors.
|
||||||
LogToConsole = True
|
LogToConsole = True
|
||||||
LogToFile = True
|
LogToFile = True
|
||||||
|
|
||||||
@ -21,14 +21,8 @@ BridgesConfig = []
|
|||||||
|
|
||||||
DefaultLanguage = "en"
|
DefaultLanguage = "en"
|
||||||
Debug = False
|
Debug = False
|
||||||
CmdPrefixes = ".!/"
|
CommandPrefixes = ".!/"
|
||||||
WebUserAgent = "WinDog v.Staging"
|
WebUserAgent = "WinDog v.Staging"
|
||||||
|
|
||||||
#ModuleGroups = (ModuleGroups | {
|
|
||||||
ModuleGroups = {
|
|
||||||
"Basic": "",
|
|
||||||
"Geek": "",
|
|
||||||
}
|
|
||||||
|
|
||||||
# Only for the platforms you want to use, uncomment the below credentials and fill with your own:
|
# Only for the platforms you want to use, uncomment the below credentials and fill with your own:
|
||||||
""" # end windog config # """
|
""" # end windog config # """
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
from peewee import *
|
from peewee import *
|
||||||
from LibWinDog.Types import *
|
from LibWinDog.Types import *
|
||||||
|
|
||||||
Db = SqliteDatabase("Database.sqlite")
|
Db = SqliteDatabase("./Data/Database.sqlite")
|
||||||
|
|
||||||
class BaseModel(Model):
|
class BaseModel(Model):
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -28,3 +28,10 @@ class Room(Entity):
|
|||||||
|
|
||||||
Db.create_tables([EntitySettings, User, Room], safe=True)
|
Db.create_tables([EntitySettings, User, Room], safe=True)
|
||||||
|
|
||||||
|
class UserSettingsData():
|
||||||
|
def __new__(cls, user_id:str) -> SafeNamespace|None:
|
||||||
|
try:
|
||||||
|
return SafeNamespace(**EntitySettings.select().join(User).where(User.id == user_id).dicts().get())
|
||||||
|
except EntitySettings.DoesNotExist:
|
||||||
|
return None
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start #
|
""" # windog config start #
|
||||||
|
|
||||||
@ -35,20 +35,19 @@ 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), "mastodon")
|
data.command = TextCommandData(" ".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"],
|
||||||
)
|
)
|
||||||
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
data.user.settings = (UserSettingsData(data.user.id) or SafeNamespace())
|
||||||
return data
|
return data
|
||||||
|
|
||||||
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)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
call_endpoint(EventContext(platform="mastodon", event=event, manager=Mastodon), data)
|
||||||
CallEndpoint(command, EventContext(platform="mastodon", event=event, manager=Mastodon), data)
|
|
||||||
|
|
||||||
def MastodonSender(context:EventContext, data:OutputMessageData) -> None:
|
def MastodonSender(context:EventContext, data:OutputMessageData) -> None:
|
||||||
media_results = None
|
media_results = None
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start #
|
""" # windog config start #
|
||||||
|
|
||||||
@ -41,9 +41,9 @@ def MatrixMain() -> bool:
|
|||||||
global MatrixClient
|
global MatrixClient
|
||||||
MatrixClient = nio.AsyncClient(MatrixUrl, MatrixUsername)
|
MatrixClient = nio.AsyncClient(MatrixUrl, MatrixUsername)
|
||||||
login = await MatrixClient.login(password=MatrixPassword, token=MatrixToken)
|
login = await MatrixClient.login(password=MatrixPassword, token=MatrixToken)
|
||||||
if MatrixPassword and (not MatrixToken) and (token := ObjGet(login, "access_token")):
|
if MatrixPassword and (not MatrixToken) and (token := obj_get(login, "access_token")):
|
||||||
open("./Config.py", 'a').write(f'\n# Added automatically #\nMatrixToken = "{token}"\n')
|
open("./Config.py", 'a').write(f'\n# Added automatically #\nMatrixToken = "{token}"\n')
|
||||||
if (bot_id := ObjGet(login, "user_id")):
|
if (bot_id := obj_get(login, "user_id")):
|
||||||
upgrade_username(bot_id) # ensure username is fully qualified for the API
|
upgrade_username(bot_id) # ensure username is fully qualified for the API
|
||||||
await MatrixClient.sync(30000) # resync old messages first to "skip read ones"
|
await MatrixClient.sync(30000) # resync old messages first to "skip read ones"
|
||||||
asyncio.ensure_future(queue_handler())
|
asyncio.ensure_future(queue_handler())
|
||||||
@ -58,8 +58,8 @@ def MatrixMakeInputMessageData(room:nio.MatrixRoom, event:nio.RoomMessage) -> In
|
|||||||
message_id = f"matrix:{event.event_id}",
|
message_id = f"matrix:{event.event_id}",
|
||||||
datetime = event.server_timestamp,
|
datetime = event.server_timestamp,
|
||||||
text_plain = event.body,
|
text_plain = event.body,
|
||||||
text_html = ObjGet(event, "formatted_body"), # this could be unavailable
|
text_html = obj_get(event, "formatted_body"), # this could be unavailable
|
||||||
media = ({"url": event.url} if ObjGet(event, "url") else None),
|
media = ({"url": event.url} if obj_get(event, "url") else None),
|
||||||
room = SafeNamespace(
|
room = SafeNamespace(
|
||||||
id = f"matrix:{room.room_id}",
|
id = f"matrix:{room.room_id}",
|
||||||
name = room.display_name,
|
name = room.display_name,
|
||||||
@ -69,11 +69,11 @@ def MatrixMakeInputMessageData(room:nio.MatrixRoom, event:nio.RoomMessage) -> In
|
|||||||
#name = , # TODO name must be get via a separate API request (and so maybe we should cache it)
|
#name = , # TODO name must be get via a separate API request (and so maybe we should cache it)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if (mxc_url := ObjGet(data, "media.url")) and mxc_url.startswith("mxc://"):
|
if (mxc_url := obj_get(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, "matrix")
|
data.command = TextCommandData(data.text_plain, "matrix")
|
||||||
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
data.user.settings = (UserSettingsData(data.user.id) or SafeNamespace())
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def MatrixInviteHandler(room:nio.MatrixRoom, event:nio.InviteEvent) -> None:
|
async def MatrixInviteHandler(room:nio.MatrixRoom, event:nio.InviteEvent) -> None:
|
||||||
@ -84,8 +84,7 @@ async def MatrixMessageHandler(room:nio.MatrixRoom, event:nio.RoomMessage) -> No
|
|||||||
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)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
call_endpoint(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)
|
|
||||||
|
|
||||||
def MatrixSender(context:EventContext, data:OutputMessageData):
|
def MatrixSender(context:EventContext, data:OutputMessageData):
|
||||||
try:
|
try:
|
||||||
@ -94,7 +93,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 and data.room.id) or ObjGet(context, "event.room.room_id")),
|
room_id=((data.room and data.room.id) or obj_get(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}))
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start #
|
""" # windog config start #
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ 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, "telegram")
|
data.command = TextCommandData(data.text_plain, "telegram")
|
||||||
data.user.settings = (GetUserSettings(data.user.id) or SafeNamespace())
|
data.user.settings = (UserSettingsData(data.user.id) or SafeNamespace())
|
||||||
linked = TelegramLinker(data)
|
linked = TelegramLinker(data)
|
||||||
data.message_url = linked.message
|
data.message_url = linked.message
|
||||||
data.room.url = linked.room
|
data.room.url = linked.room
|
||||||
@ -69,8 +69,7 @@ def TelegramHandler(update:telegram.Update, context:CallbackContext=None) -> Non
|
|||||||
if (quoted := update.message.reply_to_message):
|
if (quoted := update.message.reply_to_message):
|
||||||
data.quoted = TelegramMakeInputMessageData(quoted)
|
data.quoted = TelegramMakeInputMessageData(quoted)
|
||||||
OnInputMessageParsed(data)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
call_endpoint(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()
|
||||||
|
|
||||||
def TelegramSender(context:EventContext, data:OutputMessageData):
|
def TelegramSender(context:EventContext, data:OutputMessageData):
|
||||||
@ -83,7 +82,7 @@ def TelegramSender(context:EventContext, data:OutputMessageData):
|
|||||||
if data.media:
|
if data.media:
|
||||||
for medium in data.media:
|
for medium in data.media:
|
||||||
result = context.event.message.reply_photo(
|
result = context.event.message.reply_photo(
|
||||||
(ObjGet(medium, "bytes") or ObjGet(medium, "url")),
|
(obj_get(medium, "bytes") or obj_get(medium, "url")),
|
||||||
caption=(data.text_html or data.text_markdown or data.text_plain),
|
caption=(data.text_html or data.text_markdown or data.text_plain),
|
||||||
parse_mode=("HTML" if data.text_html else "MarkdownV2" if data.text_markdown else None),
|
parse_mode=("HTML" if data.text_html else "MarkdownV2" if data.text_markdown else None),
|
||||||
reply_to_message_id=replyToId)
|
reply_to_message_id=replyToId)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start # """
|
""" # windog config start # """
|
||||||
|
|
||||||
@ -60,13 +60,12 @@ class WebServerClass(BaseHTTPRequestHandler):
|
|||||||
self.end_headers()
|
self.end_headers()
|
||||||
data = WebMakeInputMessageData(text, uuid)
|
data = WebMakeInputMessageData(text, uuid)
|
||||||
OnInputMessageParsed(data)
|
OnInputMessageParsed(data)
|
||||||
if (command := ObjGet(data, "command.name")):
|
call_endpoint(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, "web"),
|
command = TextCommandData(text, "web"),
|
||||||
room = SafeNamespace(
|
room = SafeNamespace(
|
||||||
id = f"web:{uuid}",
|
id = f"web:{uuid}",
|
||||||
),
|
),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
@ -22,6 +22,9 @@ class SafeNamespace(DictNamespace):
|
|||||||
|
|
||||||
# we just use these for type hinting and clearer code:
|
# we just use these for type hinting and clearer code:
|
||||||
|
|
||||||
|
class CommandData(SafeNamespace):
|
||||||
|
pass
|
||||||
|
|
||||||
class EventContext(SafeNamespace):
|
class EventContext(SafeNamespace):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,37 +1,46 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def cSource(context:EventContext, data:InputMessageData) -> None:
|
def cSource(context:EventContext, data:InputMessageData):
|
||||||
SendMessage(context, {"text_plain": ("""\
|
send_message(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 ""))})
|
||||||
|
|
||||||
def cGdpr(context:EventContext, data:InputMessageData) -> None:
|
def cGdpr(context:EventContext, data:InputMessageData):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def cConfig(context:EventContext, data:InputMessageData) -> None:
|
UserSettingsLimits = {
|
||||||
if not (settings := GetUserSettings(data.user.id)):
|
"language": 13,
|
||||||
|
}
|
||||||
|
|
||||||
|
def cConfig(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
|
if not (settings := UserSettingsData(data.user.id)):
|
||||||
User.update(settings=EntitySettings.create()).where(User.id == data.user.id).execute()
|
User.update(settings=EntitySettings.create()).where(User.id == data.user.id).execute()
|
||||||
if (to_set := ObjGet(data, "command.arguments.set")):
|
settings = UserSettingsData(data.user.id)
|
||||||
pass # TODO set in db, but first we need to ensure data is handled safely
|
if not (key := data.command.arguments.get) or (key not in UserSettingsLimits):
|
||||||
if (to_get := ObjGet(data, "command.arguments.get")):
|
return send_status_400(context, language)
|
||||||
# TODO show a hint on possible options?
|
if (value := data.command.body):
|
||||||
return SendMessage(context, OutputMessageData(text_plain=str(ObjGet(data.user.settings, to_get))))
|
if len(value) > UserSettingsLimits[key]:
|
||||||
|
return send_status(context, 500, language)
|
||||||
|
EntitySettings.update(**{key: value}).where(EntitySettings.entity == data.user.id).execute()
|
||||||
|
settings = UserSettingsData(data.user.id)
|
||||||
|
if (key):
|
||||||
|
# TODO show a hint on possible options? and add proper text hints for results
|
||||||
|
return send_message(context, {"text_plain": str(obj_get(settings, key))})
|
||||||
# TODO show general help when no useful parameters are passed
|
# TODO show general help when no useful parameters are passed
|
||||||
#Cmd = TelegramHandleCmd(update)
|
|
||||||
#if not Cmd: return
|
|
||||||
# ... area: eu, us, ...
|
# ... area: eu, us, ...
|
||||||
# ... language: en, it, ...
|
# ... language: en, it, ...
|
||||||
# ... userdata: import, export, delete
|
# ... userdata: import, export, delete
|
||||||
|
|
||||||
def cPing(context:EventContext, data:InputMessageData) -> None:
|
def cPing(context:EventContext, data:InputMessageData):
|
||||||
# nice experiment, but it won't work with Telegram since time is not to milliseconds (?)
|
# nice experiment, but it won't work with Telegram since time is not to milliseconds (?)
|
||||||
#time_diff = (time_now := int(time.time())) - (time_sent := data.datetime)
|
#time_diff = (time_now := int(time.time())) - (time_sent := data.datetime)
|
||||||
#SendMessage(context, OutputMessageData(text_html=f"<b>Pong!</b>\n\n{time_sent} → {time_now} = {time_diff}"))
|
#send_message(context, OutputMessageData(text_html=f"<b>Pong!</b>\n\n{time_sent} → {time_now} = {time_diff}"))
|
||||||
SendMessage(context, OutputMessageData(text_html="<b>Pong!</b>"))
|
send_message(context, OutputMessageData(text_html="<b>Pong!</b>"))
|
||||||
|
|
||||||
#def cTime(update:Update, context:CallbackContext) -> None:
|
#def cTime(update:Update, context:CallbackContext) -> None:
|
||||||
# update.message.reply_markdown_v2(
|
# update.message.reply_markdown_v2(
|
||||||
@ -39,7 +48,7 @@ def cPing(context:EventContext, data:InputMessageData) -> None:
|
|||||||
# reply_to_message_id=update.message.message_id)
|
# reply_to_message_id=update.message.message_id)
|
||||||
|
|
||||||
#def cEval(context:EventContext, data:InputMessageData) -> None:
|
#def cEval(context:EventContext, data:InputMessageData) -> None:
|
||||||
# SendMessage(context, {"Text": choice(Locale.__('eval'))})
|
# send_message(context, {"Text": choice(Locale.__('eval'))})
|
||||||
|
|
||||||
RegisterModule(name="Base", endpoints=[
|
RegisterModule(name="Base", endpoints=[
|
||||||
SafeNamespace(names=["source"], handler=cSource),
|
SafeNamespace(names=["source"], handler=cSource),
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def cBroadcast(context:EventContext, data:InputMessageData) -> None:
|
def cBroadcast(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
if (data.user.id not in AdminIds) and (data.user.tag not in AdminIds):
|
if (data.user.id not in AdminIds) and (data.user.tag not in AdminIds):
|
||||||
return SendMessage(context, {"Text": "Permission denied."})
|
return send_status(context, 403, language)
|
||||||
destination = data.command.arguments["destination"]
|
destination = data.command.arguments.destination
|
||||||
text = data.command.body
|
text = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
if not (destination and text):
|
if not (destination and text):
|
||||||
return SendMessage(context, OutputMessageData(text_plain="Bad usage."))
|
return send_status_400(context, language)
|
||||||
SendMessage(context, {"text_plain": text, "room": SafeNamespace(id=destination)})
|
result = send_message(context, {"text_plain": text, "room": SafeNamespace(id=destination)})
|
||||||
SendMessage(context, {"text_plain": "Executed."})
|
send_message(context, {"text_plain": "Executed."})
|
||||||
|
return result
|
||||||
|
|
||||||
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,28 +1,49 @@
|
|||||||
|
# ==================================== #
|
||||||
|
# WinDog multi-purpose chatbot #
|
||||||
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
|
# ==================================== #
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
from binascii import Error as binascii_Error
|
import base256
|
||||||
|
|
||||||
|
CodingsAlgorithms = []
|
||||||
|
CodingsMethods = {
|
||||||
|
"encode:ascii85": base64.a85encode,
|
||||||
|
"decode:ascii85": base64.a85decode,
|
||||||
|
"encode:base16": base64.b16encode,
|
||||||
|
"decode:base16": base64.b16decode,
|
||||||
|
"encode:base32": base64.b32encode,
|
||||||
|
"decode:base32": base64.b32decode,
|
||||||
|
"encode:base32hex": base64.b32hexencode,
|
||||||
|
"decode:base32hex": base64.b32hexdecode,
|
||||||
|
"encode:base64": base64.b64encode,
|
||||||
|
"decode:base64": base64.b64decode,
|
||||||
|
"encode:base85": base64.b85encode,
|
||||||
|
"decode:base85": base64.b85decode,
|
||||||
|
"encode:base256": (lambda decoded: base256.encode_string(decoded.decode()).encode()),
|
||||||
|
"decode:base256": (lambda encoded: base256.decode_string(encoded.decode()).encode()),
|
||||||
|
}
|
||||||
|
for method in dict(CodingsMethods):
|
||||||
|
method2 = method.replace("ascii", 'a').replace("base", 'b')
|
||||||
|
CodingsMethods[method2] = CodingsMethods[method]
|
||||||
|
if (name := method.split(':')[1]) not in CodingsAlgorithms:
|
||||||
|
CodingsAlgorithms.append(name)
|
||||||
|
|
||||||
def mCodings(context:EventContext, data:InputMessageData):
|
def mCodings(context:EventContext, data:InputMessageData):
|
||||||
algorithms = ["base64"]
|
|
||||||
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
|
language = data.user.settings.language
|
||||||
SendMessage(context, {
|
method = obj_get(CodingsMethods, f"{data.command.name}:{data.command.arguments.algorithm}")
|
||||||
"text_html": f'{context.endpoint.help_text(language)}\n\n{context.module.get_string("algorithms", language)}: {algorithms}'})
|
text = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
|
if not (method and text):
|
||||||
|
return send_status_400(context, language)
|
||||||
|
try:
|
||||||
|
return send_message(context, {
|
||||||
|
"text_html": f"<pre>{html_escape(method(text.encode()).decode())}</pre>"})
|
||||||
|
except Exception:
|
||||||
|
return send_status_error(context, language)
|
||||||
|
|
||||||
RegisterModule(name="Codings", group="Geek", endpoints=[
|
RegisterModule(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>.')),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
0
ModWinDog/Codings/Codings.yaml
Normal file → Executable file
0
ModWinDog/Codings/Codings.yaml
Normal file → Executable file
1
ModWinDog/Codings/requirements.txt
Executable file
1
ModWinDog/Codings/requirements.txt
Executable file
@ -0,0 +1 @@
|
|||||||
|
base256
|
@ -1,16 +1,16 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
from json import dumps as json_dumps
|
from json import dumps as json_dumps
|
||||||
|
|
||||||
|
# TODO work with links to messages
|
||||||
def cDump(context:EventContext, data:InputMessageData):
|
def cDump(context:EventContext, data:InputMessageData):
|
||||||
if (message := data.quoted):
|
if not (message := data.quoted):
|
||||||
dump_text = json_dumps(message, default=(lambda obj: obj.__dict__), indent=" ")
|
return send_status_400(context, data.user.settings.language)
|
||||||
SendMessage(context, {
|
text = json_dumps(message, default=(lambda obj: obj.__dict__), indent=" ")
|
||||||
"text_html": (f'<pre>{html_escape(dump_text)}</pre>' if message
|
return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'})
|
||||||
else context.endpoint.help_text(data.user.settings.language))})
|
|
||||||
|
|
||||||
RegisterModule(name="Dumper", group="Geek", endpoints=[
|
RegisterModule(name="Dumper", group="Geek", endpoints=[
|
||||||
SafeNamespace(names=["dump"], handler=cDump, quoted=True),
|
SafeNamespace(names=["dump"], handler=cDump, quoted=True),
|
||||||
|
@ -3,9 +3,9 @@
|
|||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def cEcho(context:EventContext, data:InputMessageData) -> None:
|
def cEcho(context:EventContext, data:InputMessageData):
|
||||||
if not (text := ObjGet(data, "command.body")):
|
if not (text := data.command.body):
|
||||||
return SendMessage(context, {
|
return send_message(context, {
|
||||||
"text_html": context.endpoint.get_string("empty", data.user.settings.language)})
|
"text_html": context.endpoint.get_string("empty", data.user.settings.language)})
|
||||||
prefix = f'<a href="{data.message_url}">🗣️</a> '
|
prefix = f'<a href="{data.message_url}">🗣️</a> '
|
||||||
if len(data.command.tokens) == 2: # text is a single word
|
if len(data.command.tokens) == 2: # text is a single word
|
||||||
@ -18,7 +18,7 @@ def cEcho(context:EventContext, data:InputMessageData) -> None:
|
|||||||
# word is not ascii, probably an emoji (altough not necessarily)
|
# word is not ascii, probably an emoji (altough not necessarily)
|
||||||
# so just pass it as is (useful for Telegram emojis)
|
# so just pass it as is (useful for Telegram emojis)
|
||||||
prefix = ''
|
prefix = ''
|
||||||
SendMessage(context, {"text_html": (prefix + html_escape(text))})
|
return send_message(context, {"text_html": (prefix + html_escape(text))})
|
||||||
|
|
||||||
RegisterModule(name="Echo", endpoints=[
|
RegisterModule(name="Echo", endpoints=[
|
||||||
SafeNamespace(names=["echo"], handler=cEcho, body=True),
|
SafeNamespace(names=["echo"], handler=cEcho, body=True),
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
from g4f.client import Client as G4FClient
|
from g4f.client import Client as G4FClient
|
||||||
|
|
||||||
g4fClient = G4FClient()
|
g4fClient = G4FClient()
|
||||||
|
|
||||||
def cGpt(context:EventContext, data:InputMessageData) -> None:
|
def cGpt(context:EventContext, data:InputMessageData):
|
||||||
if not (prompt := data.command.body):
|
if not (prompt := data.command.body):
|
||||||
return SendMessage(context, {"text_plain": "You must type some text."})
|
return send_status_400(context, data.user.settings.language)
|
||||||
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, {"text_plain": f"[🤖️ GPT]\n\n{output}"})
|
return send_message(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),
|
||||||
|
@ -3,4 +3,6 @@ endpoints:
|
|||||||
summary:
|
summary:
|
||||||
en: >
|
en: >
|
||||||
Sends a message to GPT to get back a response. Note: conversations are not yet supported, and this is more standard GPT than ChatGPT, and in general there are many bugs!
|
Sends a message to GPT to get back a response. Note: conversations are not yet supported, and this is more standard GPT than ChatGPT, and in general there are many bugs!
|
||||||
|
body:
|
||||||
|
en: Prompt
|
||||||
|
|
||||||
|
@ -1,23 +1,21 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
def cHash(context:EventContext, data:InputMessageData):
|
def cHash(context:EventContext, data:InputMessageData):
|
||||||
text_input = (data.command.body or (data.quoted and data.quoted.text_plain))
|
text_input = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
algorithm = data.command.arguments.algorithm
|
algorithm = data.command.arguments.algorithm
|
||||||
language = data.user.settings.language
|
|
||||||
if not (text_input and (algorithm in hashlib.algorithms_available)):
|
if not (text_input and (algorithm in hashlib.algorithms_available)):
|
||||||
return SendMessage(context, {
|
return send_status_400(context, data.user.settings.language)
|
||||||
"text_html": f'{context.endpoint.help_text(language)}\n\n{context.endpoint.get_string("algorithms", language)}: {hashlib.algorithms_available}'})
|
return send_message(context, {
|
||||||
hashed = hashlib.new(algorithm, text_input.encode()).hexdigest()
|
"text_html": f"<pre>{html_escape(hashlib.new(algorithm, text_input.encode()).hexdigest())}</pre>"})
|
||||||
return SendMessage(context, {"text_html": f"<pre>{hashed}</pre>"})
|
|
||||||
|
|
||||||
RegisterModule(name="Hashing", group="Geek", endpoints=[
|
RegisterModule(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>.')),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -1,23 +1,32 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
# 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 '').strip()
|
|
||||||
language = data.user.settings.language
|
language = data.user.settings.language
|
||||||
for module in Modules:
|
prefix = data.command.prefix
|
||||||
|
if (endpoint := data.command.arguments.endpoint):
|
||||||
|
if endpoint[0] in CommandPrefixes:
|
||||||
|
endpoint = endpoint[1:]
|
||||||
|
if endpoint in Endpoints:
|
||||||
|
return send_message(context, {"text_html": get_help_text(endpoint, language, prefix)})
|
||||||
|
text = (context.endpoint.get_string(lang=data.user.settings.language) or '').strip()
|
||||||
|
for group in ModuleGroups:
|
||||||
|
text += f"\n\n[ {group} ]"
|
||||||
|
for module in ModuleGroups[group]:
|
||||||
summary = Modules[module].get_string("summary", language)
|
summary = Modules[module].get_string("summary", language)
|
||||||
endpoints = Modules[module].endpoints
|
endpoints = Modules[module].endpoints
|
||||||
text += (f"\n\n{module}" + (f": {summary}" if summary else ''))
|
text += (f"\n\n{module}" + (f": {summary}" if summary else ''))
|
||||||
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* {prefix}{', {prefix}'.join(endpoint.names)}" + (f": {summary}" if summary else ''))
|
||||||
text = text.strip()
|
text = text.strip()
|
||||||
SendMessage(context, {"text_html": text})
|
return send_message(context, {"text_html": text})
|
||||||
|
|
||||||
RegisterModule(name="Help", group="Basic", endpoints=[
|
RegisterModule(name="Help", group="Basic", endpoints=[
|
||||||
SafeNamespace(names=["help"], handler=cHelp),
|
SafeNamespace(names=["help"], handler=cHelp, arguments={
|
||||||
|
"endpoint": False,
|
||||||
|
}),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start # """
|
""" # windog config start # """
|
||||||
|
|
||||||
@ -12,10 +12,14 @@ MicrosoftBingSettings = {}
|
|||||||
from urlextract import URLExtract
|
from urlextract import URLExtract
|
||||||
from urllib.request import urlopen, Request
|
from urllib.request import urlopen, Request
|
||||||
|
|
||||||
|
def RandomHexString(length:int) -> str:
|
||||||
|
return ''.join([randchoice('0123456789abcdef') for i in range(length)])
|
||||||
|
|
||||||
def HttpReq(url:str, method:str|None=None, *, body:bytes=None, headers:dict[str, str]={"User-Agent": WebUserAgent}):
|
def HttpReq(url:str, method:str|None=None, *, body:bytes=None, headers:dict[str, str]={"User-Agent": WebUserAgent}):
|
||||||
return urlopen(Request(url, method=method, data=body, headers=headers))
|
return urlopen(Request(url, method=method, data=body, headers=headers))
|
||||||
|
|
||||||
def cEmbedded(context:EventContext, data:InputMessageData) -> None:
|
def cEmbedded(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
if len(data.command.tokens) >= 2:
|
if len(data.command.tokens) >= 2:
|
||||||
# Find links in command body
|
# Find links in command body
|
||||||
text = (data.text_markdown + ' ' + data.text_plain)
|
text = (data.text_markdown + ' ' + data.text_plain)
|
||||||
@ -23,9 +27,7 @@ def cEmbedded(context:EventContext, data:InputMessageData) -> None:
|
|||||||
# Find links in quoted message
|
# Find links in quoted message
|
||||||
text = ((quoted.text_markdown or '') + ' ' + (quoted.text_plain or '') + ' ' + (quoted.text_html or ''))
|
text = ((quoted.text_markdown or '') + ' ' + (quoted.text_plain or '') + ' ' + (quoted.text_html or ''))
|
||||||
else:
|
else:
|
||||||
# TODO Error message
|
return send_status_400(context, language)
|
||||||
return
|
|
||||||
pass
|
|
||||||
urls = URLExtract().find_urls(text)
|
urls = URLExtract().find_urls(text)
|
||||||
if len(urls) > 0:
|
if len(urls) > 0:
|
||||||
proto = 'https://'
|
proto = 'https://'
|
||||||
@ -47,55 +49,57 @@ def cEmbedded(context:EventContext, data:InputMessageData) -> None:
|
|||||||
elif urlDomain == "vm.tiktok.com":
|
elif urlDomain == "vm.tiktok.com":
|
||||||
urlDomain = "vm.vxtiktok.com"
|
urlDomain = "vm.vxtiktok.com"
|
||||||
url = (urlDomain + '/' + '/'.join(url.split('/')[1:]))
|
url = (urlDomain + '/' + '/'.join(url.split('/')[1:]))
|
||||||
SendMessage(context, {"text_plain": f"{{{proto}{url}}}"})
|
return send_message(context, {"text_plain": f"{{{proto}{url}}}"})
|
||||||
# else TODO error message?
|
return send_message(context, {"text_plain": "No links found."})
|
||||||
|
|
||||||
def cWeb(context:EventContext, data:InputMessageData) -> None:
|
def cWeb(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
if not (query := data.command.body):
|
if not (query := data.command.body):
|
||||||
return # TODO show message
|
return send_status_400(context, language)
|
||||||
try:
|
try:
|
||||||
QueryUrl = urlparse.quote(query)
|
query_url = urlparse.quote(query)
|
||||||
Req = HttpReq(f'https://html.duckduckgo.com/html?q={QueryUrl}')
|
request = HttpReq(f'https://html.duckduckgo.com/html?q={query_url}')
|
||||||
Caption = f'🦆🔎 "{query}": https://duckduckgo.com/?q={QueryUrl}\n\n'
|
caption = f'🦆🔎 "{query}": https://duckduckgo.com/?q={query_url}\n\n'
|
||||||
Index = 0
|
index = 0
|
||||||
for Line in Req.read().decode().replace('\t', ' ').splitlines():
|
for line in request.read().decode().replace('\t', ' ').splitlines():
|
||||||
if ' class="result__a" ' in Line and ' href="//duckduckgo.com/l/?uddg=' in Line:
|
if ' class="result__a" ' in line and ' href="//duckduckgo.com/l/?uddg=' in line:
|
||||||
Index += 1
|
index += 1
|
||||||
Link = urlparse.unquote(Line.split(' href="//duckduckgo.com/l/?uddg=')[1].split('&rut=')[0])
|
link = urlparse.unquote(line.split(' href="//duckduckgo.com/l/?uddg=')[1].split('&rut=')[0])
|
||||||
Title = Line.strip().split('</a>')[0].strip().split('</span>')[-1].strip().split('>')
|
title = line.strip().split('</a>')[0].strip().split('</span>')[-1].strip().split('>')
|
||||||
if len(Title) > 1:
|
if len(title) > 1:
|
||||||
Title = html_unescape(Title[1].strip())
|
title = html_unescape(title[1].strip())
|
||||||
Caption += f'[{Index}] {Title} : {{{Link}}}\n\n'
|
caption += f'[{index}] {title} : {{{link}}}\n\n'
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
SendMessage(context, {"TextPlain": f'{Caption}...'})
|
return send_message(context, {"text_plain": f'{caption}...'})
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
return send_status_error(context, language)
|
||||||
|
|
||||||
def cImages(context:EventContext, data:InputMessageData) -> None:
|
def cImages(context:EventContext, data:InputMessageData):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def cNews(context:EventContext, data:InputMessageData) -> None:
|
def cNews(context:EventContext, data:InputMessageData):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def cTranslate(context:EventContext, data:InputMessageData) -> None:
|
def cTranslate(context:EventContext, data:InputMessageData):
|
||||||
|
language = data.user.settings.language
|
||||||
instances = ["lingva.ml", "lingva.lunar.icu"]
|
instances = ["lingva.ml", "lingva.lunar.icu"]
|
||||||
language_to = data.command.arguments["language_to"]
|
language_to = data.command.arguments.language_to
|
||||||
text_input = (data.command.body or (data.quoted and data.quoted.text_plain))
|
text_input = (data.command.body or (data.quoted and data.quoted.text_plain))
|
||||||
if not (text_input and language_to):
|
if not (text_input and language_to):
|
||||||
return SendMessage(context, {"TextPlain": f"Usage: /translate <to language> <text>"})
|
return send_status_400(context, language)
|
||||||
try:
|
try:
|
||||||
result = json.loads(HttpReq(f'https://{randchoice(instances)}/api/v1/auto/{language_to}/{urlparse.quote(text_input)}').read())
|
result = json.loads(HttpReq(f'https://{randchoice(instances)}/api/v1/auto/{language_to}/{urlparse.quote(text_input)}').read())
|
||||||
SendMessage(context, {"TextPlain": f"[{result['info']['detectedSource']} (auto) -> {language_to}]\n\n{result['translation']}"})
|
return send_message(context, {"text_plain": f"[{result['info']['detectedSource']} (auto) -> {language_to}]\n\n{result['translation']}"})
|
||||||
except Exception:
|
except Exception:
|
||||||
raise
|
return send_status_error(context, language)
|
||||||
|
|
||||||
# unsplash source appears to be deprecated! <https://old.reddit.com/r/unsplash/comments/s13x4h/what_happened_to_sourceunsplashcom/l65epl8/>
|
# unsplash source appears to be deprecated! <https://old.reddit.com/r/unsplash/comments/s13x4h/what_happened_to_sourceunsplashcom/l65epl8/>
|
||||||
#def cUnsplash(context:EventContext, data:InputMessageData) -> None:
|
#def cUnsplash(context:EventContext, data:InputMessageData) -> None:
|
||||||
# try:
|
# try:
|
||||||
# Req = HttpReq(f'https://source.unsplash.com/random/?{urlparse.quote(data.command.body)}')
|
# Req = HttpReq(f'https://source.unsplash.com/random/?{urlparse.quote(data.command.body)}')
|
||||||
# ImgUrl = Req.geturl().split('?')[0]
|
# ImgUrl = Req.geturl().split('?')[0]
|
||||||
# SendMessage(context, {
|
# send_message(context, {
|
||||||
# "TextPlain": f'{{{ImgUrl}}}',
|
# "TextPlain": f'{{{ImgUrl}}}',
|
||||||
# "TextMarkdown": MarkdownCode(ImgUrl, True),
|
# "TextMarkdown": MarkdownCode(ImgUrl, True),
|
||||||
# "Media": Req.read(),
|
# "Media": Req.read(),
|
||||||
@ -103,20 +107,21 @@ def cTranslate(context:EventContext, data:InputMessageData) -> None:
|
|||||||
# except Exception:
|
# except Exception:
|
||||||
# raise
|
# raise
|
||||||
|
|
||||||
def cSafebooru(context:EventContext, data:InputMessageData) -> None:
|
def cSafebooru(context:EventContext, data:InputMessageData):
|
||||||
ApiUrl = 'https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags='
|
language = data.user.settings.language
|
||||||
|
api_url = 'https://safebooru.org/index.php?page=dapi&s=post&q=index&limit=100&tags='
|
||||||
try:
|
try:
|
||||||
img_id, img_url = None, None
|
img_id, img_url = None, None
|
||||||
if (query := data.command.body):
|
if (query := data.command.body):
|
||||||
for i in range(7): # retry a bunch of times if we can't find a really random result
|
for i in range(7): # retry a bunch of times if we can't find a really random result
|
||||||
ImgUrls = HttpReq(f'{ApiUrl}md5:{RandHexStr(3)}%20{urlparse.quote(query)}').read().decode().split(' file_url="')[1:]
|
img_urls = HttpReq(f'{api_url}md5:{RandomHexString(3)}%20{urlparse.quote(query)}').read().decode().split(' file_url="')[1:]
|
||||||
if ImgUrls:
|
if img_urls:
|
||||||
break
|
break
|
||||||
if not ImgUrls: # literal search
|
if not img_urls: # literal search
|
||||||
ImgUrls = HttpReq(f'{ApiUrl}{urlparse.quote(query)}').read().decode().split(' file_url="')[1:]
|
img_urls = HttpReq(f'{api_url}{urlparse.quote(query)}').read().decode().split(' file_url="')[1:]
|
||||||
if not ImgUrls:
|
if not img_urls:
|
||||||
return SendMessage(context, {"Text": "Error: Could not get any result from Safebooru."})
|
return send_status(context, 404, language, "Could not get any result from Safebooru.", summary=False)
|
||||||
ImgXml = choice(ImgUrls)
|
ImgXml = choice(img_urls)
|
||||||
img_url = ImgXml.split('"')[0]
|
img_url = ImgXml.split('"')[0]
|
||||||
img_id = ImgXml.split(' id="')[1].split('"')[0]
|
img_id = ImgXml.split(' id="')[1].split('"')[0]
|
||||||
else:
|
else:
|
||||||
@ -127,14 +132,14 @@ def cSafebooru(context:EventContext, data:InputMessageData) -> None:
|
|||||||
img_id = img_url.split('?')[-1]
|
img_id = img_url.split('?')[-1]
|
||||||
break
|
break
|
||||||
if img_url:
|
if img_url:
|
||||||
SendMessage(context, OutputMessageData(
|
return send_message(context, OutputMessageData(
|
||||||
text_plain=f"[{img_id}]\n{{{img_url}}}",
|
text_plain=f"[{img_id}]\n{{{img_url}}}",
|
||||||
text_html=f"[<code>{img_id}</code>]\n<pre>{img_url}</pre>",
|
text_html=f"[<code>{img_id}</code>]\n<pre>{img_url}</pre>",
|
||||||
media={"url": img_url}))
|
media={"url": img_url}))
|
||||||
else:
|
else:
|
||||||
pass
|
return send_status_400(context, language)
|
||||||
except Exception as error:
|
except Exception:
|
||||||
raise
|
return send_status_error(context, language)
|
||||||
|
|
||||||
RegisterModule(name="Internet", endpoints=[
|
RegisterModule(name="Internet", endpoints=[
|
||||||
SafeNamespace(names=["embedded"], handler=cEmbedded, body=False, quoted=False),
|
SafeNamespace(names=["embedded"], handler=cEmbedded, body=False, quoted=False),
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def mMultifun(context:EventContext, data:InputMessageData) -> None:
|
def mMultifun(context:EventContext, data:InputMessageData):
|
||||||
reply_to = None
|
reply_to = None
|
||||||
fun_strings = {}
|
fun_strings = {}
|
||||||
for key in ("empty", "bot", "self", "others"):
|
for key in ("empty", "bot", "self", "others"):
|
||||||
@ -19,7 +19,7 @@ def mMultifun(context:EventContext, data:InputMessageData) -> None:
|
|||||||
else:
|
else:
|
||||||
if fun_strings["empty"]:
|
if fun_strings["empty"]:
|
||||||
text = choice(fun_strings["empty"])
|
text = choice(fun_strings["empty"])
|
||||||
SendMessage(context, {"text_html": text, "ReplyTo": reply_to})
|
return send_message(context, {"text_html": text, "ReplyTo": reply_to})
|
||||||
|
|
||||||
RegisterModule(name="Multifun", endpoints=[
|
RegisterModule(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),
|
||||||
|
@ -3,12 +3,17 @@
|
|||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def mPercenter(context:EventContext, data:InputMessageData) -> None:
|
# NOTE: with this implementation there is a 1/100 probability (high!) of result 100.00, which is not always ideal
|
||||||
SendMessage(context, {"text_html": (context.endpoint.get_string(
|
def RandomPercentString() -> str:
|
||||||
|
num = randint(0,100)
|
||||||
|
return (f'{num}.00' if num == 100 else f'{num}.{randint(0,9)}{randint(0,9)}')
|
||||||
|
|
||||||
|
def mPercenter(context:EventContext, data:InputMessageData):
|
||||||
|
return send_message(context, {"text_html": (context.endpoint.get_string(
|
||||||
("done" if data.command.body else "empty"),
|
("done" if data.command.body else "empty"),
|
||||||
data.user.settings.language
|
data.user.settings.language
|
||||||
) or context.endpoint.help_text(data.user.settings.language)
|
) or context.endpoint.get_help_text(data.user.settings.language)
|
||||||
).format(RandPercent(), data.command.body)})
|
).format(RandomPercentString(), data.command.body)})
|
||||||
|
|
||||||
RegisterModule(name="Percenter", endpoints=[
|
RegisterModule(name="Percenter", endpoints=[
|
||||||
SafeNamespace(names=["wish", "level"], handler=mPercenter, body=True),
|
SafeNamespace(names=["wish", "level"], handler=mPercenter, body=True),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start # """
|
""" # windog config start # """
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ def getSelenium() -> tuple[int, Driver]|bool:
|
|||||||
if index not in currentSeleniumDrivers:
|
if index not in currentSeleniumDrivers:
|
||||||
currentSeleniumDrivers.append(index)
|
currentSeleniumDrivers.append(index)
|
||||||
break
|
break
|
||||||
return (index, Driver(uc=True, headless2=True, user_data_dir=f"./Selenium-WinDog/{index}"))
|
return (index, Driver(uc=True, headless2=True, user_data_dir=f"./Data/Selenium/{index}"))
|
||||||
|
|
||||||
def closeSelenium(index:int, driver:Driver) -> None:
|
def closeSelenium(index:int, driver:Driver) -> None:
|
||||||
if driver:
|
if driver:
|
||||||
@ -30,19 +30,19 @@ def closeSelenium(index:int, driver:Driver) -> None:
|
|||||||
driver.close()
|
driver.close()
|
||||||
driver.quit()
|
driver.quit()
|
||||||
except:
|
except:
|
||||||
Log(format_exc())
|
app_log(format_exc())
|
||||||
if index:
|
if index:
|
||||||
currentSeleniumDrivers.remove(index)
|
currentSeleniumDrivers.remove(index)
|
||||||
|
|
||||||
def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
def cDalleSelenium(context:EventContext, data:InputMessageData):
|
||||||
warning_text = "has been blocked by Microsoft because it violates their content policy. Further attempts might lead to a ban on your profile. Please review the Code of Conduct for Image Creator in this picture or at https://www.bing.com/new/termsofuseimagecreator#content-policy."
|
warning_text = "has been blocked by Microsoft because it violates their content policy. Further attempts might lead to a ban on your profile. Please review the Code of Conduct for Image Creator in this picture or at https://www.bing.com/new/termsofuseimagecreator#content-policy."
|
||||||
if not (prompt := data.command.body):
|
if not (prompt := data.command.body):
|
||||||
return SendMessage(context, {"Text": "Please tell me what to generate."})
|
return send_message(context, {"text_plain": "Please tell me what to generate."})
|
||||||
driver_index, driver = None, None
|
driver_index, driver = None, None
|
||||||
try:
|
try:
|
||||||
driver = getSelenium()
|
driver = getSelenium()
|
||||||
if not driver:
|
if not driver:
|
||||||
return SendMessage(context, {"Text": "Couldn't access a web scraping VM as they are all busy. Please try again later."})
|
return send_message(context, {"text_plain": "Couldn't access a web scraping VM as they are all busy. Please try again later."})
|
||||||
driver_index, driver = driver
|
driver_index, driver = driver
|
||||||
driver.get("https://www.bing.com/images/create/")
|
driver.get("https://www.bing.com/images/create/")
|
||||||
driver.refresh()
|
driver.refresh()
|
||||||
@ -50,11 +50,11 @@ def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
|||||||
driver.find_element('form a[role="button"]').submit()
|
driver.find_element('form a[role="button"]').submit()
|
||||||
try:
|
try:
|
||||||
driver.find_element('img.gil_err_img[alt="Content warning"]')
|
driver.find_element('img.gil_err_img[alt="Content warning"]')
|
||||||
SendMessage(context, {"Text": f"Content warning: This prompt {warning_text}", "media": {"bytes": open("./Assets/ImageCreator-CodeOfConduct.png", 'rb').read()}})
|
send_message(context, {"text_plain": f"Content warning: This prompt {warning_text}", "media": {"bytes": open("./Assets/ImageCreator-CodeOfConduct.png", 'rb').read()}})
|
||||||
return closeSelenium(driver_index, driver)
|
return closeSelenium(driver_index, driver)
|
||||||
except Exception: # warning element was not found, we should be good
|
except Exception: # warning element was not found, we should be good
|
||||||
pass
|
pass
|
||||||
SendMessage(context, {"Text": "Request sent successfully, please wait..."})
|
send_message(context, {"text_plain": "Request sent successfully, please wait..."})
|
||||||
retry_index = 3
|
retry_index = 3
|
||||||
while retry_index < 12:
|
while retry_index < 12:
|
||||||
# note that sometimes generation can still fail and we will never get any image!
|
# note that sometimes generation can still fail and we will never get any image!
|
||||||
@ -64,8 +64,9 @@ def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
|||||||
if not len(img_list):
|
if not len(img_list):
|
||||||
try:
|
try:
|
||||||
driver.find_element('img.gil_err_img[alt="Unsafe image content detected"]')
|
driver.find_element('img.gil_err_img[alt="Unsafe image content detected"]')
|
||||||
SendMessage(context, {"Text": f"Unsafe image content detected: This result {warning_text}", "media": {"bytes": open("./Assets/ImageCreator-CodeOfConduct.png", 'rb').read()}})
|
result = send_message(context, {"text_plain": f"Unsafe image content detected: This result {warning_text}", "media": {"bytes": open("./Assets/ImageCreator-CodeOfConduct.png", 'rb').read()}})
|
||||||
return closeSelenium(driver_index, driver)
|
closeSelenium(driver_index, driver)
|
||||||
|
return result
|
||||||
except: # no error is present, so we just have to wait more for the images
|
except: # no error is present, so we just have to wait more for the images
|
||||||
continue
|
continue
|
||||||
img_array = []
|
img_array = []
|
||||||
@ -73,30 +74,32 @@ def cDalleSelenium(context:EventContext, data:InputMessageData) -> None:
|
|||||||
img_url = img_url.get_attribute("src").split('?')[0]
|
img_url = img_url.get_attribute("src").split('?')[0]
|
||||||
img_array.append({"url": img_url}) #, "bytes": HttpReq(img_url).read()})
|
img_array.append({"url": img_url}) #, "bytes": HttpReq(img_url).read()})
|
||||||
page_url = driver.current_url.split('?')[0]
|
page_url = driver.current_url.split('?')[0]
|
||||||
SendMessage(context, OutputMessageData(
|
result = send_message(context, OutputMessageData(
|
||||||
text_plain=f'"{prompt}"\n{{{page_url}}}',
|
text_plain=f'"{prompt}"\n{{{page_url}}}',
|
||||||
text_html=f'"<i>{html_escape(prompt)}</i>"\n<pre>{page_url}</pre>',
|
text_html=f'"<i>{html_escape(prompt)}</i>"\n<pre>{page_url}</pre>',
|
||||||
media=img_array))
|
media=img_array))
|
||||||
return closeSelenium(driver_index, driver)
|
closeSelenium(driver_index, driver)
|
||||||
|
return result
|
||||||
raise Exception("VM timed out.")
|
raise Exception("VM timed out.")
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
Log(format_exc())
|
app_log(format_exc())
|
||||||
SendMessage(context, {"TextPlain": "An unexpected error occurred."})
|
result = send_message(context, {"text_plain": "An unexpected error occurred."})
|
||||||
closeSelenium(driver_index, driver)
|
closeSelenium(driver_index, driver)
|
||||||
|
return result
|
||||||
|
|
||||||
def cCraiyonSelenium(context:EventContext, data:InputMessageData) -> None:
|
def cCraiyonSelenium(context:EventContext, data:InputMessageData):
|
||||||
if not (prompt := data.command.body):
|
if not (prompt := data.command.body):
|
||||||
return SendMessage(context, {"Text": "Please tell me what to generate."})
|
return send_message(context, {"text_plain": "Please tell me what to generate."})
|
||||||
driver_index, driver = None, None
|
driver_index, driver = None, None
|
||||||
try:
|
try:
|
||||||
driver = getSelenium()
|
driver = getSelenium()
|
||||||
if not driver:
|
if not driver:
|
||||||
return SendMessage(context, {"Text": "Couldn't access a web scraping VM as they are all busy. Please try again later."})
|
return send_message(context, {"text_plain": "Couldn't access a web scraping VM as they are all busy. Please try again later."})
|
||||||
driver_index, driver = driver
|
driver_index, driver = driver
|
||||||
driver.get("https://www.craiyon.com/")
|
driver.get("https://www.craiyon.com/")
|
||||||
driver.find_element('textarea#prompt').send_keys(prompt)
|
driver.find_element('textarea#prompt').send_keys(prompt)
|
||||||
driver.execute_script("arguments[0].click();", driver.find_element('button#generateButton'))
|
driver.execute_script("arguments[0].click();", driver.find_element('button#generateButton'))
|
||||||
SendMessage(context, {"Text": "Request sent successfully, please wait up to 60 seconds..."})
|
send_message(context, {"text_plain": "Request sent successfully, please wait up to 60 seconds..."})
|
||||||
retry_index = 3
|
retry_index = 3
|
||||||
while retry_index < 16:
|
while retry_index < 16:
|
||||||
time.sleep(retry_index := retry_index + 1)
|
time.sleep(retry_index := retry_index + 1)
|
||||||
@ -106,17 +109,19 @@ def cCraiyonSelenium(context:EventContext, data:InputMessageData) -> None:
|
|||||||
img_array = []
|
img_array = []
|
||||||
for img_elem in img_list:
|
for img_elem in img_list:
|
||||||
img_array.append({"url": img_elem.get_attribute("src")}) #, "bytes": HttpReq(img_url).read()})
|
img_array.append({"url": img_elem.get_attribute("src")}) #, "bytes": HttpReq(img_url).read()})
|
||||||
SendMessage(context, {
|
result = send_message(context, {
|
||||||
"text_plain": f'"{prompt}"',
|
"text_plain": f'"{prompt}"',
|
||||||
"text_html": f'"<i>{html_escape(prompt)}</i>"',
|
"text_html": f'"<i>{html_escape(prompt)}</i>"',
|
||||||
"media": img_array,
|
"media": img_array,
|
||||||
})
|
})
|
||||||
return closeSelenium(driver_index, driver)
|
closeSelenium(driver_index, driver)
|
||||||
|
return result
|
||||||
raise Exception("VM timed out.")
|
raise Exception("VM timed out.")
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
Log(format_exc())
|
app_log(format_exc())
|
||||||
SendMessage(context, {"TextPlain": "An unexpected error occurred."})
|
result = send_message(context, {"text_plain": "An unexpected error occurred."})
|
||||||
closeSelenium(driver_index, driver)
|
closeSelenium(driver_index, driver)
|
||||||
|
return result
|
||||||
|
|
||||||
RegisterModule(name="Scrapers", endpoints=[
|
RegisterModule(name="Scrapers", endpoints=[
|
||||||
SafeNamespace(names=["dalle"], handler=cDalleSelenium, body=True),
|
SafeNamespace(names=["dalle"], handler=cDalleSelenium, body=True),
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# ================================== #
|
# ==================================== #
|
||||||
# WinDog multi-purpose chatbot #
|
# WinDog multi-purpose chatbot #
|
||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ================================== #
|
# ==================================== #
|
||||||
|
|
||||||
""" # windog config start # """
|
""" # windog config start # """
|
||||||
|
|
||||||
@ -23,11 +23,10 @@ def luaAttributeFilter(obj, attr_name, is_setting):
|
|||||||
raise AttributeError("Access Denied.")
|
raise AttributeError("Access Denied.")
|
||||||
|
|
||||||
# TODO make print behave the same as normal Lua, and expose a function for printing without newlines
|
# TODO make print behave the same as normal Lua, and expose a function for printing without newlines
|
||||||
def cLua(context:EventContext, data:InputMessageData) -> None:
|
def cLua(context:EventContext, data:InputMessageData):
|
||||||
# TODO update quoted api getting
|
# TODO update quoted api getting
|
||||||
scriptText = (data.command.body or (data.quoted and data.quoted.text_plain))
|
if not (script_text := (data.command.body or (data.quoted and data.quoted.text_plain))):
|
||||||
if not scriptText:
|
return send_message(context, {"text_plain": "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 = "" }}
|
||||||
@ -44,13 +43,12 @@ end)()""")
|
|||||||
elif key not in LuaGlobalsWhitelist:
|
elif key not in LuaGlobalsWhitelist:
|
||||||
del luaRuntime.globals()[key]
|
del luaRuntime.globals()[key]
|
||||||
try:
|
try:
|
||||||
textOutput = ("[ʟᴜᴀ ꜱᴛᴅᴏᴜᴛ]\n\n" + luaRuntime.eval(f"""(function()
|
return send_message(context, {"text_plain": ("[ʟᴜᴀ ꜱᴛᴅᴏᴜᴛ]\n\n" + luaRuntime.eval(f"""(function()
|
||||||
_windog.scriptout = (function()\n{scriptText}\nend)()
|
_windog.scriptout = (function()\n{script_text}\nend)()
|
||||||
return _windog.stdout .. (_windog.scriptout or '')
|
return _windog.stdout .. (_windog.scriptout or '')
|
||||||
end)()"""))
|
end)()"""))})
|
||||||
except (LuaError, LuaSyntaxError) as error:
|
except (LuaError, LuaSyntaxError):
|
||||||
Log(textOutput := ("Lua Error: " + str(error)))
|
return send_status_error(context, data.user.settings.language)
|
||||||
SendMessage(context, {"TextPlain": textOutput})
|
|
||||||
|
|
||||||
RegisterModule(name="Scripting", group="Geek", endpoints=[
|
RegisterModule(name="Scripting", group="Geek", endpoints=[
|
||||||
SafeNamespace(names=["lua"], handler=cLua, body=False, quoted=False),
|
SafeNamespace(names=["lua"], handler=cLua, body=False, quoted=False),
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
# Licensed under AGPLv3 by OctoSpacc #
|
# Licensed under AGPLv3 by OctoSpacc #
|
||||||
# ==================================== #
|
# ==================================== #
|
||||||
|
|
||||||
def cStart(context:EventContext, data:InputMessageData) -> None:
|
def cStart(context:EventContext, data:InputMessageData):
|
||||||
SendMessage(context, OutputMessageData(
|
return send_message(context, OutputMessageData(
|
||||||
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)))
|
||||||
|
|
||||||
|
@ -13,19 +13,28 @@ ExecAllowed = {"date": False, "fortune": False, "neofetch": True, "uptime": Fals
|
|||||||
import subprocess
|
import subprocess
|
||||||
from re import compile as re_compile
|
from re import compile as re_compile
|
||||||
|
|
||||||
def cExec(context:EventContext, data:InputMessageData) -> None:
|
def cExec(context:EventContext, data:InputMessageData):
|
||||||
if not (len(data.command.tokens) >= 2 and data.command.tokens[1].lower() in ExecAllowed):
|
language = data.user.settings.language
|
||||||
return SendMessage(context, {"text_plain": "This feature is not implemented [Security Issue]."})
|
if not (len(data.command.tokens) >= 2):
|
||||||
|
return send_status_400(context, language)
|
||||||
|
if not data.command.tokens[1].lower() in ExecAllowed:
|
||||||
|
return send_status(context, 404, language, context.endpoint.get_string("statuses.404", language), summary=False)
|
||||||
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}"),
|
||||||
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode()
|
stdout=subprocess.PIPE, stderr=subprocess.STDOUT).stdout.decode()
|
||||||
# <https://stackoverflow.com/a/14693789>
|
# <https://stackoverflow.com/a/14693789>
|
||||||
text = (re_compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").sub('', output))
|
text = (re_compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])").sub('', output))
|
||||||
SendMessage(context, OutputMessageData(
|
return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'})
|
||||||
text_plain=text, text_html=f"<pre>{html_escape(text)}</pre>"))
|
|
||||||
|
def cRestart(context:EventContext, data:InputMessageData):
|
||||||
|
if (data.user.id not in AdminIds) and (data.user.tag not in AdminIds):
|
||||||
|
return send_message(context, {"text_plain": "Permission denied."})
|
||||||
|
open("./.WinDog.Restart.lock", 'w').close()
|
||||||
|
return send_message(context, {"text_plain": "Bot restart queued."})
|
||||||
|
|
||||||
RegisterModule(name="System", endpoints=[
|
RegisterModule(name="System", endpoints=[
|
||||||
SafeNamespace(names=["exec"], handler=cExec, body=True),
|
SafeNamespace(names=["exec"], handler=cExec, body=True),
|
||||||
|
SafeNamespace(names=["restart"], handler=cRestart),
|
||||||
])
|
])
|
||||||
|
|
||||||
|
@ -2,4 +2,7 @@ endpoints:
|
|||||||
exec:
|
exec:
|
||||||
summary:
|
summary:
|
||||||
en: Execute a system command from the allowed ones and return stdout+stderr.
|
en: Execute a system command from the allowed ones and return stdout+stderr.
|
||||||
|
statuses:
|
||||||
|
404:
|
||||||
|
en: The requested command is not available.
|
||||||
|
|
||||||
|
19
RunWinDog.sh
Executable file
19
RunWinDog.sh
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
windog_start(){
|
||||||
|
rm -f ./.WinDog.Restart.lock
|
||||||
|
python3 ./WinDog.py &
|
||||||
|
}
|
||||||
|
|
||||||
|
cd "$( dirname "$( realpath "$0" )" )"
|
||||||
|
windog_start
|
||||||
|
|
||||||
|
while true
|
||||||
|
do
|
||||||
|
if [ -f ./.WinDog.Restart.lock ]
|
||||||
|
then
|
||||||
|
kill "$!"
|
||||||
|
windog_start
|
||||||
|
fi
|
||||||
|
sleep 5
|
||||||
|
done
|
@ -1,3 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
cd "$( dirname "$( realpath "$0" )" )"
|
|
||||||
python3 ./WinDog.py
|
|
218
WinDog.py
218
WinDog.py
@ -11,6 +11,7 @@ from html import escape as html_escape, unescape as html_unescape
|
|||||||
from os import listdir
|
from os import listdir
|
||||||
from os.path import isfile, isdir
|
from os.path import isfile, isdir
|
||||||
from random import choice, choice as randchoice, randint
|
from random import choice, choice as randchoice, randint
|
||||||
|
from sys import exc_info as sys_exc_info
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from traceback import format_exc, format_exc as traceback_format_exc
|
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
|
||||||
@ -32,23 +33,35 @@ def ObjectUnion(*objects:object, clazz:object=None):
|
|||||||
dikt[key] = value
|
dikt[key] = value
|
||||||
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 ObjectClone(obj:object):
|
||||||
endline = '\n'
|
return ObjectUnion(obj, {});
|
||||||
if newline == False or (inline and newline == None):
|
|
||||||
endline = ''
|
|
||||||
text = (text if inline else f"[{level}] [{time.ctime()}] [{int(time.time())}] {text}")
|
|
||||||
if LogToConsole:
|
|
||||||
print(text, end=endline)
|
|
||||||
if LogToFile:
|
|
||||||
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 ObjGet(node:object, query:str, /) -> any:
|
def app_log(text:str=None, level:str="?", *, newline:bool|None=None, inline:bool=False) -> None:
|
||||||
|
if not text:
|
||||||
|
text = get_exception_text(full=True)
|
||||||
|
endline = '\n'
|
||||||
|
if newline == False or (inline and newline == None):
|
||||||
|
endline = ''
|
||||||
|
text = (str(text) if inline else f"[{level}] [{time.ctime()}] [{int(time.time())}] {text}")
|
||||||
|
if LogToConsole:
|
||||||
|
print(text, end=endline)
|
||||||
|
if LogToFile:
|
||||||
|
open((DumpToFile if (DumpToFile and type(DumpToFile) == str) else "./Data/Log.txt"), 'a').write(text + endline)
|
||||||
|
|
||||||
|
def get_exception_text(full:bool=False):
|
||||||
|
exc_type, exc_value, exc_traceback = sys_exc_info()
|
||||||
|
text = f'{exc_type.__qualname__}: {exc_value}'
|
||||||
|
if full:
|
||||||
|
text = f'@{exc_traceback.tb_frame.f_code.co_name}:{exc_traceback.tb_lineno} {text}'
|
||||||
|
return text
|
||||||
|
|
||||||
|
def call_or_return(obj:any, *args) -> any:
|
||||||
|
return (obj(*args) if callable(obj) else obj)
|
||||||
|
|
||||||
|
def obj_get(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__:
|
||||||
# dicts and such
|
# dicts and such
|
||||||
@ -67,19 +80,30 @@ def ObjGet(node:object, query:str, /) -> any:
|
|||||||
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)
|
||||||
|
|
||||||
def get_string(bank:dict, query:str|dict, lang:str=None) -> str|list[str]|None:
|
def get_string(bank:dict, query:str, lang:str=None) -> str|list[str]|None:
|
||||||
if not (result := ObjGet(bank, f"{query}.{lang or DefaultLanguage}")):
|
if type(result := obj_get(bank, query)) != str:
|
||||||
if not (result := ObjGet(bank, f"{query}.en")):
|
if not (result := obj_get(bank, f"{query}.{lang or DefaultLanguage}")):
|
||||||
result = ObjGet(bank, query)
|
if not (result := obj_get(bank, f"{query}.en")):
|
||||||
|
result = obj_get(bank, query)
|
||||||
|
if result:
|
||||||
|
result = result.strip()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def help_text(endpoint, lang:str=None) -> str:
|
def get_help_text(endpoint, lang:str=None, prefix:str=None) -> str:
|
||||||
|
if type(endpoint) == str:
|
||||||
|
endpoint = instanciate_endpoint(endpoint, prefix)
|
||||||
global_string = (lambda query: get_string(GlobalStrings, query, lang))
|
global_string = (lambda query: get_string(GlobalStrings, query, lang))
|
||||||
text = f'{endpoint.get_string("summary", lang) or ""}\n\n{global_string("usage")}:'
|
text = f'{endpoint.get_string("summary", lang) or ""}\n\n{global_string("usage")}: {prefix or ""}{endpoint.name}'
|
||||||
if endpoint.arguments:
|
if endpoint.arguments:
|
||||||
for argument in endpoint.arguments:
|
for argument in endpoint.arguments:
|
||||||
if endpoint.arguments[argument]:
|
if not ((endpoint.body != None) and (endpoint.arguments[argument] == False)):
|
||||||
text += f' <{endpoint.get_string(f"arguments.{argument}", lang) or endpoint.module.get_string(f"arguments.{argument}", lang) or argument}>'
|
argument_help = (endpoint.get_string(f"arguments.{argument}", lang)
|
||||||
|
or endpoint.module.get_string(f"arguments.{argument}", lang)
|
||||||
|
or argument)
|
||||||
|
if endpoint.arguments[argument] == True:
|
||||||
|
text += f' <{argument_help}>'
|
||||||
|
elif endpoint.arguments[argument] == False:
|
||||||
|
text += f' [{argument_help}]'
|
||||||
body_help = (endpoint.get_string("body", lang) or endpoint.module.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:
|
||||||
@ -95,51 +119,38 @@ def help_text(endpoint, lang:str=None) -> str:
|
|||||||
text += f' <{quoted_help}>'
|
text += f' <{quoted_help}>'
|
||||||
elif endpoint.quoted == False:
|
elif endpoint.quoted == False:
|
||||||
text += f' [{quoted_help}]'
|
text += f' [{quoted_help}]'
|
||||||
|
if (extra := call_or_return(endpoint.help_extra, endpoint, lang)):
|
||||||
|
text += f'\n\n{extra}'
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def strip_url_scheme(url:str) -> str:
|
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 RandPercent() -> int:
|
def TextCommandData(text:str, platform:str) -> CommandData|None:
|
||||||
num = randint(0,100)
|
|
||||||
return (f'{num}.00' if num == 100 else f'{num}.{randint(0,9)}{randint(0,9)}')
|
|
||||||
|
|
||||||
def RandHexStr(length:int) -> str:
|
|
||||||
hexa = ''
|
|
||||||
for char in range(length):
|
|
||||||
hexa += choice('0123456789abcdef')
|
|
||||||
return hexa
|
|
||||||
|
|
||||||
def GetUserSettings(user_id:str) -> SafeNamespace|None:
|
|
||||||
try:
|
|
||||||
return SafeNamespace(**EntitySettings.select().join(User).where(User.id == user_id).dicts().get())
|
|
||||||
except EntitySettings.DoesNotExist:
|
|
||||||
return 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()
|
||||||
try: # ensure text is a non-empty command
|
try: # ensure text is non-empty and an actual command
|
||||||
if not (text[0] in CmdPrefixes and text[1:].strip()):
|
if not (text[0] in CommandPrefixes and text[1:].strip()):
|
||||||
return None
|
return None
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return None
|
return None
|
||||||
command = SafeNamespace()
|
command = SafeNamespace()
|
||||||
command.tokens = text.split()
|
command.tokens = text.split()
|
||||||
|
command.prefix = command.tokens[0][0]
|
||||||
command.name, command_target = (command.tokens[0][1:].lower().split('@') + [''])[:2]
|
command.name, command_target = (command.tokens[0][1:].lower().split('@') + [''])[:2]
|
||||||
if command_target and not (command_target == call_or_return(Platforms[platform].agent_info).tag.lower()):
|
if command_target and not (command_target == call_or_return(Platforms[platform].agent_info).tag.lower()):
|
||||||
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 not (endpoint := obj_get(Endpoints, command.name)):
|
||||||
return command
|
return command # TODO shouldn't this return None?
|
||||||
if (endpoint_arguments := Endpoints[command.name].arguments):
|
if (endpoint.arguments):
|
||||||
command.arguments = SafeNamespace()
|
command.arguments = SafeNamespace()
|
||||||
index = 1
|
index = 1
|
||||||
for key in endpoint_arguments:
|
for key in endpoint.arguments:
|
||||||
if not endpoint_arguments[key]:
|
if (endpoint.body != None) and (endpoint.arguments[key] == False):
|
||||||
continue # skip optional (False) arguments for now, they will be implemented later
|
continue # skip optional (False) arguments for now if command expects a body, they will be implemented later
|
||||||
try:
|
try:
|
||||||
value = command.tokens[index]
|
value = command.tokens[index]
|
||||||
command.body = command.body[len(value):].strip()
|
command.body = command.body[len(value):].strip()
|
||||||
@ -151,7 +162,7 @@ def ParseCommand(text:str, platform:str) -> SafeNamespace|None:
|
|||||||
|
|
||||||
def OnInputMessageParsed(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(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 OnOutputMessageSent(output_data:OutputMessageData, input_data:InputMessageData, from_sent:bool) -> None:
|
||||||
@ -159,14 +170,13 @@ def OnOutputMessageSent(output_data:OutputMessageData, input_data:InputMessageDa
|
|||||||
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 " "}')
|
||||||
if not from_sent:
|
if not from_sent:
|
||||||
handle_bridging(SendMessage, output_data, from_sent=True)
|
handle_bridging(send_message, 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
|
|
||||||
def handle_bridging(method:callable, data:MessageData, from_sent:bool):
|
def handle_bridging(method:callable, data:MessageData, from_sent:bool):
|
||||||
if data.user:
|
if data.user:
|
||||||
if (text_plain := ObjGet(data, "text_plain")):
|
if (text_plain := obj_get(data, "text_plain")):
|
||||||
text_plain = f"<{data.user.name}>: {text_plain}"
|
text_plain = f"<{data.user.name}>: {text_plain}"
|
||||||
if (text_html := ObjGet(data, "text_html")):
|
if (text_html := obj_get(data, "text_html")):
|
||||||
text_html = (urlparse.quote(f"<{data.user.name}>: ") + text_html)
|
text_html = (urlparse.quote(f"<{data.user.name}>: ") + text_html)
|
||||||
for bridge in BridgesConfig:
|
for bridge in BridgesConfig:
|
||||||
if data.room.id not in bridge:
|
if data.room.id not in bridge:
|
||||||
@ -200,9 +210,28 @@ def dump_message(data:InputMessageData, prefix:str='') -> None:
|
|||||||
if DumpToConsole:
|
if DumpToConsole:
|
||||||
print(text, data)
|
print(text, data)
|
||||||
if DumpToFile:
|
if DumpToFile:
|
||||||
open((DumpToFile if (DumpToFile and type(DumpToFile) == str) else "./Dump.txt"), 'a').write(text + '\n')
|
open((DumpToFile if (DumpToFile and type(DumpToFile) == str) else "./Data/Dump.txt"), 'a').write(text + '\n')
|
||||||
|
|
||||||
def SendMessage(context:EventContext, data:OutputMessageData, from_sent:bool=False) -> None:
|
def send_status(context:EventContext, code:int, lang:str=None, extra:str=None, preamble:bool=True, summary:bool=True):
|
||||||
|
global_string = (lambda query: get_string(GlobalStrings, query, lang))
|
||||||
|
summary_text = (global_string(f"statuses.{code}.summary") or '')
|
||||||
|
return send_message(context, {"text_html": (
|
||||||
|
(((f'{global_string(f"statuses.{code}.icon")} {global_string("error") if code >= 400 else ""}'.strip()
|
||||||
|
+ f' {code}: {global_string(f"statuses.{code}.title")}. {summary_text if summary else ""}').strip()) if preamble else '')
|
||||||
|
+ '\n\n' + (extra or "")).strip()})
|
||||||
|
|
||||||
|
def send_status_400(context:EventContext, lang:str=None, extra:str=None):
|
||||||
|
return send_status(context, 400, lang,
|
||||||
|
f'{context.endpoint.get_help_text(lang)}\n\n{extra or ""}', preamble=False, summary=False)
|
||||||
|
|
||||||
|
def send_status_error(context:EventContext, lang:str=None, code:int=500, extra:str=None):
|
||||||
|
result = send_status(context, code, lang,
|
||||||
|
f'{html_escape(get_exception_text())}\n\n{extra or ""}')
|
||||||
|
app_log()
|
||||||
|
return result
|
||||||
|
|
||||||
|
def send_message(context:EventContext, data:OutputMessageData, *, from_sent:bool=False):
|
||||||
|
context = ObjectClone(context)
|
||||||
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:
|
if data.text_html and not data.text_plain:
|
||||||
data.text_plain = BeautifulSoup(data.text_html, "html.parser").get_text()
|
data.text_plain = BeautifulSoup(data.text_html, "html.parser").get_text()
|
||||||
@ -229,15 +258,18 @@ def SendMessage(context:EventContext, data:OutputMessageData, from_sent:bool=Fal
|
|||||||
OnOutputMessageSent(data, context.data, from_sent)
|
OnOutputMessageSent(data, context.data, from_sent)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def SendNotice(context:EventContext, data) -> None:
|
def send_notice(context:EventContext, data):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def DeleteMessage(context:EventContext, data) -> None:
|
def edit_message(context:EventContext, data:MessageData):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def delete_message(context:EventContext, data:MessageData):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def RegisterPlatform(name:str, main:callable, sender:callable, linker:callable=None, *, event_class=None, manager_class=None, agent_info=None) -> None:
|
def RegisterPlatform(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)
|
||||||
Log(f"{name}, ", inline=True)
|
app_log(f"{name}, ", inline=True)
|
||||||
|
|
||||||
def RegisterModule(name:str, endpoints:dict, *, group:str|None=None) -> None:
|
def RegisterModule(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))
|
||||||
@ -245,27 +277,40 @@ def RegisterModule(name:str, endpoints:dict, *, group:str|None=None) -> None:
|
|||||||
module.strings = good_yaml_load(open(file, 'r').read())
|
module.strings = good_yaml_load(open(file, 'r').read())
|
||||||
module.get_string = (lambda query, lang=None: get_string(module.strings, query, lang))
|
module.get_string = (lambda query, lang=None: get_string(module.strings, query, lang))
|
||||||
Modules[name] = module
|
Modules[name] = module
|
||||||
Log(f"{name}, ", inline=True)
|
if group not in ModuleGroups:
|
||||||
|
ModuleGroups[group] = []
|
||||||
|
ModuleGroups[group].append(name)
|
||||||
|
app_log(f"{name}, ", inline=True)
|
||||||
for endpoint in endpoints:
|
for endpoint in endpoints:
|
||||||
endpoint.module = module
|
endpoint.module = module
|
||||||
for name in endpoint.names:
|
for name in endpoint.names:
|
||||||
Endpoints[name] = endpoint
|
Endpoints[name] = endpoint
|
||||||
|
|
||||||
def CallEndpoint(name:str, context:EventContext, data:InputMessageData):
|
def instanciate_endpoint(name:str, prefix:str):
|
||||||
endpoint = Endpoints[name]
|
if not (endpoint := obj_get(Endpoints, name)):
|
||||||
|
return None
|
||||||
|
endpoint = ObjectClone(endpoint)
|
||||||
|
endpoint.name = name
|
||||||
|
endpoint.get_string = (lambda query=name, lang=None:
|
||||||
|
endpoint.module.get_string(f"endpoints.{name}.{query}", lang))
|
||||||
|
endpoint.get_help_text = (lambda lang=None: get_help_text(endpoint, lang, prefix))
|
||||||
|
return endpoint
|
||||||
|
|
||||||
|
def call_endpoint(context:EventContext, data:InputMessageData):
|
||||||
|
if not ((command := data.command) and (name := command.name)):
|
||||||
|
return
|
||||||
|
if not (endpoint := instanciate_endpoint(name, command.prefix)):
|
||||||
|
return
|
||||||
context.data = data
|
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:
|
|
||||||
endpoint.module.get_string(f"endpoints.{data.command.name}.{query}", lang))
|
|
||||||
context.endpoint.help_text = (lambda lang=None: help_text(endpoint, lang))
|
|
||||||
if callable(agent_info := Platforms[context.platform].agent_info):
|
if callable(agent_info := Platforms[context.platform].agent_info):
|
||||||
Platforms[context.platform].agent_info = agent_info()
|
Platforms[context.platform].agent_info = agent_info()
|
||||||
return endpoint.handler(context, data)
|
return endpoint.handler(context, data)
|
||||||
|
|
||||||
def WriteNewConfig() -> None:
|
def write_new_config() -> None:
|
||||||
Log("💾️ No configuration found! Generating and writing to `./Config.py`... ", inline=True)
|
app_log("💾️ No configuration found! Generating and writing to `./Data/Config.py`... ", inline=True)
|
||||||
with open("./Config.py", 'w') as configFile:
|
with open("./Data/Config.py", 'w') as configFile:
|
||||||
opening = '# windog config start #'
|
opening = '# windog config start #'
|
||||||
closing = '# end windog config #'
|
closing = '# end windog config #'
|
||||||
for folder in ("LibWinDog", "ModWinDog"):
|
for folder in ("LibWinDog", "ModWinDog"):
|
||||||
@ -279,50 +324,49 @@ def WriteNewConfig() -> None:
|
|||||||
except IndexError:
|
except IndexError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def Main() -> None:
|
def app_main() -> None:
|
||||||
#SetupDb()
|
#SetupDb()
|
||||||
Log(f"📨️ Initializing Platforms... ", newline=False)
|
app_log(f"📨️ Initializing Platforms... ", newline=False)
|
||||||
for platform in Platforms.values():
|
for platform in Platforms.values():
|
||||||
if platform.main():
|
if platform.main():
|
||||||
Log(f"{platform.name}, ", inline=True)
|
app_log(f"{platform.name}, ", inline=True)
|
||||||
Log("...Done. ✅️", inline=True, newline=True)
|
app_log("...Done. ✅️", inline=True, newline=True)
|
||||||
Log("🐶️ WinDog Ready!")
|
app_log("🐶️ WinDog Ready!")
|
||||||
while True:
|
while True:
|
||||||
time.sleep(9**9)
|
time.sleep(9**9)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
Log("🌞️ WinDog Starting...")
|
app_log("🌞️ WinDog Starting...")
|
||||||
GlobalStrings = good_yaml_load(open("./WinDog.yaml", 'r').read())
|
GlobalStrings = good_yaml_load(open("./WinDog.yaml", 'r').read())
|
||||||
Platforms, Modules, ModuleGroups, Endpoints = {}, {}, {}, {}
|
Platforms, Modules, ModuleGroups, Endpoints = {}, {}, {}, {}
|
||||||
|
|
||||||
for folder in ("LibWinDog/Platforms", "ModWinDog"):
|
for folder in ("LibWinDog/Platforms", "ModWinDog"):
|
||||||
match folder:
|
match folder:
|
||||||
case "LibWinDog/Platforms":
|
case "LibWinDog/Platforms":
|
||||||
Log("📩️ Loading Platforms... ", newline=False)
|
app_log("📩️ Loading Platforms... ", newline=False)
|
||||||
case "ModWinDog":
|
case "ModWinDog":
|
||||||
Log("🔩️ Loading Modules... ", newline=False)
|
app_log("🔩️ Loading Modules... ", newline=False)
|
||||||
for name in listdir(f"./{folder}"):
|
for name in listdir(f"./{folder}"):
|
||||||
path = f"./{folder}/{name}"
|
path = f"./{folder}/{name}"
|
||||||
if isfile(path):
|
if path.endswith(".py") and isfile(path):
|
||||||
exec(open(path, 'r').read())
|
exec(open(path).read())
|
||||||
elif isdir(path):
|
elif isdir(path):
|
||||||
files = listdir(path)
|
files = listdir(path)
|
||||||
if f"{name}.py" in files:
|
if f"{name}.py" in files:
|
||||||
files.remove(f"{name}.py")
|
files.remove(f"{name}.py")
|
||||||
exec(open(f"{path}/{name}.py", 'r').read())
|
exec(open(f"{path}/{name}.py", 'r').read())
|
||||||
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}/{file}", 'r').read())
|
||||||
Log("...Done. ✅️", inline=True, newline=True)
|
app_log("...Done. ✅️", inline=True, newline=True)
|
||||||
|
|
||||||
Log("💽️ Loading Configuration... ", newline=False)
|
app_log("💽️ Loading Configuration... ", newline=False)
|
||||||
from Config import *
|
if isfile("./Data/Config.py"):
|
||||||
if isfile("./Config.py"):
|
exec(open("./Data/Config.py", 'r').read())
|
||||||
from Config import *
|
|
||||||
else:
|
else:
|
||||||
WriteNewConfig()
|
write_new_config()
|
||||||
Log("Done. ✅️", inline=True, newline=True)
|
app_log("Done. ✅️", inline=True, newline=True)
|
||||||
|
|
||||||
Main()
|
app_main()
|
||||||
Log("🌚️ WinDog Stopping...")
|
app_log("🌚️ WinDog Stopping...")
|
||||||
|
|
||||||
|
38
WinDog.yaml
Normal file → Executable file
38
WinDog.yaml
Normal file → Executable file
@ -1,3 +1,6 @@
|
|||||||
|
error:
|
||||||
|
en: Error
|
||||||
|
it: Errore
|
||||||
or:
|
or:
|
||||||
en: or
|
en: or
|
||||||
it: o
|
it: o
|
||||||
@ -10,4 +13,39 @@ text:
|
|||||||
usage:
|
usage:
|
||||||
en: Usage
|
en: Usage
|
||||||
it: Uso
|
it: Uso
|
||||||
|
statuses:
|
||||||
|
102:
|
||||||
|
title:
|
||||||
|
en: Processing
|
||||||
|
icon: ⚙️
|
||||||
|
403:
|
||||||
|
title:
|
||||||
|
en: Forbidden
|
||||||
|
it: Vietato
|
||||||
|
summary:
|
||||||
|
en: You don't have the necessary permissions to access the requested resource or complete the specified action.
|
||||||
|
it: Non hai i permessi necessari per accedere alla risorsa richiesta o completare l'azione specificata.
|
||||||
|
icon: ⛔️
|
||||||
|
404:
|
||||||
|
title:
|
||||||
|
en: Not Found
|
||||||
|
it: Non Trovato
|
||||||
|
summary:
|
||||||
|
en: The requested resource was not found or the specified action cannot be completed.
|
||||||
|
it: La risorsa richiesta non è stata trovata o non è possibile completare l'azione specificata.
|
||||||
|
icon: 🕳️
|
||||||
|
500:
|
||||||
|
title:
|
||||||
|
en: Internal Error
|
||||||
|
it: Errore Interno
|
||||||
|
icon: 💣️
|
||||||
|
503:
|
||||||
|
title:
|
||||||
|
en: Service Unavailable
|
||||||
|
it: Servizio non Disponibile
|
||||||
|
icon: 🪦️
|
||||||
|
504:
|
||||||
|
title:
|
||||||
|
en: Gateway Timeout
|
||||||
|
icon: 🧻️
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
beautifulsoup4
|
beautifulsoup4
|
||||||
Markdown
|
|
||||||
peewee
|
peewee
|
||||||
PyYAML
|
PyYAML
|
||||||
|
Reference in New Issue
Block a user