mirror of
https://gitlab.com/octospacc/WinDog.git
synced 2025-06-05 22:09:20 +02:00
Add /api/v1/FileProxy, support for Telegram file linking
This commit is contained in:
@ -56,7 +56,7 @@ def MatrixMain(path:str) -> bool:
|
|||||||
def MatrixMakeInputMessageData(room:nio.MatrixRoom, event:nio.RoomMessage) -> InputMessageData:
|
def MatrixMakeInputMessageData(room:nio.MatrixRoom, event:nio.RoomMessage) -> InputMessageData:
|
||||||
data = InputMessageData(
|
data = InputMessageData(
|
||||||
message_id = f"matrix:{event.event_id}",
|
message_id = f"matrix:{event.event_id}",
|
||||||
datetime = event.server_timestamp,
|
timestamp = event.server_timestamp,
|
||||||
text_plain = event.body,
|
text_plain = event.body,
|
||||||
text_html = obj_get(event, "formatted_body"), # this could be unavailable
|
text_html = obj_get(event, "formatted_body"), # this could be unavailable
|
||||||
media = ({"url": event.url} if obj_get(event, "url") else None),
|
media = ({"url": event.url} if obj_get(event, "url") else None),
|
||||||
|
@ -16,11 +16,14 @@ TelegramToken = None
|
|||||||
TelegramGetterChannel = TelegramGetterGroup = None
|
TelegramGetterChannel = TelegramGetterGroup = None
|
||||||
|
|
||||||
import telegram, telegram.ext
|
import telegram, telegram.ext
|
||||||
from telegram import Bot #, Update
|
#from telegram import Bot #, Update
|
||||||
#from telegram.helpers import escape_markdown
|
#from telegram.helpers import escape_markdown
|
||||||
#from telegram.ext import Application, filters, CommandHandler, MessageHandler, CallbackContext
|
#from telegram.ext import Application, filters, CommandHandler, MessageHandler, CallbackContext
|
||||||
from telegram.utils.helpers import escape_markdown
|
from telegram.utils.helpers import escape_markdown
|
||||||
from telegram.ext import CommandHandler, MessageHandler, Filters, CallbackContext
|
from telegram.ext import CommandHandler, MessageHandler, Filters, CallbackContext
|
||||||
|
from base64 import urlsafe_b64encode
|
||||||
|
from hashlib import sha256
|
||||||
|
from hmac import new as hmac_new
|
||||||
|
|
||||||
TelegramClient = None
|
TelegramClient = None
|
||||||
|
|
||||||
@ -45,20 +48,25 @@ def TelegramMakeUserData(user:telegram.User) -> UserData:
|
|||||||
name = user.first_name,
|
name = user.first_name,
|
||||||
)
|
)
|
||||||
|
|
||||||
def TelegramMakeInputMessageData(message:telegram.Message) -> InputMessageData:
|
def TelegramMakeInputMessageData(message:telegram.Message, access_token:str=None) -> InputMessageData:
|
||||||
#if not message:
|
#if not message:
|
||||||
# return None
|
# return None
|
||||||
|
timestamp = int(time.mktime(message.date.timetuple()))
|
||||||
media = None
|
media = None
|
||||||
if (photo := (message.photo and message.photo[-1])):
|
if (photo := (message.photo and message.photo[-1])):
|
||||||
media = {"url": photo.file_id, "type": "image/"}
|
media = {"url": photo.file_id, "type": "image/jpeg"}
|
||||||
elif (video_note := message.video_note):
|
elif (video_note := message.video_note):
|
||||||
media = {"url": video_note.file_id, "type": "video/"}
|
media = {"url": video_note.file_id, "type": "video/mp4"}
|
||||||
elif (media := (message.video or message.voice or message.audio or message.document or message.sticker)):
|
elif (media := (message.video or message.voice or message.audio or message.document or message.sticker)):
|
||||||
media = {"url": media.file_id, "type": media.mime_type}
|
media = {"url": media.file_id, "type": obj_get(media, "mime_type")}
|
||||||
|
if (file_id := obj_get(media, "url")):
|
||||||
|
media["url"] = f"telegram:{file_id}"
|
||||||
|
if access_token:
|
||||||
|
media["token"] = get_media_token_hash(media["url"], timestamp, access_token)
|
||||||
data = InputMessageData(
|
data = InputMessageData(
|
||||||
id = f"telegram:{message.message_id}",
|
id = f"telegram:{message.message_id}",
|
||||||
message_id = f"telegram:{message.message_id}",
|
message_id = f"telegram:{message.message_id}",
|
||||||
datetime = int(time.mktime(message.date.timetuple())),
|
timestamp = timestamp,
|
||||||
text_plain = (message.text or message.caption),
|
text_plain = (message.text or message.caption),
|
||||||
text_markdown = message.text_markdown_v2,
|
text_markdown = message.text_markdown_v2,
|
||||||
media = media,
|
media = media,
|
||||||
@ -88,14 +96,14 @@ def TelegramHandler(update:telegram.Update, context:CallbackContext=None) -> Non
|
|||||||
call_endpoint(EventContext(platform="telegram", event=update, manager=context), data)
|
call_endpoint(EventContext(platform="telegram", event=update, manager=context), data)
|
||||||
Thread(target=handler).start()
|
Thread(target=handler).start()
|
||||||
|
|
||||||
def TelegramGetter(context:EventContext, data:InputMessageData) -> InputMessageData:
|
def TelegramGetter(context:EventContext, data:InputMessageData, access_token:str=None) -> InputMessageData:
|
||||||
# bot API doesn't allow direct access of messages,
|
# bot API doesn't allow direct access of messages,
|
||||||
# so we ask the server to copy it to a service channel, so that the API returns its data, then delete the copy
|
# so we ask the server to copy it to a service channel, so that the API returns its data, then delete the copy
|
||||||
message = TelegramMakeInputMessageData(
|
message = TelegramMakeInputMessageData(
|
||||||
context.manager.bot.forward_message(
|
context.manager.bot.forward_message(
|
||||||
message_id=data.message_id,
|
message_id=data.message_id,
|
||||||
from_chat_id=data.room.id,
|
from_chat_id=data.room.id,
|
||||||
chat_id=TelegramGetterChannel))
|
chat_id=TelegramGetterChannel), access_token)
|
||||||
delete_message(context, message)
|
delete_message(context, message)
|
||||||
return message
|
return message
|
||||||
|
|
||||||
@ -112,7 +120,7 @@ def TelegramSender(context:EventContext, data:OutputMessageData):
|
|||||||
"reply_photo" if medium.type.startswith("image/") else
|
"reply_photo" if medium.type.startswith("image/") else
|
||||||
"reply_video" if medium.type.startswith("video/") else
|
"reply_video" if medium.type.startswith("video/") else
|
||||||
"reply_document"))(
|
"reply_document"))(
|
||||||
(medium.bytes or medium.url),
|
(medium.bytes or medium.url.removeprefix("telegram:")),
|
||||||
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)
|
||||||
@ -125,7 +133,15 @@ def TelegramSender(context:EventContext, data:OutputMessageData):
|
|||||||
return TelegramMakeInputMessageData(result)
|
return TelegramMakeInputMessageData(result)
|
||||||
|
|
||||||
def TelegramDeleter(context:EventContext, data:MessageData):
|
def TelegramDeleter(context:EventContext, data:MessageData):
|
||||||
context.manager.bot.delete_message(chat_id=data.room.id, message_id=data.message_id)
|
return context.manager.bot.delete_message(chat_id=data.room.id, message_id=data.message_id)
|
||||||
|
|
||||||
|
def TelegramFileGetter(context:EventContext, file_id:str, out=None):
|
||||||
|
try:
|
||||||
|
file = context.manager.bot.get_file(file_id)
|
||||||
|
return (lambda: file.download(out=out)) if out else file.download_as_bytearray()
|
||||||
|
#return file.download(out=out) if out else file.download_as_bytearray()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
# TODO support usernames
|
# TODO support usernames
|
||||||
# TODO remove the platform stripping here (after modifying above functions here that use it), it's now implemented in get_link
|
# TODO remove the platform stripping here (after modifying above functions here that use it), it's now implemented in get_link
|
||||||
@ -148,6 +164,7 @@ register_platform(
|
|||||||
linker=TelegramLinker,
|
linker=TelegramLinker,
|
||||||
sender=TelegramSender,
|
sender=TelegramSender,
|
||||||
deleter=TelegramDeleter,
|
deleter=TelegramDeleter,
|
||||||
|
filegetter=TelegramFileGetter,
|
||||||
event_class=telegram.Update,
|
event_class=telegram.Update,
|
||||||
manager_class=(lambda:TelegramClient),
|
manager_class=(lambda:TelegramClient),
|
||||||
agent_info=(lambda:TelegramMakeUserData(TelegramClient.bot.get_me())),
|
agent_info=(lambda:TelegramMakeUserData(TelegramClient.bot.get_me())),
|
||||||
|
@ -16,6 +16,9 @@ WebTokens = {}
|
|||||||
""" # end windog config # """
|
""" # end windog config # """
|
||||||
|
|
||||||
import queue
|
import queue
|
||||||
|
from base64 import urlsafe_b64encode
|
||||||
|
from hashlib import sha256
|
||||||
|
from hmac import new as hmac_new
|
||||||
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from uuid6 import uuid7
|
from uuid6 import uuid7
|
||||||
@ -34,11 +37,11 @@ web_html_prefix = (lambda document_class='', head_extra='': (f'''<!DOCTYPE html>
|
|||||||
|
|
||||||
class WebServerClass(BaseHTTPRequestHandler):
|
class WebServerClass(BaseHTTPRequestHandler):
|
||||||
def parse_path(self):
|
def parse_path(self):
|
||||||
path = self.path.strip('/').lower()
|
path = self.path.strip('/')
|
||||||
try:
|
try:
|
||||||
query = path.split('?')[1]
|
query = path.split('?')[1]
|
||||||
params = dict(parse_qsl(query))
|
params = dict(parse_qsl(query))
|
||||||
query = query.split('&')
|
query = query.lower().split('&')
|
||||||
except Exception:
|
except Exception:
|
||||||
query = []
|
query = []
|
||||||
params = {}
|
params = {}
|
||||||
@ -123,6 +126,7 @@ class WebServerClass(BaseHTTPRequestHandler):
|
|||||||
self.init_new_room()
|
self.init_new_room()
|
||||||
elif path == "favicon.ico":
|
elif path == "favicon.ico":
|
||||||
self.send_response(404)
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
elif path == "windog.css":
|
elif path == "windog.css":
|
||||||
self.send_text_content(web_css_style, "text/css")
|
self.send_text_content(web_css_style, "text/css")
|
||||||
#elif path == "on-connection-dropped.css":
|
#elif path == "on-connection-dropped.css":
|
||||||
@ -155,6 +159,45 @@ class WebServerClass(BaseHTTPRequestHandler):
|
|||||||
elif fields[:2] == ["api", "v1"]:
|
elif fields[:2] == ["api", "v1"]:
|
||||||
self.handle_api("POST", fields[2:], params)
|
self.handle_api("POST", fields[2:], params)
|
||||||
|
|
||||||
|
def handle_api(self, verb:str, fields:list, params:dict):
|
||||||
|
result = None
|
||||||
|
if (access_token := self.headers["Authorization"]):
|
||||||
|
access_token = ' '.join(access_token.split(' ')[1:]) if access_token.startswith("Bearer ") else None
|
||||||
|
else:
|
||||||
|
access_token = obj_get(params, "authorization")
|
||||||
|
fields.append('')
|
||||||
|
match fields[0]:
|
||||||
|
case "Call" if (text := obj_get(params, "text")) or (endpoint := obj_get(params, "endpoint")):
|
||||||
|
result = call_endpoint(EventContext(), InputMessageData(
|
||||||
|
command = TextCommandData(text) if text else SafeNamespace(
|
||||||
|
name = endpoint,
|
||||||
|
arguments = self.parse_command_arguments_web(params),
|
||||||
|
body = obj_get(params, "body"),
|
||||||
|
tokens = [],
|
||||||
|
),
|
||||||
|
user = UserData(
|
||||||
|
settings = UserSettingsData(),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
case "GetMessage" if (auth := self.check_web_auth(access_token)) and (message_id := obj_get(params, "message_id")) and (room_id := obj_get(params, "room_id")):
|
||||||
|
if (type(auth) == bool) or ((type(auth) == list) and (room_id in auth)):
|
||||||
|
result = get_message(EventContext(), {"message_id": message_id, "room": {"id": room_id}}, access_token)
|
||||||
|
#case "sendmessage":
|
||||||
|
#case "endpoints":
|
||||||
|
case "FileProxy" if (url := obj_get(params, "url")) and (timestamp := obj_get(params, "timestamp")) and (token := obj_get(params, "token")):
|
||||||
|
if self.validate_file_token(url, timestamp, token) and (passtrough := get_file(EventContext(), url, self.wfile)):
|
||||||
|
self.send_response(200)
|
||||||
|
if (filetype := obj_get(params, "type")):
|
||||||
|
self.send_header("Content-Type", filetype)
|
||||||
|
self.end_headers()
|
||||||
|
passtrough()
|
||||||
|
return
|
||||||
|
if result:
|
||||||
|
self.send_text_content(data_to_json(result), "application/json")
|
||||||
|
else:
|
||||||
|
self.send_response(404)
|
||||||
|
self.end_headers()
|
||||||
|
|
||||||
def parse_command_arguments_web(self, params:dict):
|
def parse_command_arguments_web(self, params:dict):
|
||||||
args = SafeNamespace()
|
args = SafeNamespace()
|
||||||
for key in params:
|
for key in params:
|
||||||
@ -162,28 +205,16 @@ class WebServerClass(BaseHTTPRequestHandler):
|
|||||||
args[key[1:]] = params[key]
|
args[key[1:]] = params[key]
|
||||||
return args
|
return args
|
||||||
|
|
||||||
def handle_api(self, verb:str, fields:list, params:dict):
|
def check_web_auth(self, token:str):
|
||||||
fields.append('')
|
if token and (identity := obj_get(WebTokens, token)):
|
||||||
match (method := fields[0].lower()):
|
return True if (identity["owners"] == AdminIds) else identity["room_whitelist"]
|
||||||
case "call" if (text := obj_get(params, "text")) or (endpoint := obj_get(params, "endpoint")):
|
return False
|
||||||
result = call_endpoint(EventContext(), InputMessageData(
|
|
||||||
command = TextCommandData(text) if text else SafeNamespace(
|
def validate_file_token(self, url:str, timestamp:int, file_token:str):
|
||||||
name = endpoint,
|
for token in WebTokens:
|
||||||
arguments = self.parse_command_arguments_web(params),
|
if urlsafe_b64encode(hmac_new(token.encode(), f"{url}:{timestamp}".encode(), sha256).digest()).decode() == file_token:
|
||||||
body = obj_get(params, 'body')
|
return True
|
||||||
),
|
return False
|
||||||
user = UserData(
|
|
||||||
settings = UserSettingsData(),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
if result:
|
|
||||||
self.send_text_content(data_to_json(result), "application/json")
|
|
||||||
return
|
|
||||||
#case "getmessage":
|
|
||||||
#case "sendmessage":
|
|
||||||
#case "endpoints":
|
|
||||||
self.send_response(404)
|
|
||||||
self.end_headers()
|
|
||||||
|
|
||||||
def WebPushEvent(room_id:str, user_id:str, text:str, headers:dict[str:str]):
|
def WebPushEvent(room_id:str, user_id:str, text:str, headers:dict[str:str]):
|
||||||
context = EventContext(platform="web", event=SafeNamespace(room_id=room_id, user_id=user_id))
|
context = EventContext(platform="web", event=SafeNamespace(room_id=room_id, user_id=user_id))
|
||||||
|
@ -10,13 +10,27 @@ def get_message_wrapper(context:EventContext, data:InputMessageData):
|
|||||||
if check_bot_admin(data.user) and (message_id := data.command.arguments.message_id) and (room_id := (data.command.arguments.room_id or data.room.id)):
|
if check_bot_admin(data.user) and (message_id := data.command.arguments.message_id) and (room_id := (data.command.arguments.room_id or data.room.id)):
|
||||||
return get_message(context, {"message_id": message_id, "room": {"id": room_id}})
|
return get_message(context, {"message_id": message_id, "room": {"id": room_id}})
|
||||||
|
|
||||||
# TODO work with links to messages
|
# TODO dump and getmessage should work with links to messages!
|
||||||
|
|
||||||
def cDump(context:EventContext, data:InputMessageData):
|
def cDump(context:EventContext, data:InputMessageData):
|
||||||
if not (message := (data.quoted or get_message_wrapper(context, data))):
|
if not (message := (data.quoted or get_message_wrapper(context, data))):
|
||||||
return send_status_400(context, data.user.settings.language)
|
return send_status_400(context, data.user.settings.language)
|
||||||
text = data_to_json(message, indent=" ")
|
text = data_to_json(message, indent=" ")
|
||||||
return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'})
|
return send_message(context, {"text_html": f'<pre>{html_escape(text)}</pre>'})
|
||||||
|
|
||||||
|
def cGetLink(context:EventContext, data:InputMessageData):
|
||||||
|
if not (message := (data.quoted or get_message_wrapper(context, data))):
|
||||||
|
return send_status_400(context, data.user.settings.language)
|
||||||
|
text = ''
|
||||||
|
if (url := message.message_url):
|
||||||
|
text += f"Message: {url}\n"
|
||||||
|
if (media := message.media) and (url := media.url):
|
||||||
|
link = get_media_link(url, type=media.type, timestamp=message.timestamp, access_token=tuple(WebTokens)[0])
|
||||||
|
text += f"Media: {link or url}\n"
|
||||||
|
if not text:
|
||||||
|
return send_status_400(context, data.user.settings.language)
|
||||||
|
return send_message(context, {"text_plain": text})
|
||||||
|
|
||||||
def cGetMessage(context:EventContext, data:InputMessageData):
|
def cGetMessage(context:EventContext, data:InputMessageData):
|
||||||
if not (message := get_message_wrapper(context, data)):
|
if not (message := get_message_wrapper(context, data)):
|
||||||
return send_status_400(context, data.user.settings.language)
|
return send_status_400(context, data.user.settings.language)
|
||||||
@ -27,6 +41,10 @@ register_module(name="Dumper", group="Geek", endpoints=[
|
|||||||
"message_id": True,
|
"message_id": True,
|
||||||
"room_id": True,
|
"room_id": True,
|
||||||
}),
|
}),
|
||||||
|
SafeNamespace(names=["getlink"], handler=cGetLink, quoted=True, arguments={
|
||||||
|
"message_id": True,
|
||||||
|
"room_id": True,
|
||||||
|
}),
|
||||||
SafeNamespace(names=["getmessage"], handler=cGetMessage, arguments={
|
SafeNamespace(names=["getmessage"], handler=cGetMessage, arguments={
|
||||||
"message_id": True,
|
"message_id": True,
|
||||||
"room_id": True,
|
"room_id": True,
|
||||||
|
40
WinDog.py
40
WinDog.py
@ -220,7 +220,7 @@ def send_status_error(context:EventContext, lang:str=None, code:int=500, extra:s
|
|||||||
app_log()
|
app_log()
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_link(context:EventContext, data:InputMessageData) -> InputMessageData:
|
def get_link(context:EventContext, data:InputMessageData):
|
||||||
data = (InputMessageData(**data) if type(data) == dict else data)
|
data = (InputMessageData(**data) if type(data) == dict else data)
|
||||||
if (data.room and data.room.id):
|
if (data.room and data.room.id):
|
||||||
data.room.id = data.room.id.removeprefix(f"{context.platform}:")
|
data.room.id = data.room.id.removeprefix(f"{context.platform}:")
|
||||||
@ -230,9 +230,28 @@ def get_link(context:EventContext, data:InputMessageData) -> InputMessageData:
|
|||||||
data.id = data.id.removeprefix(f"{context.platform}:")
|
data.id = data.id.removeprefix(f"{context.platform}:")
|
||||||
return Platforms[context.platform].linker(data)
|
return Platforms[context.platform].linker(data)
|
||||||
|
|
||||||
def get_message(context:EventContext, data:InputMessageData) -> InputMessageData:
|
def get_media_token_hash(url:str, timestamp:int, access_token:str):
|
||||||
|
return urlsafe_b64encode(hmac_new(access_token.encode(), f"{url}:{timestamp}".encode(), sha256).digest()).decode()
|
||||||
|
|
||||||
|
def get_media_link(url:str, type:str=None, timestamp:int=None, access_token:str=None):
|
||||||
|
urllow = url.lower()
|
||||||
|
if not (urllow.startswith('http') or urllow.startswith('https') or urllow.startswith('/')):
|
||||||
|
if not (timestamp and access_token):
|
||||||
|
return None
|
||||||
|
url = WebConfig["url"] + f"/api/v1/FileProxy/?url={url}&type={type or ''}×tamp={timestamp}&token={get_media_token_hash(url, timestamp, access_token)}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
def get_message(context:EventContext, data:InputMessageData, access_token:str=None) -> InputMessageData:
|
||||||
data = (InputMessageData(**data) if type(data) == dict else data)
|
data = (InputMessageData(**data) if type(data) == dict else data)
|
||||||
message = Platforms[context.platform].getter(context, data)
|
tokens = data.room.id.split(':')
|
||||||
|
if tokens[0] != context.platform:
|
||||||
|
context.platform = tokens[0]
|
||||||
|
context.manager = context.event = None
|
||||||
|
data.room.id = ':'.join(tokens[1:])
|
||||||
|
platform = Platforms[context.platform]
|
||||||
|
if (not context.manager) and (manager := platform.manager_class):
|
||||||
|
context.manager = call_or_return(manager)
|
||||||
|
message = platform.getter(context, data, access_token)
|
||||||
linked = get_link(context, data)
|
linked = get_link(context, data)
|
||||||
return ObjectUnion(message, {
|
return ObjectUnion(message, {
|
||||||
"message_id": data.message_id,
|
"message_id": data.message_id,
|
||||||
@ -259,6 +278,7 @@ def send_message(context:EventContext, data:OutputMessageData, *, from_sent:bool
|
|||||||
if data.ReplyTo: # TODO decide if this has to be this way
|
if data.ReplyTo: # TODO decide if this has to be this way
|
||||||
data.ReplyTo = ':'.join(data.ReplyTo.split(':')[1:])
|
data.ReplyTo = ':'.join(data.ReplyTo.split(':')[1:])
|
||||||
if context.platform not in Platforms:
|
if context.platform not in Platforms:
|
||||||
|
# platform has no handler, so instead of doing a send action just return data to caller
|
||||||
return ObjectUnion(data, {"status": status})
|
return ObjectUnion(data, {"status": status})
|
||||||
platform = Platforms[context.platform]
|
platform = Platforms[context.platform]
|
||||||
if (not context.manager) and (manager := platform.manager_class):
|
if (not context.manager) and (manager := platform.manager_class):
|
||||||
@ -280,8 +300,18 @@ def delete_message(context:EventContext, data:MessageData):
|
|||||||
data.id = data.id.removeprefix(f"{context.platform}:")
|
data.id = data.id.removeprefix(f"{context.platform}:")
|
||||||
return Platforms[context.platform].deleter(context, data)
|
return Platforms[context.platform].deleter(context, data)
|
||||||
|
|
||||||
def register_platform(name:str, main:callable, sender:callable, getter:callable=None, linker:callable=None, deleter:callable=None, *, event_class=None, manager_class=None, agent_info=None) -> None:
|
def get_file(context:EventContext, url:str, out=None):
|
||||||
Platforms[name.lower()] = SafeNamespace(name=name, main=main, getter=getter, linker=linker, sender=sender, deleter=deleter, event_class=event_class, manager_class=manager_class, agent_info=agent_info)
|
tokens = url.split(':')
|
||||||
|
if tokens[0] != context.platform:
|
||||||
|
context.platform = tokens[0]
|
||||||
|
context.manager = context.event = None
|
||||||
|
platform = Platforms[context.platform]
|
||||||
|
if (not context.manager) and (manager := platform.manager_class):
|
||||||
|
context.manager = call_or_return(manager)
|
||||||
|
return platform.filegetter(context, ':'.join(tokens[1:]), out)
|
||||||
|
|
||||||
|
def register_platform(name:str, main:callable, getter:callable=None, linker:callable=None, sender:callable=None, deleter:callable=None, filegetter:callable=None, *, event_class=None, manager_class=None, agent_info=None) -> None:
|
||||||
|
Platforms[name.lower()] = SafeNamespace(name=name, main=main, getter=getter, linker=linker, sender=sender, deleter=deleter, filegetter=filegetter, event_class=event_class, manager_class=manager_class, agent_info=agent_info)
|
||||||
app_log(f"{name}, ", inline=True)
|
app_log(f"{name}, ", inline=True)
|
||||||
|
|
||||||
def register_module(name:str, endpoints:dict, *, group:str|None=None) -> None:
|
def register_module(name:str, endpoints:dict, *, group:str|None=None) -> None:
|
||||||
|
Reference in New Issue
Block a user