diff --git a/packages/python-client/.gitignore b/packages/python-client/.gitignore new file mode 100644 index 000000000..1d17dae13 --- /dev/null +++ b/packages/python-client/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/packages/python-client/.vscode/launch.json b/packages/python-client/.vscode/launch.json new file mode 100644 index 000000000..3b85ada7c --- /dev/null +++ b/packages/python-client/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Python: Current File", + "type": "python", + "request": "launch", + "program": "${workspaceFolder}/test.py", + "console": "integratedTerminal", + "justMyCode": true + } + ] +} \ No newline at end of file diff --git a/packages/python-client/plugin_remote.py b/packages/python-client/plugin_remote.py new file mode 120000 index 000000000..66db592c7 --- /dev/null +++ b/packages/python-client/plugin_remote.py @@ -0,0 +1 @@ +../../server/python/plugin_remote.py \ No newline at end of file diff --git a/packages/python-client/requirements.txt b/packages/python-client/requirements.txt new file mode 100644 index 000000000..517568526 --- /dev/null +++ b/packages/python-client/requirements.txt @@ -0,0 +1,3 @@ +python-engineio[asyncio_client] +aiohttp +aiodns diff --git a/packages/python-client/rpc.py b/packages/python-client/rpc.py new file mode 120000 index 000000000..1c45121b0 --- /dev/null +++ b/packages/python-client/rpc.py @@ -0,0 +1 @@ +../../server/python/rpc.py \ No newline at end of file diff --git a/packages/python-client/rpc_reader.py b/packages/python-client/rpc_reader.py new file mode 120000 index 000000000..8d1d0a0a5 --- /dev/null +++ b/packages/python-client/rpc_reader.py @@ -0,0 +1 @@ +../../server/python/rpc_reader.py \ No newline at end of file diff --git a/packages/python-client/scrypted_python b/packages/python-client/scrypted_python new file mode 120000 index 000000000..2da818a5e --- /dev/null +++ b/packages/python-client/scrypted_python @@ -0,0 +1 @@ +../../sdk/types/scrypted_python \ No newline at end of file diff --git a/packages/python-client/test.py b/packages/python-client/test.py new file mode 100644 index 000000000..a10e3149d --- /dev/null +++ b/packages/python-client/test.py @@ -0,0 +1,101 @@ +import asyncio +import engineio +import os +import aiohttp +import rpc_reader +import plugin_remote +from plugin_remote import SystemManager, MediaManager +from scrypted_python.scrypted_sdk import ScryptedStatic + +class EioRpcTransport(rpc_reader.RpcTransport): + message_queue = asyncio.Queue() + + def __init__(self, loop: asyncio.AbstractEventLoop): + super().__init__() + self.eio = engineio.AsyncClient(ssl_verify=False) + self.loop = loop + + @self.eio.on("message") + def on_message(data): + self.message_queue.put_nowait(data) + + async def read(self): + return await self.message_queue.get() + + def writeBuffer(self, buffer, reject): + self.writeBuffer(buffer, reject) + + def writeJSON(self, json, reject): + async def send(): + try: + await self.eio.send(json) + except Exception as e: + reject(e) + asyncio.run_coroutine_threadsafe(send(), self.loop) + + +async def connect_scrypted_client( + base_url: str, username: str, password: str, plugin_id: str = "@scrypted/core" +): + login_url = f"{base_url}/login" + login_body = { + "username": username, + "password": password, + } + + async with aiohttp.ClientSession() as session: + async with session.post( + login_url, verify_ssl=False, json=login_body + ) as response: + login_response = await response.json() + + headers = {"Authorization": login_response["authorization"]} + + loop = asyncio.get_event_loop() + transport = EioRpcTransport(loop) + + await transport.eio.connect( + base_url, + headers=headers, + engineio_path=f"/endpoint/{plugin_id}/engine.io/api/", + ) + + ret = asyncio.Future[ScryptedStatic](loop=loop) + peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(loop, transport) + peer.params['print'] = print + def callback(api, pluginId, hostInfo): + remote = plugin_remote.PluginRemote(peer, api, pluginId, hostInfo, loop) + wrapped = remote.setSystemState + async def remoteSetSystemState(systemState): + await wrapped(systemState) + async def resolve(): + sdk = ScryptedStatic() + sdk.api = api + sdk.remote = remote + sdk.systemManager = SystemManager(api, remote.systemState) + sdk.mediaManager = MediaManager(await api.getMediaManager()) + ret.set_result(sdk) + asyncio.run_coroutine_threadsafe(resolve(), loop) + remote.setSystemState = remoteSetSystemState + return remote + peer.params['getRemote'] = callback + asyncio.run_coroutine_threadsafe(peerReadLoop(), loop) + + sdk = await ret + return sdk + +async def main(): + sdk = await connect_scrypted_client( + "https://localhost:10443", + os.environ["SCRYPTED_USERNAME"], + os.environ["SCRYPTED_PASSWORD"], + ) + + for id in sdk.systemManager.getSystemState(): + device = sdk.systemManager.getDeviceById(id) + print(device.name) + os._exit(0) + +loop = asyncio.new_event_loop() +asyncio.run_coroutine_threadsafe(main(), loop) +loop.run_forever()