mirror of
https://github.com/koush/scrypted.git
synced 2026-02-03 14:13:28 +00:00
Merge branch 'main' of github.com:koush/scrypted
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
.DS_Store
|
||||
__pycache__
|
||||
venv
|
||||
.venv
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
from contextlib import nullcontext
|
||||
import engineio
|
||||
import os
|
||||
import aiohttp
|
||||
import rpc_reader
|
||||
import plugin_remote
|
||||
from plugin_remote import SystemManager, MediaManager
|
||||
from plugin_remote import DeviceManager, SystemManager, MediaManager
|
||||
from scrypted_python.scrypted_sdk import ScryptedStatic
|
||||
|
||||
class EioRpcTransport(rpc_reader.RpcTransport):
|
||||
@@ -35,57 +38,62 @@ class EioRpcTransport(rpc_reader.RpcTransport):
|
||||
|
||||
|
||||
async def connect_scrypted_client(
|
||||
base_url: str, username: str, password: str, plugin_id: str = "@scrypted/core"
|
||||
):
|
||||
transport: EioRpcTransport, base_url: str, username: str, password: str, plugin_id: str = "@scrypted/core", session: aiohttp.ClientSession | None = None
|
||||
) -> ScryptedStatic:
|
||||
login_url = f"{base_url}/login"
|
||||
login_body = {
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(
|
||||
if session:
|
||||
cm = nullcontext(session)
|
||||
else:
|
||||
cm = aiohttp.ClientSession()
|
||||
|
||||
async with cm 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"]}
|
||||
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=transport.loop)
|
||||
peer, peerReadLoop = await rpc_reader.prepare_peer_readloop(transport.loop, transport)
|
||||
peer.params['print'] = print
|
||||
def callback(api, pluginId, hostInfo):
|
||||
remote = plugin_remote.PluginRemote(peer, api, pluginId, hostInfo, transport.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.deviceManager = DeviceManager(remote.nativeIds, sdk.systemManager)
|
||||
sdk.mediaManager = MediaManager(await api.getMediaManager())
|
||||
ret.set_result(sdk)
|
||||
asyncio.run_coroutine_threadsafe(resolve(), transport.loop)
|
||||
remote.setSystemState = remoteSetSystemState
|
||||
return remote
|
||||
peer.params['getRemote'] = callback
|
||||
asyncio.run_coroutine_threadsafe(peerReadLoop(), transport.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
|
||||
sdk = await ret
|
||||
return sdk
|
||||
|
||||
async def main():
|
||||
transport = EioRpcTransport(asyncio.get_event_loop())
|
||||
sdk = await connect_scrypted_client(
|
||||
transport,
|
||||
"https://localhost:10443",
|
||||
os.environ["SCRYPTED_USERNAME"],
|
||||
os.environ["SCRYPTED_PASSWORD"],
|
||||
@@ -94,6 +102,8 @@ async def main():
|
||||
for id in sdk.systemManager.getSystemState():
|
||||
device = sdk.systemManager.getDeviceById(id)
|
||||
print(device.name)
|
||||
|
||||
await transport.eio.disconnect()
|
||||
os._exit(0)
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
from typing import AbstractSet, Any, Callable
|
||||
|
||||
from enum import Enum
|
||||
from typing import AbstractSet, Any, Callable, Literal
|
||||
try:
|
||||
from typing import TypedDict
|
||||
except:
|
||||
@@ -7,6 +9,7 @@ except:
|
||||
|
||||
SettingValue = str
|
||||
EventListener = Callable[[Any, Any, Any], None]
|
||||
VibratePattern = int | list[int]
|
||||
|
||||
|
||||
class Console:
|
||||
@@ -36,3 +39,51 @@ class MediaObject:
|
||||
|
||||
class RTCSessionDescriptionInit(TypedDict):
|
||||
pass
|
||||
|
||||
|
||||
class NotificationAction(TypedDict, total=False):
|
||||
|
||||
action: str
|
||||
title: str
|
||||
icon: str # optional
|
||||
|
||||
|
||||
class NotificationDirection(str, Enum):
|
||||
|
||||
auto = "auto"
|
||||
ltr = "ltr"
|
||||
rtl = "rtl"
|
||||
|
||||
|
||||
class WebSocket:
|
||||
|
||||
CLOSED: int
|
||||
CLOSING: int
|
||||
CONNECTING: int
|
||||
EventTarget: dict
|
||||
OPEN: int
|
||||
binaryType: Literal["blob", "arraybuffer"]
|
||||
bufferedAmount: int
|
||||
extensions: str
|
||||
onclose: Callable[[dict], None]
|
||||
onerror: Callable[[dict], None]
|
||||
onmessage: Callable[[dict], None]
|
||||
onopen: Callable[[dict], None]
|
||||
protocol: str
|
||||
readyState: int
|
||||
url: str
|
||||
|
||||
def addEventListener(self, type: str, listener: Callable[[dict], None], options: dict = None) -> None:
|
||||
pass
|
||||
|
||||
def close(self, code: int = None, reason: str = None) -> None:
|
||||
pass
|
||||
|
||||
def dispatchEvent(self, event: dict) -> bool:
|
||||
pass
|
||||
|
||||
def removeEventListener(self, type: str, listener: Callable[[dict], None], options: dict = None) -> None:
|
||||
pass
|
||||
|
||||
def send(self, data: str | bytes | bytearray | int | float | bool) -> None:
|
||||
pass
|
||||
File diff suppressed because it is too large
Load Diff
@@ -151,25 +151,63 @@ seen.add('RTCSignalingSession');
|
||||
seen.add('RTCSignalingChannel');
|
||||
seen.add('RTCSignalingClient');
|
||||
|
||||
function toDocstring(td: any, includePass: boolean = false) {
|
||||
const suffix = includePass ? ` pass` : '';
|
||||
const comments: any[] = ((td.comment ?? {}).summary ?? []).filter((item: any) => item.kind === "text");
|
||||
if (comments.length === 0) {
|
||||
if (includePass) {
|
||||
return `pass`;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
if (comments.length === 1) {
|
||||
return ` """${comments[0].text.replaceAll('\n', ' ')}"""\n${suffix}`;
|
||||
}
|
||||
let text = ` """\n`;
|
||||
for (const comment of comments) {
|
||||
text += ` ${comment.text.replaceAll('\n', ' ')}\n\n`;
|
||||
}
|
||||
text = text.slice(0,text.length - 2)
|
||||
text += ` """\n${suffix}`;
|
||||
return text
|
||||
|
||||
}
|
||||
|
||||
function toComment(td: any) {
|
||||
const comments: any[] = ((td.comment ?? {}).summary ?? []).filter((item: any) => item.kind === "text");
|
||||
if (comments.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (comments.length === 1) {
|
||||
return ` # ${comments[0].text.replaceAll('\n', ' ')}`;
|
||||
}
|
||||
let text = ` # `;
|
||||
for (const comment of comments) {
|
||||
text += `${comment.text.replaceAll('\n', ' ')} `;
|
||||
}
|
||||
return text.slice(0,text.length - 1)
|
||||
|
||||
}
|
||||
|
||||
function addNonDictionaryType(td: any) {
|
||||
seen.add(td.name);
|
||||
python += `
|
||||
class ${td.name}:
|
||||
${toDocstring(td)}
|
||||
`;
|
||||
|
||||
const properties = td.children.filter((child: any) => child.kindString === 'Property');
|
||||
const methods = td.children.filter((child: any) => child.kindString === 'Method');
|
||||
for (const property of properties) {
|
||||
python += ` ${property.name}: ${toPythonType(property.type)}
|
||||
python += ` ${property.name}: ${toPythonType(property.type)}${toComment(property)}
|
||||
`
|
||||
}
|
||||
for (const method of methods) {
|
||||
python += ` ${toPythonMethodDeclaration(method)} ${method.name}(${selfSignature(method)}) -> ${toPythonReturnType(method.signatures[0].type)}:
|
||||
pass
|
||||
${toDocstring(method, true)}
|
||||
|
||||
`
|
||||
}
|
||||
python += ` pass
|
||||
`;
|
||||
}
|
||||
|
||||
for (const td of interfaces) {
|
||||
@@ -181,7 +219,8 @@ for (const td of interfaces) {
|
||||
let pythonEnums = ''
|
||||
for (const e of enums) {
|
||||
pythonEnums += `
|
||||
class ${e.name}(Enum):
|
||||
class ${e.name}(str, Enum):
|
||||
${toDocstring(e)}
|
||||
`
|
||||
for (const val of e.children) {
|
||||
pythonEnums += ` ${val.name} = "${val.type.value}"
|
||||
@@ -190,7 +229,7 @@ class ${e.name}(Enum):
|
||||
}
|
||||
|
||||
python += `
|
||||
class ScryptedInterfaceProperty(Enum):
|
||||
class ScryptedInterfaceProperty(str, Enum):
|
||||
`
|
||||
for (const val of properties) {
|
||||
python += ` ${val} = "${val}"
|
||||
@@ -198,7 +237,7 @@ for (const val of properties) {
|
||||
}
|
||||
|
||||
python += `
|
||||
class ScryptedInterfaceMethods(Enum):
|
||||
class ScryptedInterfaceMethods(str, Enum):
|
||||
`
|
||||
for (const val of methods) {
|
||||
python += ` ${val} = "${val}"
|
||||
@@ -207,10 +246,13 @@ for (const val of methods) {
|
||||
|
||||
python += `
|
||||
class DeviceState:
|
||||
|
||||
def getScryptedProperty(self, property: str) -> Any:
|
||||
pass
|
||||
|
||||
def setScryptedProperty(self, property: str, value: Any):
|
||||
pass
|
||||
|
||||
`
|
||||
for (const [val, type] of Object.entries(allProperties)) {
|
||||
if (val === 'nativeId')
|
||||
@@ -219,6 +261,7 @@ for (const [val, type] of Object.entries(allProperties)) {
|
||||
@property
|
||||
def ${val}(self) -> ${toPythonType(type)}:
|
||||
return self.getScryptedProperty("${val}")
|
||||
|
||||
@${val}.setter
|
||||
def ${val}(self, value: ${toPythonType(type)}):
|
||||
self.setScryptedProperty("${val}", value)
|
||||
@@ -249,15 +292,19 @@ while (discoveredTypes.size) {
|
||||
}
|
||||
pythonUnknowns += `
|
||||
class ${td.name}(TypedDict):
|
||||
${toDocstring(td)}
|
||||
`;
|
||||
|
||||
const properties = td.children?.filter((child: any) => child.kindString === 'Property') || [];
|
||||
for (const property of properties) {
|
||||
pythonUnknowns += ` ${property.name}: ${toPythonType(property.type)}
|
||||
pythonUnknowns += ` ${property.name}: ${toPythonType(property.type)}${toComment(property)}
|
||||
`
|
||||
}
|
||||
pythonUnknowns += ` pass
|
||||
if (properties.length === 0) {
|
||||
pythonUnknowns += ` pass
|
||||
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
python = pythonUnknowns + python;
|
||||
@@ -270,7 +317,7 @@ try:
|
||||
from typing import TypedDict
|
||||
except:
|
||||
from typing_extensions import TypedDict
|
||||
from typing import Union, Any, Callable
|
||||
from typing import Union, Any
|
||||
|
||||
from .other import *
|
||||
|
||||
|
||||
@@ -1855,7 +1855,7 @@ export interface HttpResponseOptions {
|
||||
headers?: object;
|
||||
}
|
||||
export interface EngineIOHandler {
|
||||
onConnection(request: HttpRequest, webScoket: WebSocket): Promise<void>;
|
||||
onConnection(request: HttpRequest, webSocket: WebSocket): Promise<void>;
|
||||
|
||||
}
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user