summaryrefslogtreecommitdiff
path: root/plugin_loader
diff options
context:
space:
mode:
authorJonas Dellinger <jonas@dellinger.dev>2022-05-13 19:14:47 +0200
committerJonas Dellinger <jonas@dellinger.dev>2022-05-13 19:14:47 +0200
commit74438a31458af8bddd08d90eacc6d63677bab844 (patch)
treea7bfc044941f65c7f9971c5386c463eac31be768 /plugin_loader
parent945db5de4788feefebc845817752472419051640 (diff)
downloaddecky-loader-74438a31458af8bddd08d90eacc6d63677bab844.tar.gz
decky-loader-74438a31458af8bddd08d90eacc6d63677bab844.zip
Work on react frontend loader
Diffstat (limited to 'plugin_loader')
-rw-r--r--plugin_loader/browser.py89
-rw-r--r--plugin_loader/injector.py97
-rw-r--r--plugin_loader/loader.py191
-rw-r--r--plugin_loader/main.py144
-rw-r--r--plugin_loader/plugin.py104
-rw-r--r--plugin_loader/static/library.js71
-rw-r--r--plugin_loader/static/plugin_page.js98
-rw-r--r--plugin_loader/static/styles.css3
-rw-r--r--plugin_loader/templates/plugin_view.html76
-rw-r--r--plugin_loader/utilities.py106
10 files changed, 0 insertions, 979 deletions
diff --git a/plugin_loader/browser.py b/plugin_loader/browser.py
deleted file mode 100644
index ffec26b3..00000000
--- a/plugin_loader/browser.py
+++ /dev/null
@@ -1,89 +0,0 @@
-from injector import get_tab
-from logging import getLogger
-from os import path, rename
-from shutil import rmtree
-from aiohttp import ClientSession, web
-from io import BytesIO
-from zipfile import ZipFile
-from concurrent.futures import ProcessPoolExecutor
-from asyncio import get_event_loop
-from time import time
-from hashlib import sha256
-from subprocess import Popen
-
-class PluginInstallContext:
- def __init__(self, gh_url, version, hash) -> None:
- self.gh_url = gh_url
- self.version = version
- self.hash = hash
-
-class PluginBrowser:
- def __init__(self, plugin_path, server_instance, store_url) -> None:
- self.log = getLogger("browser")
- self.plugin_path = plugin_path
- self.store_url = store_url
- self.install_requests = {}
-
- server_instance.add_routes([
- web.post("/browser/install_plugin", self.install_plugin),
- web.get("/browser/iframe", self.redirect_to_store)
- ])
-
- def _unzip_to_plugin_dir(self, zip, name, hash):
- zip_hash = sha256(zip.getbuffer()).hexdigest()
- if zip_hash != hash:
- return False
- zip_file = ZipFile(zip)
- zip_file.extractall(self.plugin_path)
- rename(path.join(self.plugin_path, zip_file.namelist()[0]), path.join(self.plugin_path, name))
- Popen(["chown", "-R", "deck:deck", self.plugin_path])
- Popen(["chmod", "-R", "555", self.plugin_path])
- return True
-
- async def _install(self, artifact, version, hash):
- name = artifact.split("/")[-1]
- rmtree(path.join(self.plugin_path, name), ignore_errors=True)
- self.log.info(f"Installing {artifact} (Version: {version})")
- async with ClientSession() as client:
- url = f"https://github.com/{artifact}/archive/refs/tags/{version}.zip"
- self.log.debug(f"Fetching {url}")
- res = await client.get(url)
- if res.status == 200:
- self.log.debug("Got 200. Reading...")
- data = await res.read()
- self.log.debug(f"Read {len(data)} bytes")
- res_zip = BytesIO(data)
- with ProcessPoolExecutor() as executor:
- self.log.debug("Unzipping...")
- ret = await get_event_loop().run_in_executor(
- executor,
- self._unzip_to_plugin_dir,
- res_zip,
- name,
- hash
- )
- if ret:
- self.log.info(f"Installed {artifact} (Version: {version})")
- else:
- self.log.fatal(f"SHA-256 Mismatch!!!! {artifact} (Version: {version})")
- else:
- self.log.fatal(f"Could not fetch from github. {await res.text()}")
-
- async def redirect_to_store(self, request):
- return web.Response(status=302, headers={"Location": self.store_url})
-
- async def install_plugin(self, request):
- data = await request.post()
- get_event_loop().create_task(self.request_plugin_install(data["artifact"], data["version"], data["hash"]))
- return web.Response(text="Requested plugin install")
-
- async def request_plugin_install(self, artifact, version, hash):
- request_id = str(time())
- self.install_requests[request_id] = PluginInstallContext(artifact, version, hash)
- tab = await get_tab("QuickAccess")
- await tab.open_websocket()
- await tab.evaluate_js(f"addPluginInstallPrompt('{artifact}', '{version}', '{request_id}')")
-
- async def confirm_plugin_install(self, request_id):
- request = self.install_requests.pop(request_id)
- await self._install(request.gh_url, request.version, request.hash) \ No newline at end of file
diff --git a/plugin_loader/injector.py b/plugin_loader/injector.py
deleted file mode 100644
index c2157472..00000000
--- a/plugin_loader/injector.py
+++ /dev/null
@@ -1,97 +0,0 @@
-#Injector code from https://github.com/SteamDeckHomebrew/steamdeck-ui-inject. More info on how it works there.
-
-from aiohttp import ClientSession
-from logging import debug, getLogger
-from asyncio import sleep
-from traceback import format_exc
-
-BASE_ADDRESS = "http://localhost:8080"
-
-logger = getLogger("Injector")
-
-class Tab:
- def __init__(self, res) -> None:
- self.title = res["title"]
- self.id = res["id"]
- self.ws_url = res["webSocketDebuggerUrl"]
-
- self.websocket = None
- self.client = None
-
- async def open_websocket(self):
- self.client = ClientSession()
- self.websocket = await self.client.ws_connect(self.ws_url)
-
- async def listen_for_message(self):
- async for message in self.websocket:
- yield message
-
- async def _send_devtools_cmd(self, dc, receive=True):
- if self.websocket:
- await self.websocket.send_json(dc)
- return (await self.websocket.receive_json()) if receive else None
- raise RuntimeError("Websocket not opened")
-
- async def evaluate_js(self, js, run_async=False):
- await self.open_websocket()
- res = await self._send_devtools_cmd({
- "id": 1,
- "method": "Runtime.evaluate",
- "params": {
- "expression": js,
- "userGesture": True,
- "awaitPromise": run_async
- }
- })
- await self.client.close()
- return res
-
- async def get_steam_resource(self, url):
- res = await self.evaluate_js(f'(async function test() {{ return await (await fetch("{url}")).text() }})()', True)
- return res["result"]["result"]["value"]
-
- def __repr__(self):
- return self.title
-
-async def get_tabs():
- async with ClientSession() as web:
- res = {}
-
- while True:
- try:
- res = await web.get(f"{BASE_ADDRESS}/json")
- break
- except:
- logger.debug("Steam isn't available yet. Wait for a moment...")
- logger.debug(format_exc())
- await sleep(5)
-
- if res.status == 200:
- r = await res.json()
- return [Tab(i) for i in r]
- else:
- raise Exception(f"/json did not return 200. {await r.text()}")
-
-async def get_tab(tab_name):
- tabs = await get_tabs()
- tab = next((i for i in tabs if i.title == tab_name), None)
- if not tab:
- raise ValueError(f"Tab {tab_name} not found")
- return tab
-
-async def inject_to_tab(tab_name, js, run_async=False):
- tab = await get_tab(tab_name)
-
- return await tab.evaluate_js(js, run_async)
-
-async def tab_has_element(tab_name, element_name):
- try:
- tab = await get_tab(tab_name)
- except ValueError:
- return False
- res = await tab.evaluate_js(f"document.getElementById('{element_name}') != null", False)
-
- if not "result" in res or not "result" in res["result"] or not "value" in res["result"]["result"]:
- return False
-
- return res["result"]["result"]["value"]
diff --git a/plugin_loader/loader.py b/plugin_loader/loader.py
deleted file mode 100644
index fea5f149..00000000
--- a/plugin_loader/loader.py
+++ /dev/null
@@ -1,191 +0,0 @@
-from aiohttp import web
-from aiohttp_jinja2 import template
-from watchdog.observers.polling import PollingObserver as Observer
-from watchdog.events import FileSystemEventHandler
-from asyncio import Queue
-from os import path, listdir
-from logging import getLogger
-from time import time
-
-from injector import get_tabs, get_tab
-from plugin import PluginWrapper
-from traceback import print_exc
-
-class FileChangeHandler(FileSystemEventHandler):
- def __init__(self, queue, plugin_path) -> None:
- super().__init__()
- self.logger = getLogger("file-watcher")
- self.plugin_path = plugin_path
- self.queue = queue
-
- def on_created(self, event):
- src_path = event.src_path
- if "__pycache__" in src_path:
- return
-
- # check to make sure this isn't a directory
- if path.isdir(src_path):
- return
-
- # get the directory name of the plugin so that we can find its "main.py" and reload it; the
- # file that changed is not necessarily the one that needs to be reloaded
- self.logger.debug(f"file created: {src_path}")
- rel_path = path.relpath(src_path, path.commonprefix([self.plugin_path, src_path]))
- plugin_dir = path.split(rel_path)[0]
- main_file_path = path.join(self.plugin_path, plugin_dir, "main.py")
- self.queue.put_nowait((main_file_path, plugin_dir, True))
-
- def on_modified(self, event):
- src_path = event.src_path
- if "__pycache__" in src_path:
- return
-
- # check to make sure this isn't a directory
- if path.isdir(src_path):
- return
-
- # get the directory name of the plugin so that we can find its "main.py" and reload it; the
- # file that changed is not necessarily the one that needs to be reloaded
- self.logger.debug(f"file modified: {src_path}")
- plugin_dir = path.split(path.relpath(src_path, path.commonprefix([self.plugin_path, src_path])))[0]
- self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
-
-class Loader:
- def __init__(self, server_instance, plugin_path, loop, live_reload=False) -> None:
- self.loop = loop
- self.logger = getLogger("Loader")
- self.plugin_path = plugin_path
- self.logger.info(f"plugin_path: {self.plugin_path}")
- self.plugins = {}
- self.callsigns = {}
- self.callsign_matches = {}
- self.import_plugins()
-
- if live_reload:
- self.reload_queue = Queue()
- self.observer = Observer()
- self.observer.schedule(FileChangeHandler(self.reload_queue, plugin_path), self.plugin_path, recursive=True)
- self.observer.start()
- self.loop.create_task(self.handle_reloads())
-
- server_instance.add_routes([
- web.get("/plugins/iframe", self.plugin_iframe_route),
- web.get("/plugins/load_main/{name}", self.load_plugin_main_view),
- web.get("/plugins/plugin_resource/{name}/{path:.+}", self.handle_sub_route),
- web.get("/plugins/load_tile/{name}", self.load_plugin_tile_view),
- web.get("/steam_resource/{path:.+}", self.get_steam_resource)
- ])
-
- def import_plugin(self, file, plugin_directory, refresh=False):
- try:
- plugin = PluginWrapper(file, plugin_directory, self.plugin_path)
- if plugin.name in self.plugins:
- if not "debug" in plugin.flags and refresh:
- self.logger.info(f"Plugin {plugin.name} is already loaded and has requested to not be re-loaded")
- return
- else:
- self.plugins[plugin.name].stop()
- self.plugins.pop(plugin.name, None)
- self.callsigns.pop(self.callsign_matches[file], None)
- if plugin.passive:
- self.logger.info(f"Plugin {plugin.name} is passive")
- callsign = str(time())
- plugin.callsign = callsign
- self.plugins[plugin.name] = plugin.start()
- self.callsigns[callsign] = plugin
- self.callsign_matches[file] = callsign
- self.logger.info(f"Loaded {plugin.name}")
- except Exception as e:
- self.logger.error(f"Could not load {file}. {e}")
- print_exc()
- finally:
- if refresh:
- self.loop.create_task(self.refresh_iframe())
-
- def import_plugins(self):
- self.logger.info(f"import plugins from {self.plugin_path}")
-
- directories = [i for i in listdir(self.plugin_path) if path.isdir(path.join(self.plugin_path, i)) and path.isfile(path.join(self.plugin_path, i, "plugin.json"))]
- for directory in directories:
- self.logger.info(f"found plugin: {directory}")
- self.import_plugin(path.join(self.plugin_path, directory, "main.py"), directory)
-
- async def handle_reloads(self):
- while True:
- args = await self.reload_queue.get()
- self.import_plugin(*args)
-
- async def handle_plugin_method_call(self, callsign, method_name, **kwargs):
- if method_name.startswith("_"):
- raise RuntimeError("Tried to call private method")
- return await self.callsigns[callsign].execute_method(method_name, kwargs)
-
- async def get_steam_resource(self, request):
- tab = (await get_tabs())[0]
- try:
- return web.Response(text=await tab.get_steam_resource(f"https://steamloopback.host/{request.match_info['path']}"), content_type="text/html")
- except Exception as e:
- return web.Response(text=str(e), status=400)
-
- async def load_plugin_main_view(self, request):
- plugin = self.callsigns[request.match_info["name"]]
-
- # open up the main template
- with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.main_view_html), 'r') as template:
- template_data = template.read()
- # setup the main script, plugin, and pull in the template
- ret = f"""
- <script src="/static/library.js"></script>
- <script>const plugin_name = '{plugin.callsign}' </script>
- <base href="http://127.0.0.1:1337/plugins/plugin_resource/{plugin.callsign}/">
- {template_data}
- """
- return web.Response(text=ret, content_type="text/html")
-
- async def handle_sub_route(self, request):
- plugin = self.callsigns[request.match_info["name"]]
- route_path = request.match_info["path"]
- self.logger.info(path)
-
- ret = ""
-
- file_path = path.join(self.plugin_path, plugin.plugin_directory, route_path)
- with open(file_path, 'r') as resource_data:
- ret = resource_data.read()
-
- return web.Response(text=ret)
-
- async def load_plugin_tile_view(self, request):
- plugin = self.callsigns[request.match_info["name"]]
-
- inner_content = ""
-
- # open up the tile template (if we have one defined)
- if hasattr(plugin, "tile_view_html"):
- with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.tile_view_html), 'r') as template:
- template_data = template.read()
- inner_content = template_data
-
- # setup the default template
- ret = f"""
- <html style="height: fit-content;">
- <head>
- <link rel="stylesheet" href="/static/styles.css">
- <script src="/static/library.js"></script>
- <script>const plugin_name = '{plugin.callsign}';</script>
- </head>
- <body style="height: fit-content; display: block;">
- {inner_content}
- </body>
- <html>
- """
- return web.Response(text=ret, content_type="text/html")
-
- @template('plugin_view.html')
- async def plugin_iframe_route(self, request):
- return {"plugins": self.plugins.values()}
-
- async def refresh_iframe(self):
- tab = await get_tab("QuickAccess")
- await tab.open_websocket()
- return await tab.evaluate_js("reloadIframe()", False)
diff --git a/plugin_loader/main.py b/plugin_loader/main.py
deleted file mode 100644
index 9ed30760..00000000
--- a/plugin_loader/main.py
+++ /dev/null
@@ -1,144 +0,0 @@
-from logging import getLogger, basicConfig, INFO, DEBUG, Filter, root
-from os import getenv
-
-CONFIG = {
- "plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"),
- "server_host": getenv("SERVER_HOST", "127.0.0.1"),
- "server_port": int(getenv("SERVER_PORT", "1337")),
- "live_reload": getenv("LIVE_RELOAD", "1") == "1",
- "log_level": {"CRITICAL": 50, "ERROR": 40, "WARNING":30, "INFO": 20, "DEBUG": 10}[getenv("LOG_LEVEL", "INFO")],
- "store_url": getenv("STORE_URL", "https://plugins.deckbrew.xyz"),
- "log_base_events": getenv("LOG_BASE_EVENTS", "0")=="1"
-}
-
-class NoBaseEvents(Filter):
- def filter(self, record):
- return not "asyncio" in record.name
-
-basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s")
-for handler in root.handlers:
- if not CONFIG["log_base_events"]:
- handler.addFilter(NoBaseEvents())
-
-from aiohttp.web import Application, run_app, static
-from aiohttp_jinja2 import setup as jinja_setup
-from jinja2 import FileSystemLoader
-from os import path
-from asyncio import get_event_loop, sleep
-from json import loads, dumps
-from subprocess import Popen
-
-from loader import Loader
-from injector import inject_to_tab, get_tab, tab_has_element
-from utilities import Utilities
-from browser import PluginBrowser
-
-logger = getLogger("Main")
-from traceback import print_exc
-
-async def chown_plugin_dir(_):
- Popen(["chown", "-R", "deck:deck", CONFIG["plugin_path"]])
- Popen(["chmod", "-R", "555", CONFIG["plugin_path"]])
-
-class PluginManager:
- def __init__(self) -> None:
- self.loop = get_event_loop()
- self.web_app = Application()
- self.plugin_loader = Loader(self.web_app, CONFIG["plugin_path"], self.loop, CONFIG["live_reload"])
- self.plugin_browser = PluginBrowser(CONFIG["plugin_path"], self.web_app, CONFIG["store_url"])
- self.utilities = Utilities(self)
-
- jinja_setup(self.web_app, loader=FileSystemLoader(path.join(path.dirname(__file__), 'templates')))
- self.web_app.on_startup.append(self.inject_javascript)
- self.web_app.on_startup.append(chown_plugin_dir)
- self.web_app.add_routes([static("/static", path.join(path.dirname(__file__), 'static'))])
- self.loop.create_task(self.method_call_listener())
- self.loop.create_task(self.loader_reinjector())
-
- self.loop.set_exception_handler(self.exception_handler)
-
- def exception_handler(self, loop, context):
- if context["message"] == "Unclosed connection":
- return
- loop.default_exception_handler(context)
-
- async def loader_reinjector(self):
- finished_reinjection = False
- logger.info("Plugin loader isn't present in Steam anymore, reinjecting...")
- while True:
- await sleep(1)
- if not await tab_has_element("QuickAccess", "plugin_iframe"):
- logger.debug("Plugin loader isn't present in Steam anymore, reinjecting...")
- await self.inject_javascript()
- finished_reinjection = True
- elif finished_reinjection:
- finished_reinjection = False
- logger.info("Reinjecting successful!")
-
- self.loop.create_task(self.method_call_listener())
-
- async def inject_javascript(self, request=None):
- try:
- await inject_to_tab("QuickAccess", open(path.join(path.dirname(__file__), "static/library.js"), "r").read())
- await inject_to_tab("QuickAccess", open(path.join(path.dirname(__file__), "static/plugin_page.js"), "r").read())
- except:
- logger.info("Failed to inject JavaScript into tab")
- pass
-
- async def resolve_method_call(self, tab, call_id, response):
- try:
- r = dumps(response)
- except Exception as e:
- logger.error(response["result"])
- response["result"] = str(response["result"])
- r = response
- await tab._send_devtools_cmd({
- "id": 1,
- "method": "Runtime.evaluate",
- "params": {
- "expression": f"resolveMethodCall({call_id}, {r})",
- "userGesture": True
- }
- }, receive=False)
-
- async def handle_method_call(self, method, tab):
- res = {}
- try:
- if method["method"] == "plugin_method":
- res["result"] = await self.plugin_loader.handle_plugin_method_call(
- method["args"]["plugin_name"],
- method["args"]["method_name"],
- **method["args"]["args"]
- )
- res["success"] = True
- else:
- r = await self.utilities.util_methods[method["method"]](**method["args"])
- res["result"] = r
- res["success"] = True
- except Exception as e:
- res["result"] = str(e)
- res["success"] = False
- finally:
- await self.resolve_method_call(tab, method["id"], res)
-
- async def method_call_listener(self):
- while True:
- try:
- tab = await get_tab("QuickAccess")
- break
- except:
- await sleep(1)
- await tab.open_websocket()
- await tab._send_devtools_cmd({"id": 1, "method": "Runtime.discardConsoleEntries"})
- await tab._send_devtools_cmd({"id": 1, "method": "Runtime.enable"})
- async for message in tab.listen_for_message():
- data = message.json()
- if not "id" in data and data["method"] == "Runtime.consoleAPICalled" and data["params"]["type"] == "debug":
- method = loads(data["params"]["args"][0]["value"])
- self.loop.create_task(self.handle_method_call(method, tab))
-
- def run(self):
- return run_app(self.web_app, host=CONFIG["server_host"], port=CONFIG["server_port"], loop=self.loop, access_log=None)
-
-if __name__ == "__main__":
- PluginManager().run() \ No newline at end of file
diff --git a/plugin_loader/plugin.py b/plugin_loader/plugin.py
deleted file mode 100644
index 30626058..00000000
--- a/plugin_loader/plugin.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from importlib.util import spec_from_file_location, module_from_spec
-from asyncio import get_event_loop, new_event_loop, set_event_loop, start_unix_server, open_unix_connection, sleep, Lock
-from os import path, setuid
-from json import loads, dumps, load
-from time import time
-from multiprocessing import Process
-from signal import signal, SIGINT
-from sys import exit
-
-class PluginWrapper:
- def __init__(self, file, plugin_directory, plugin_path) -> None:
- self.file = file
- self.plugin_directory = plugin_directory
- self.reader = None
- self.writer = None
- self.socket_addr = f"/tmp/plugin_socket_{time()}"
- self.method_call_lock = Lock()
-
- json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r"))
-
- self.name = json["name"]
- self.author = json["author"]
- self.main_view_html = json["main_view_html"]
- self.tile_view_html = json["tile_view_html"] if "tile_view_html" in json else ""
- self.flags = json["flags"]
-
- self.passive = not path.isfile(self.file)
-
- def _init(self):
- signal(SIGINT, lambda s, f: exit(0))
-
- set_event_loop(new_event_loop())
- if self.passive:
- return
- setuid(0 if "root" in self.flags else 1000)
- spec = spec_from_file_location("_", self.file)
- module = module_from_spec(spec)
- spec.loader.exec_module(module)
- self.Plugin = module.Plugin
-
- if hasattr(self.Plugin, "_main"):
- get_event_loop().create_task(self.Plugin._main(self.Plugin))
- get_event_loop().create_task(self._setup_socket())
- get_event_loop().run_forever()
-
- async def _setup_socket(self):
- self.socket = await start_unix_server(self._listen_for_method_call, path=self.socket_addr)
-
- async def _listen_for_method_call(self, reader, writer):
- while True:
- data = loads((await reader.readline()).decode("utf-8"))
- if "stop" in data:
- get_event_loop().stop()
- while get_event_loop().is_running():
- await sleep(0)
- get_event_loop().close()
- return
- d = {"res": None, "success": True}
- try:
- d["res"] = await getattr(self.Plugin, data["method"])(self.Plugin, **data["args"])
- except Exception as e:
- d["res"] = str(e)
- d["success"] = False
- finally:
- writer.write((dumps(d)+"\n").encode("utf-8"))
- await writer.drain()
-
- async def _open_socket_if_not_exists(self):
- if not self.reader:
- while True:
- try:
- self.reader, self.writer = await open_unix_connection(self.socket_addr)
- break
- except:
- await sleep(0)
-
- def start(self):
- if self.passive:
- return self
- Process(target=self._init).start()
- return self
-
- def stop(self):
- if self.passive:
- return
- async def _(self):
- await self._open_socket_if_not_exists()
- self.writer.write((dumps({"stop": True})+"\n").encode("utf-8"))
- await self.writer.drain()
- self.writer.close()
- get_event_loop().create_task(_(self))
-
- async def execute_method(self, method_name, kwargs):
- if self.passive:
- raise RuntimeError("This plugin is passive (aka does not implement main.py)")
- async with self.method_call_lock:
- await self._open_socket_if_not_exists()
- self.writer.write(
- (dumps({"method": method_name, "args": kwargs})+"\n").encode("utf-8"))
- await self.writer.drain()
- res = loads((await self.reader.readline()).decode("utf-8"))
- if not res["success"]:
- raise Exception(res["res"])
- return res["res"]
diff --git a/plugin_loader/static/library.js b/plugin_loader/static/library.js
deleted file mode 100644
index 744cc77f..00000000
--- a/plugin_loader/static/library.js
+++ /dev/null
@@ -1,71 +0,0 @@
-class PluginEventTarget extends EventTarget { }
-method_call_ev_target = new PluginEventTarget();
-
-window.addEventListener("message", function(evt) {
- let ev = new Event(evt.data.call_id);
- ev.data = evt.data.result;
- method_call_ev_target.dispatchEvent(ev);
-}, false);
-
-async function call_server_method(method_name, arg_object={}) {
- let id = `${uuidv4()}`;
- console.debug(JSON.stringify({
- "id": id,
- "method": method_name,
- "args": arg_object
- }));
- return new Promise((resolve, reject) => {
- method_call_ev_target.addEventListener(`${id}`, function (event) {
- if (event.data.success) resolve(event.data.result);
- else reject(event.data.result);
- });
- });
-}
-
-// Source: https://stackoverflow.com/a/2117523 Thanks!
-function uuidv4() {
- return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
- (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
- );
-}
-
-async function fetch_nocors(url, request={}) {
- let args = { method: "POST", headers: {}, body: "" };
- request = {...args, ...request};
- request.url = url;
- request.data = request.body;
- delete request.body; //maintain api-compatibility with fetch
- return await call_server_method("http_request", request);
-}
-
-async function call_plugin_method(method_name, arg_object={}) {
- if (plugin_name == undefined)
- throw new Error("Plugin methods can only be called from inside plugins (duh)");
- return await call_server_method("plugin_method", {
- 'plugin_name': plugin_name,
- 'method_name': method_name,
- 'args': arg_object
- });
-}
-
-async function execute_in_tab(tab, run_async, code) {
- return await call_server_method("execute_in_tab", {
- 'tab': tab,
- 'run_async': run_async,
- 'code': code
- });
-}
-
-async function inject_css_into_tab(tab, style) {
- return await call_server_method("inject_css_into_tab", {
- 'tab': tab,
- 'style': style
- });
-}
-
-async function remove_css_from_tab(tab, css_id) {
- return await call_server_method("remove_css_from_tab", {
- 'tab': tab,
- 'css_id': css_id
- });
-} \ No newline at end of file
diff --git a/plugin_loader/static/plugin_page.js b/plugin_loader/static/plugin_page.js
deleted file mode 100644
index 0531f04e..00000000
--- a/plugin_loader/static/plugin_page.js
+++ /dev/null
@@ -1,98 +0,0 @@
-function reloadIframe() {
- document.getElementById("plugin_iframe").contentWindow.location.href = "http://127.0.0.1:1337/plugins/iframe";
-}
-
-function resolveMethodCall(call_id, result) {
- let iframe = document.getElementById("plugin_iframe").contentWindow;
- iframe.postMessage({'call_id': call_id, 'result': result}, "http://127.0.0.1:1337");
-}
-
-function installPlugin(request_id) {
- let id = `${new Date().getTime()}`;
- console.debug(JSON.stringify({
- "id": id,
- "method": "confirm_plugin_install",
- "args": {"request_id": request_id}
- }));
- document.getElementById('plugin_install_list').removeChild(document.getElementById(`plugin_install_prompt_${request_id}`));
-}
-
-function addPluginInstallPrompt(artifact, version, request_id) {
- let text = `
- <link rel="stylesheet" href="/static/styles.css">
-
- <div id="plugin_install_prompt_${request_id}" style="background-color: #0c131b; display: block; border: 1px solid #22262f; box-shadow: 0px 0px 8px #202020; width: calc(100% - 50px); padding: 0px 10px 10px 10px;">
- <h3>Install Plugin?</h3>
- <p style="font-size: 12px;">
- ${artifact}
- Version: ${version}
- </p>
- <button type="button" tabindex="0" class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable"
- onclick="installPlugin('${request_id}')">
- Install
- </button>
- <p style="margin: 2px;"></p>
- <button type="button" tabindex="0" class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable"
- onclick="document.getElementById('plugin_install_list').removeChild(document.getElementById('plugin_install_prompt_${request_id}'))">
- Cancel
- </button>
- </div>
- `;
- document.getElementById('plugin_install_list').innerHTML = text;
-
- execute_in_tab('SP', false, 'FocusNavController.DispatchVirtualButtonClick(28)')
-}
-
-(function () {
- const PLUGIN_ICON = `
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plugin" viewBox="0 0 16 16">
- <path fill-rule="evenodd" d="M1 8a7 7 0 1 1 2.898 5.673c-.167-.121-.216-.406-.002-.62l1.8-1.8a3.5 3.5 0 0 0
- 4.572-.328l1.414-1.415a.5.5 0 0 0 0-.707l-.707-.707 1.559-1.563a.5.5 0 1 0-.708-.706l-1.559 1.562-1.414-1.414
- 1.56-1.562a.5.5 0 1 0-.707-.706l-1.56 1.56-.707-.706a.5.5 0 0 0-.707 0L5.318 5.975a3.5 3.5 0 0 0-.328
- 4.571l-1.8 1.8c-.58.58-.62 1.6.121 2.137A8 8 0 1 0 0 8a.5.5 0 0 0 1 0Z"/>
- </svg>
- `;
-
- function createTitle(text) {
- return `<div id="plugin_title" class="quickaccessmenu_Title_34nl5">${text}</div>`;
- }
-
- function createPluginList() {
- let pages = document.getElementsByClassName("quickaccessmenu_AllTabContents_2yKG4 quickaccessmenu_Down_3rR0o")[0];
- let pluginPage = pages.children[pages.children.length - 1];
- pluginPage.innerHTML = createTitle("Plugins");
-
- pluginPage.innerHTML += `<div id="plugin_install_list" style="position: fixed; height: 100%; z-index: 99; transform: translate(5%, 0);"></div>`
-
- pluginPage.innerHTML += `<iframe id="plugin_iframe" style="border: none; width: 100%; height: 100%;" src="http://127.0.0.1:1337/plugins/iframe"></iframe>`;
- }
-
- function inject() {
- let tabs = document.getElementsByClassName("quickaccessmenu_TabContentColumn_2z5NL Panel Focusable")[0];
- tabs.children[tabs.children.length - 1].innerHTML = PLUGIN_ICON;
-
- createPluginList();
- }
-
- let injector = setInterval(function () {
- if (document.hasFocus()) {
- inject();
- document.getElementById("plugin_title").onclick = function() {
- reloadIframe();
- document.getElementById("plugin_title").innerText = "Plugins";
- }
- window.onmessage = function(ev) {
- let title = ev.data;
- if (title.startsWith("PLUGIN_LOADER__")) {
- document.getElementById("plugin_title").innerHTML = `
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-arrow-left-square-fill" viewBox="0 0 16 16">
- <path d="M16 14a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v12zm-4.5-6.5H5.707l2.147-2.146a.5.5 0 1 0-.708-.708l-3 3a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708-.708L5.707 8.5H11.5a.5.5 0 0 0 0-1z"/>
- </svg>
- ${title.replace("PLUGIN_LOADER__", "")}
- `;
- }
- }
- clearInterval(injector);
- }
- }, 100);
-})(); \ No newline at end of file
diff --git a/plugin_loader/static/styles.css b/plugin_loader/static/styles.css
deleted file mode 100644
index 8d27a538..00000000
--- a/plugin_loader/static/styles.css
+++ /dev/null
@@ -1,3 +0,0 @@
-@import url("/steam_resource/css/2.css");
-@import url("/steam_resource/css/39.css");
-@import url("/steam_resource/css/library.css");
diff --git a/plugin_loader/templates/plugin_view.html b/plugin_loader/templates/plugin_view.html
deleted file mode 100644
index 9d7ba1bc..00000000
--- a/plugin_loader/templates/plugin_view.html
+++ /dev/null
@@ -1,76 +0,0 @@
-<link rel="stylesheet" href="/static/styles.css">
-<script>
- const tile_iframes = [];
- window.addEventListener("message", function (evt) {
- tile_iframes.forEach(iframe => {
- iframe.contentWindow.postMessage(evt.data, "http://127.0.0.1:1337");
- });
- }, false);
-
- function loadPlugin(callsign, name) {
- this.parent.postMessage("PLUGIN_LOADER__"+name, "https://steamloopback.host");
- location.href = `/plugins/load_main/${callsign}`;
- }
-</script>
-
-{% if not plugins|length %}
- <div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
- <div class="quickaccesscontrols_EmptyNotifications_3ZjbM" style="padding-top:7px;">
- No plugins installed
- </div>
- </div>
-{% endif %}
-
-<div class="quickaccessmenu_TabGroupPanel_1QO7b Panel Focusable">
- {% for plugin in plugins %}
- {% if plugin.tile_view_html|length %}
- <div class="quickaccesscontrols_PanelSectionRow_26R5w">
- <div onclick="loadPlugin('{{ plugin.callsign }}', '{{ plugin.name }}')"
- class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable"
- style="--indent-level:0; margin: 0px; padding: 0px; padding-top: 8px;">
- <iframe id="tile_view_iframe_{{ plugin.callsign }}"
- scrolling="no" marginwidth="0" marginheight="0"
- hspace="0" vspace="0" frameborder="0"
- style="border-radius: 2px;"
- src="/plugins/load_tile/{{ plugin.callsign }}">
- </iframe>
- <script>
- (function() {
- let iframe = document.getElementById("tile_view_iframe_{{ plugin.callsign }}");
- tile_iframes.push(document.getElementById("tile_view_iframe_{{ plugin.callsign }}"));
-
- iframe.onload = function() {
- let html = iframe.contentWindow.document.children[0];
- let last_height = 0;
-
- setInterval(function() {
- let height = iframe.contentWindow.document.children[0].scrollHeight;
- if (height != last_height) {
- iframe.height = height + "px";
- last_height = height;
- }
- }, 100);
-
- iframe.contentWindow.document.body.onclick = function () {
- loadPlugin('{{ plugin.callsign }}', '{{ plugin.name }}');
- };
- }
- })();
- </script>
- </div>
- </div>
- {% else %}
- <div class="quickaccesscontrols_PanelSectionRow_26R5w">
- <div onclick="loadPlugin('{{ plugin.callsign }}', '{{ plugin.name }}')"
- class="basicdialog_Field_ugL9c basicdialog_WithChildrenBelow_1RjOd basicdialog_InlineWrapShiftsChildrenBelow_3a6QZ basicdialog_ExtraPaddingOnChildrenBelow_2-owv basicdialog_StandardPadding_1HrfN basicdialog_HighlightOnFocus_1xh2W Panel Focusable"
- style="--indent-level:0; margin: 0px; padding: 0px; padding-top: 8px;">
- <div class="basicdialog_FieldChildren_279n8">
- <button type="button" tabindex="0"
- class="DialogButton _DialogLayout Secondary basicdialog_Button_1Ievp Focusable">{{ plugin.name }}
- </button>
- </div>
- </div>
- </div>
- {% endif %}
- {% endfor %}
-</div>
diff --git a/plugin_loader/utilities.py b/plugin_loader/utilities.py
deleted file mode 100644
index 39f9ca55..00000000
--- a/plugin_loader/utilities.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from aiohttp import ClientSession
-from injector import inject_to_tab
-import uuid
-
-class Utilities:
- def __init__(self, context) -> None:
- self.context = context
- self.util_methods = {
- "ping": self.ping,
- "http_request": self.http_request,
- "confirm_plugin_install": self.confirm_plugin_install,
- "execute_in_tab": self.execute_in_tab,
- "inject_css_into_tab": self.inject_css_into_tab,
- "remove_css_from_tab": self.remove_css_from_tab
- }
-
- async def confirm_plugin_install(self, request_id):
- return await self.context.plugin_browser.confirm_plugin_install(request_id)
-
- async def http_request(self, method="", url="", **kwargs):
- async with ClientSession() as web:
- async with web.request(method, url, **kwargs) as res:
- return {
- "status": res.status,
- "headers": dict(res.headers),
- "body": await res.text()
- }
-
- async def ping(self, **kwargs):
- return "pong"
-
- async def execute_in_tab(self, tab, run_async, code):
- try:
- result = await inject_to_tab(tab, code, run_async)
- if "exceptionDetails" in result["result"]:
- return {
- "success": False,
- "result": result["result"]
- }
-
- return {
- "success": True,
- "result" : result["result"]["result"].get("value")
- }
- except Exception as e:
- return {
- "success": False,
- "result": e
- }
-
- async def inject_css_into_tab(self, tab, style):
- try:
- css_id = str(uuid.uuid4())
-
- result = await inject_to_tab(tab,
- f"""
- (function() {{
- const style = document.createElement('style');
- style.id = "{css_id}";
- document.head.append(style);
- style.textContent = `{style}`;
- }})()
- """, False)
-
- if "exceptionDetails" in result["result"]:
- return {
- "success": False,
- "result": result["result"]
- }
-
- return {
- "success": True,
- "result" : css_id
- }
- except Exception as e:
- return {
- "success": False,
- "result": e
- }
-
- async def remove_css_from_tab(self, tab, css_id):
- try:
- result = await inject_to_tab(tab,
- f"""
- (function() {{
- let style = document.getElementById("{css_id}");
-
- if (style.nodeName.toLowerCase() == 'style')
- style.parentNode.removeChild(style);
- }})()
- """, False)
-
- if "exceptionDetails" in result["result"]:
- return {
- "success": False,
- "result": result
- }
-
- return {
- "success": True
- }
- except Exception as e:
- return {
- "success": False,
- "result": e
- }