Update auf Skills
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
import json, os, ssl, sys
|
||||
from urllib.parse import urlencode
|
||||
from urllib.request import Request, urlopen
|
||||
from urllib.error import HTTPError, URLError
|
||||
PROTO='jarvis.skill.invoke.v1'
|
||||
def truthy(v): return str(v or '').strip().lower() in ('1','true','yes','on')
|
||||
def fail(c,m): return {'protocol':PROTO,'success':False,'error':{'code':c,'message':m}}
|
||||
def ok(d,m='OK',mut=False): return {'protocol':PROTO,'success':True,'data':d,'message':m,'mutated':mut}
|
||||
def cfg():
|
||||
b=os.getenv('UNIFI_NETWORK_URL','').strip().rstrip('/'); k=os.getenv('UNIFI_API_KEY','').strip()
|
||||
if not b or not k: raise RuntimeError('UNIFI_NETWORK_URL und UNIFI_API_KEY müssen gesetzt sein')
|
||||
if not b.startswith(('http://','https://')): b='https://'+b
|
||||
return b,k,truthy(os.getenv('UNIFI_VERIFY_TLS','false'))
|
||||
def request(method,path,body=None,query=None):
|
||||
b,k,verify=cfg(); url=b+path
|
||||
if query:
|
||||
q={a:v for a,v in query.items() if v not in (None,'')};
|
||||
if q: url+='?'+urlencode(q)
|
||||
data=None; h={'Accept':'application/json','X-API-Key':k}
|
||||
if body is not None: data=json.dumps(body).encode(); h['Content-Type']='application/json'
|
||||
ctx=None
|
||||
if url.startswith('https://') and not verify: ctx=ssl._create_unverified_context()
|
||||
try:
|
||||
with urlopen(Request(url,data=data,headers=h,method=method),timeout=8,context=ctx) as r:
|
||||
raw=r.read(); return json.loads(raw.decode()) if raw else {}
|
||||
except HTTPError as e: raise RuntimeError(f'UniFi Network HTTP {e.code}: {e.read().decode(errors="replace")[:1000]}')
|
||||
except URLError as e: raise RuntimeError(f'UniFi Network nicht erreichbar: {e.reason}')
|
||||
def items(raw):
|
||||
if isinstance(raw,list): return raw
|
||||
if isinstance(raw,dict):
|
||||
d=raw.get('data'); return d if isinstance(d,list) else []
|
||||
return []
|
||||
def normalize_site(v): return {'id':v.get('id') or v.get('siteId') or '', 'name':v.get('name') or (v.get('meta') or {}).get('name') or '', 'description':v.get('description') or (v.get('meta') or {}).get('desc') or ''}
|
||||
def normalize_device(v): return {'id':v.get('id',''),'name':v.get('name',''),'model':v.get('model',''),'state':v.get('state',''),'ip':v.get('ipAddress') or v.get('ip',''),'mac':v.get('macAddress') or v.get('mac',''),'firmware':v.get('firmwareVersion',''),'updatable':v.get('firmwareUpdatable')}
|
||||
def normalize_client(v):
|
||||
return {'id':v.get('id',''),'name':v.get('name') or v.get('hostname') or v.get('displayName') or '', 'type':v.get('type',''),'ip':v.get('ipAddress') or v.get('ip',''),'mac':v.get('macAddress') or v.get('mac',''),'connected_at':v.get('connectedAt'),'access':v.get('access')}
|
||||
def main(inv):
|
||||
a=inv.get('action'); x=inv.get('input') or {}
|
||||
if a=='info': return ok(request('GET','/v1/info'),'UniFi Network API erreichbar.')
|
||||
if a=='list_sites':
|
||||
vals=[normalize_site(v) for v in items(request('GET','/v1/sites'))]; return ok({'sites':vals,'count':len(vals)},'UniFi-Sites geladen.')
|
||||
if a in ('list_devices','list_clients'):
|
||||
limit=max(1,min(200,int(x.get('limit',50)))); q={'limit':limit,'filter':x.get('filter','')}
|
||||
path=f"/v1/sites/{x['site_id']}/"+('devices' if a=='list_devices' else 'clients')
|
||||
raw=items(request('GET',path,query=q)); vals=[(normalize_device(v) if a=='list_devices' else normalize_client(v)) for v in raw]
|
||||
key='devices' if a=='list_devices' else 'clients'; return ok({key:vals,'count':len(vals)},'UniFi-Daten geladen.')
|
||||
if a=='restart_device':
|
||||
request('POST',f"/v1/sites/{x['site_id']}/devices/{x['device_id']}/actions",{'action':'RESTART'}); return ok({'accepted':True},'Geräte-Neustart ausgelöst.',True)
|
||||
if a=='power_cycle_port':
|
||||
request('POST',f"/v1/sites/{x['site_id']}/devices/{x['device_id']}/interfaces/ports/{int(x['port_idx'])}/actions",{'action':'POWER_CYCLE'}); return ok({'accepted':True},'PoE Power-Cycle ausgelöst.',True)
|
||||
if a in ('authorize_guest','unauthorize_guest'):
|
||||
body={'action':'AUTHORIZE_GUEST_ACCESS' if a=='authorize_guest' else 'UNAUTHORIZE_GUEST_ACCESS'}
|
||||
if a=='authorize_guest' and x.get('time_limit_minutes'): body['timeLimitMinutes']=int(x['time_limit_minutes'])
|
||||
raw=request('POST',f"/v1/sites/{x['site_id']}/clients/{x['client_id']}/actions",body)
|
||||
return ok(raw if isinstance(raw,dict) else {'result':raw},'Gastzugang geändert.',True)
|
||||
return fail('UNKNOWN_ACTION',f'Unbekannte Aktion: {a}')
|
||||
try: res=main(json.load(sys.stdin))
|
||||
except Exception as e: res=fail('UNIFI_NETWORK_ERROR',str(e))
|
||||
json.dump(res,sys.stdout,ensure_ascii=False)
|
||||
@@ -0,0 +1,273 @@
|
||||
{
|
||||
"protocol": "jarvis.skill.v1",
|
||||
"id": "unifi.network",
|
||||
"name": "UniFi Network",
|
||||
"version": "1.0.0",
|
||||
"description": "Lokale UniFi-Network-Integration über die offizielle Integration API.",
|
||||
"runtime": {
|
||||
"type": "process",
|
||||
"command": "python3",
|
||||
"args": [
|
||||
"main.py"
|
||||
],
|
||||
"timeout_ms": 10000,
|
||||
"env_from": [
|
||||
"UNIFI_NETWORK_URL",
|
||||
"UNIFI_API_KEY",
|
||||
"UNIFI_VERIFY_TLS"
|
||||
]
|
||||
},
|
||||
"permissions": {
|
||||
"network": true,
|
||||
"system_exec": true
|
||||
},
|
||||
"actions": [
|
||||
{
|
||||
"name": "info",
|
||||
"description": "Prüft die UniFi-Network-API und liefert Versionsinformationen.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_sites",
|
||||
"description": "Listet lokale UniFi-Network-Sites.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"sites": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"sites",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_devices",
|
||||
"description": "Listet adoptierte UniFi-Geräte einer Site.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"filter": {
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"site_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"devices": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"devices",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "list_clients",
|
||||
"description": "Listet verbundene Clients einer UniFi-Site; kann zur Anwesenheitserkennung genutzt werden.",
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"filter": {
|
||||
"type": "string"
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"site_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"clients": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"count": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"clients",
|
||||
"count"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "restart_device",
|
||||
"description": "Startet ein adoptiertes UniFi-Gerät neu.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"device_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"site_id",
|
||||
"device_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "power_cycle_port",
|
||||
"description": "Führt einen PoE Power-Cycle auf einem Switch-Port aus.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"device_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"port_idx": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"site_id",
|
||||
"device_id",
|
||||
"port_idx"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"accepted": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"accepted"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "authorize_guest",
|
||||
"description": "Autorisiert einen UniFi-Gastclient optional zeitlich begrenzt.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"time_limit_minutes": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"site_id",
|
||||
"client_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "unauthorize_guest",
|
||||
"description": "Entzieht einem UniFi-Gastclient den Netzwerkzugang.",
|
||||
"mutates": true,
|
||||
"requires_confirmation": true,
|
||||
"input_schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"site_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"client_id": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"site_id",
|
||||
"client_id"
|
||||
],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"output_schema": {
|
||||
"type": "object"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user