mirror of
https://github.com/SteamDeckHomebrew/decky-loader.git
synced 2026-07-11 11:12:00 +00:00
Work on react frontend loader
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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)
|
||||
@@ -0,0 +1,95 @@
|
||||
#Injector code from https://github.com/SteamDeckHomebrew/steamdeck-ui-inject. More info on how it works there.
|
||||
|
||||
from asyncio import sleep
|
||||
from logging import debug, getLogger
|
||||
from traceback import format_exc
|
||||
|
||||
from aiohttp import ClientSession
|
||||
|
||||
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
|
||||
|
||||
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_global_var(tab_name, var_name):
|
||||
try:
|
||||
tab = await get_tab(tab_name)
|
||||
except ValueError:
|
||||
return False
|
||||
res = await tab.evaluate_js(f"window['{var_name}'] !== null && window['{var_name}'] !== undefined", 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"]
|
||||
@@ -0,0 +1,162 @@
|
||||
from asyncio import Queue
|
||||
from logging import getLogger
|
||||
from os import listdir, path
|
||||
from pathlib import Path
|
||||
from time import time
|
||||
from traceback import print_exc
|
||||
|
||||
from aiohttp import web
|
||||
from aiohttp_jinja2 import template
|
||||
from genericpath import exists
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
from watchdog.observers.polling import PollingObserver as Observer
|
||||
|
||||
from injector import inject_to_tab
|
||||
from plugin import PluginWrapper
|
||||
|
||||
|
||||
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 maybe_reload(self, src_path):
|
||||
plugin_dir = Path(path.relpath(src_path, self.plugin_path)).parts[0]
|
||||
self.logger.info(path.join(self.plugin_path, plugin_dir, "plugin.json"))
|
||||
if exists(path.join(self.plugin_path, plugin_dir, "plugin.json")):
|
||||
self.queue.put_nowait((path.join(self.plugin_path, plugin_dir, "main.py"), plugin_dir, True))
|
||||
|
||||
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}")
|
||||
self.maybe_reload(src_path)
|
||||
|
||||
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}")
|
||||
self.maybe_reload(src_path)
|
||||
|
||||
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.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", self.handle_plugins),
|
||||
web.get("/plugins/{plugin_name}/frontend_bundle", self.handle_frontend_bundle),
|
||||
web.post("/plugins/{plugin_name}/methods/{method_name}", self.handle_plugin_method_call),
|
||||
web.post("/methods/{method_name}", self.handle_server_method_call)
|
||||
])
|
||||
|
||||
def handle_plugins(self, request):
|
||||
plugins = list(map(lambda kv: dict([("name", kv[0])]), self.plugins.items()))
|
||||
return web.json_response(plugins)
|
||||
|
||||
def handle_frontend_bundle(self, request):
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
|
||||
with open(path.join(self.plugin_path, plugin.plugin_directory, plugin.frontend_bundle), 'r') as bundle:
|
||||
return web.Response(text=bundle.read(), content_type="application/javascript")
|
||||
|
||||
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)
|
||||
if plugin.passive:
|
||||
self.logger.info(f"Plugin {plugin.name} is passive")
|
||||
self.plugins[plugin.name] = plugin.start()
|
||||
self.logger.info(f"Loaded {plugin.name}")
|
||||
if refresh:
|
||||
self.loop.create_task(self.reload_frontend_plugin(plugin.name))
|
||||
except Exception as e:
|
||||
self.logger.error(f"Could not load {file}. {e}")
|
||||
print_exc()
|
||||
|
||||
async def reload_frontend_plugin(self, name):
|
||||
await inject_to_tab("SP", f"window.DeckyPluginLoader?.loadPlugin('{name}')")
|
||||
|
||||
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_server_method_call(self, request):
|
||||
method_name = request.match_info["method_name"]
|
||||
method_info = await request.json()
|
||||
args = method_info["args"]
|
||||
|
||||
res = {}
|
||||
try:
|
||||
r = await self.utilities.util_methods[method_name](**args)
|
||||
res["result"] = r
|
||||
res["success"] = True
|
||||
except Exception as e:
|
||||
res["result"] = str(e)
|
||||
res["success"] = False
|
||||
|
||||
return web.json_response(res)
|
||||
|
||||
async def handle_plugin_method_call(self, request):
|
||||
res = {}
|
||||
plugin = self.plugins[request.match_info["plugin_name"]]
|
||||
method_name = request.match_info["method_name"]
|
||||
|
||||
method_info = await request.json()
|
||||
args = method_info["args"]
|
||||
|
||||
try:
|
||||
if method_name.startswith("_"):
|
||||
raise RuntimeError("Tried to call private method")
|
||||
|
||||
res["result"] = await plugin.execute_method(method_name, args)
|
||||
res["success"] = True
|
||||
except Exception as e:
|
||||
res["result"] = str(e)
|
||||
res["success"] = False
|
||||
|
||||
return web.json_response(res)
|
||||
@@ -0,0 +1,85 @@
|
||||
from logging import DEBUG, INFO, basicConfig, getLogger
|
||||
from os import getenv
|
||||
|
||||
CONFIG = {
|
||||
"plugin_path": getenv("PLUGIN_PATH", "/home/deck/homebrew/plugins"),
|
||||
"chown_plugin_path": getenv("CHOWN_PLUGIN_PATH", "1") == "1",
|
||||
"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://sdh.tzatzi.me/browse")
|
||||
}
|
||||
|
||||
basicConfig(level=CONFIG["log_level"], format="[%(module)s][%(levelname)s]: %(message)s")
|
||||
|
||||
from asyncio import get_event_loop, sleep
|
||||
from json import dumps, loads
|
||||
from os import path
|
||||
from subprocess import Popen
|
||||
|
||||
import aiohttp_cors
|
||||
from aiohttp.web import Application, run_app, static
|
||||
from aiohttp_jinja2 import setup as jinja_setup
|
||||
from jinja2 import FileSystemLoader
|
||||
|
||||
from browser import PluginBrowser
|
||||
from injector import get_tab, inject_to_tab, tab_has_global_var
|
||||
from loader import Loader
|
||||
from utilities import Utilities
|
||||
|
||||
logger = getLogger("Main")
|
||||
|
||||
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.cors = aiohttp_cors.setup(self.web_app, defaults={
|
||||
"https://steamloopback.host": aiohttp_cors.ResourceOptions(expose_headers="*",
|
||||
allow_headers="*")
|
||||
})
|
||||
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)
|
||||
self.web_app.on_startup.append(self.inject_javascript)
|
||||
|
||||
if CONFIG["chown_plugin_path"] == True:
|
||||
self.web_app.on_startup.append(chown_plugin_dir)
|
||||
|
||||
self.loop.create_task(self.loader_reinjector())
|
||||
|
||||
self.loop.set_exception_handler(self.exception_handler)
|
||||
|
||||
for route in list(self.web_app.router.routes()):
|
||||
self.cors.add(route)
|
||||
|
||||
def exception_handler(self, loop, context):
|
||||
if context["message"] == "Unclosed connection":
|
||||
return
|
||||
loop.default_exception_handler(context)
|
||||
|
||||
async def loader_reinjector(self):
|
||||
while True:
|
||||
await sleep(1)
|
||||
if not await tab_has_global_var("SP", "DeckyPluginLoader"):
|
||||
logger.info("Plugin loader isn't present in Steam anymore, reinjecting...")
|
||||
await self.inject_javascript()
|
||||
|
||||
async def inject_javascript(self, request=None):
|
||||
try:
|
||||
await inject_to_tab("SP", open(path.join(path.dirname(__file__), "./static/plugin-loader.iife.js"), "r").read(), True)
|
||||
except:
|
||||
logger.info("Failed to inject JavaScript into tab")
|
||||
pass
|
||||
|
||||
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()
|
||||
@@ -0,0 +1,109 @@
|
||||
from asyncio import (Lock, get_event_loop, new_event_loop,
|
||||
open_unix_connection, set_event_loop, sleep,
|
||||
start_unix_server)
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from importlib.util import module_from_spec, spec_from_file_location
|
||||
from json import dumps, load, loads
|
||||
from multiprocessing import Process
|
||||
from os import path, setuid
|
||||
from signal import SIGINT, signal
|
||||
from sys import exit
|
||||
from time import time
|
||||
|
||||
from injector import inject_to_tab
|
||||
|
||||
|
||||
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.frontend_bundle = json["frontend_bundle"]
|
||||
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"]
|
||||
@@ -0,0 +1,106 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user