Merge branch 'main' of github.com:koush/scrypted

This commit is contained in:
Koushik Dutta
2023-07-06 08:12:59 -07:00
6 changed files with 717 additions and 295 deletions

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
.DS_Store
__pycache__
venv
.venv

View File

@@ -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()

View File

@@ -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

View File

@@ -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 *

View File

@@ -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>;
}
/**