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