forked from platypush/platypush
Merge branch 'master' into snyk-upgrade-503e414934e3e9df4999abbd15eed244
This commit is contained in:
commit
912dddd3da
34 changed files with 1078 additions and 574 deletions
13
README.md
13
README.md
|
@ -505,11 +505,10 @@ event.hook.SearchSongVoiceCommand:
|
||||||
[Example](https://git.platypush.tech/platypush/platypush/src/branch/master/examples/conf/hook.py):
|
[Example](https://git.platypush.tech/platypush/platypush/src/branch/master/examples/conf/hook.py):
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from platypush.event.hook import hook
|
from platypush import run, when
|
||||||
from platypush.utils import run
|
|
||||||
from platypush.message.event.assistant import SpeechRecognizedEvent
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
|
|
||||||
@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
@when(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
||||||
def on_music_play_command(event, title=None, artist=None, **context):
|
def on_music_play_command(event, title=None, artist=None, **context):
|
||||||
results = run('music.mpd.search', filter={
|
results = run('music.mpd.search', filter={
|
||||||
'artist': artist,
|
'artist': artist,
|
||||||
|
@ -527,22 +526,22 @@ against partial event arguments are also possible, and relational operators are
|
||||||
supported as well. For example:
|
supported as well. For example:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from platypush.event.hook import hook
|
from platypush import hook
|
||||||
from platypush.message.event.sensor import SensorDataChangeEvent
|
from platypush.message.event.sensor import SensorDataChangeEvent
|
||||||
|
|
||||||
@hook(SensorDataChangeEvent, data=1):
|
@when(SensorDataChangeEvent, data=1):
|
||||||
def hook_1(event):
|
def hook_1(event):
|
||||||
"""
|
"""
|
||||||
Triggered when event.data == 1
|
Triggered when event.data == 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hook(SensorDataChangeEvent, data={'state': 1}):
|
@when(SensorDataChangeEvent, data={'state': 1}):
|
||||||
def hook_2(event):
|
def hook_2(event):
|
||||||
"""
|
"""
|
||||||
Triggered when event.data['state'] == 1
|
Triggered when event.data['state'] == 1
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@hook(SensorDataChangeEvent, data={
|
@when(SensorDataChangeEvent, data={
|
||||||
'temperature': {'$gt': 25},
|
'temperature': {'$gt': 25},
|
||||||
'humidity': {'$le': 15}
|
'humidity': {'$le': 15}
|
||||||
}):
|
}):
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
``camera.android``
|
|
||||||
=============================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.camera.android
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
``camera``
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.camera
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
``pihole``
|
|
||||||
=====================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.pihole
|
|
||||||
:members:
|
|
|
@ -1,5 +0,0 @@
|
||||||
``stt``
|
|
||||||
==================================
|
|
||||||
|
|
||||||
.. automodule:: platypush.message.response.stt
|
|
||||||
:members:
|
|
|
@ -6,13 +6,9 @@ Responses
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
:caption: Responses:
|
:caption: Responses:
|
||||||
|
|
||||||
platypush/responses/camera.rst
|
|
||||||
platypush/responses/camera.android.rst
|
|
||||||
platypush/responses/google.drive.rst
|
platypush/responses/google.drive.rst
|
||||||
platypush/responses/pihole.rst
|
|
||||||
platypush/responses/printer.cups.rst
|
platypush/responses/printer.cups.rst
|
||||||
platypush/responses/qrcode.rst
|
platypush/responses/qrcode.rst
|
||||||
platypush/responses/ssh.rst
|
platypush/responses/ssh.rst
|
||||||
platypush/responses/stt.rst
|
|
||||||
platypush/responses/tensorflow.rst
|
platypush/responses/tensorflow.rst
|
||||||
platypush/responses/translate.rst
|
platypush/responses/translate.rst
|
||||||
|
|
|
@ -3,13 +3,10 @@
|
||||||
# which event type they should be called, and optionally on which event attribute values.
|
# which event type they should be called, and optionally on which event attribute values.
|
||||||
#
|
#
|
||||||
# Event hooks should be stored in Python files under `~/.config/platypush/scripts`. All the functions that use the
|
# Event hooks should be stored in Python files under `~/.config/platypush/scripts`. All the functions that use the
|
||||||
# @hook decorator will automatically be discovered and imported as event hooks into the platform at runtime.
|
# @when decorator will automatically be discovered and imported as event hooks into the platform at runtime.
|
||||||
|
|
||||||
# `run` is a utility function that runs a request by name (e.g. `light.hue.on`).
|
# `run` is a utility function that runs a request by name (e.g. `light.hue.on`).
|
||||||
from platypush.utils import run
|
from platypush import when, run
|
||||||
|
|
||||||
# @hook decorator
|
|
||||||
from platypush.event.hook import hook
|
|
||||||
|
|
||||||
# Event types that you want to react to
|
# Event types that you want to react to
|
||||||
from platypush.message.event.assistant import (
|
from platypush.message.event.assistant import (
|
||||||
|
@ -18,7 +15,7 @@ from platypush.message.event.assistant import (
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
@when(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
||||||
def on_music_play_command(event, title=None, artist=None, **context):
|
def on_music_play_command(event, title=None, artist=None, **context):
|
||||||
"""
|
"""
|
||||||
This function will be executed when a SpeechRecognizedEvent with `phrase="play the music"` is triggered.
|
This function will be executed when a SpeechRecognizedEvent with `phrase="play the music"` is triggered.
|
||||||
|
@ -40,7 +37,7 @@ def on_music_play_command(event, title=None, artist=None, **context):
|
||||||
run('tts.say', "I can't find any music matching your query")
|
run('tts.say', "I can't find any music matching your query")
|
||||||
|
|
||||||
|
|
||||||
@hook(ConversationStartEvent)
|
@when(ConversationStartEvent)
|
||||||
def on_conversation_start(event, **context):
|
def on_conversation_start(event, **context):
|
||||||
"""
|
"""
|
||||||
A simple hook that gets invoked when a new conversation starts with a voice assistant and simply pauses the music
|
A simple hook that gets invoked when a new conversation starts with a voice assistant and simply pauses the music
|
||||||
|
|
|
@ -17,6 +17,10 @@ from .procedure import procedure
|
||||||
from .runner import main
|
from .runner import main
|
||||||
from .utils import run
|
from .utils import run
|
||||||
|
|
||||||
|
# Alias for platypush.event.hook.hook,
|
||||||
|
# see https://git.platypush.tech/platypush/platypush/issues/399
|
||||||
|
when = hook
|
||||||
|
|
||||||
|
|
||||||
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
|
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
|
||||||
__version__ = '0.50.3'
|
__version__ = '0.50.3'
|
||||||
|
@ -35,6 +39,7 @@ __all__ = [
|
||||||
'main',
|
'main',
|
||||||
'procedure',
|
'procedure',
|
||||||
'run',
|
'run',
|
||||||
|
'when',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -153,14 +153,13 @@ class HttpBackend(Backend):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from platypush.context import get_plugin
|
from platypush import get_plugin, when
|
||||||
from platypush.event.hook import hook
|
|
||||||
from platypush.message.event.http.hook import WebhookEvent
|
from platypush.message.event.http.hook import WebhookEvent
|
||||||
|
|
||||||
hook_token = 'abcdefabcdef'
|
hook_token = 'abcdefabcdef'
|
||||||
|
|
||||||
# Expose the hook under the /hook/lights_toggle endpoint
|
# Expose the hook under the /hook/lights_toggle endpoint
|
||||||
@hook(WebhookEvent, hook='lights_toggle')
|
@when(WebhookEvent, hook='lights_toggle')
|
||||||
def lights_toggle(event, **context):
|
def lights_toggle(event, **context):
|
||||||
# Do any checks on the request
|
# Do any checks on the request
|
||||||
assert event.headers.get('X-Token') == hook_token, 'Unauthorized'
|
assert event.headers.get('X-Token') == hook_token, 'Unauthorized'
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" src="/static/js/chunk-vendors.89d8d8a7.js"></script><script defer="defer" src="/static/js/app.0268780d.js"></script><link href="/static/css/chunk-vendors.a2412607.css" rel="stylesheet"><link href="/static/css/app.0e655fed.css" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Platypush"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><!--[if IE]><link rel="icon" href="/favicon.ico"><![endif]--><link rel="stylesheet" href="/fonts/poppins.css"><title>platypush</title><script defer="defer" src="/static/js/chunk-vendors.89d8d8a7.js"></script><script defer="defer" src="/static/js/app.50f2ef87.js"></script><link href="/static/css/chunk-vendors.a2412607.css" rel="stylesheet"><link href="/static/css/app.0e655fed.css" rel="stylesheet"><link rel="icon" type="image/svg+xml" href="/img/icons/favicon.svg"><link rel="icon" type="image/png" sizes="32x32" href="/img/icons/favicon-32x32.png"><link rel="icon" type="image/png" sizes="16x16" href="/img/icons/favicon-16x16.png"><link rel="manifest" href="/manifest.json"><meta name="theme-color" content="#ffffff"><meta name="apple-mobile-web-app-capable" content="no"><meta name="apple-mobile-web-app-status-bar-style" content="default"><meta name="apple-mobile-web-app-title" content="Platypush"><link rel="apple-touch-icon" href="/img/icons/apple-touch-icon-152x152.png"><link rel="mask-icon" href="/img/icons/safari-pinned-tab.svg" color="#ffffff"><meta name="msapplication-TileImage" content="/img/icons/msapplication-icon-144x144.png"><meta name="msapplication-TileColor" content="#000000"></head><body><noscript><strong>We're sorry but platypush doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div></body></html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/css/3924.e7e714bc.css
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/css/3924.e7e714bc.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
platypush/backend/http/webapp/dist/static/js/3924.9592f111.js
vendored
Normal file
2
platypush/backend/http/webapp/dist/static/js/3924.9592f111.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
platypush/backend/http/webapp/dist/static/js/3924.9592f111.js.map
vendored
Normal file
1
platypush/backend/http/webapp/dist/static/js/3924.9592f111.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
155
platypush/backend/http/webapp/package-lock.json
generated
155
platypush/backend/http/webapp/package-lock.json
generated
|
@ -16,9 +16,9 @@
|
||||||
"lato-font": "^3.0.0",
|
"lato-font": "^3.0.0",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"sass": "^1.71.0",
|
"sass": "^1.75.0",
|
||||||
"sass-loader": "^10.5.2",
|
"sass-loader": "^10.5.2",
|
||||||
"vue": "^3.4.19",
|
"vue": "^3.4.23",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
"vue-skycons": "^4.2.0",
|
"vue-skycons": "^4.2.0",
|
||||||
"w3css": "^2.7.0"
|
"w3css": "^2.7.0"
|
||||||
|
@ -492,9 +492,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.24.0",
|
"version": "7.24.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz",
|
||||||
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
|
"integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
},
|
},
|
||||||
|
@ -3080,15 +3080,15 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-core": {
|
"node_modules/@vue/compiler-core": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.23.tgz",
|
||||||
"integrity": "sha512-gj81785z0JNzRcU0Mq98E56e4ltO1yf8k5PQ+tV/7YHnbZkrM0fyFyuttnN8ngJZjbpofWE/m4qjKBiLl8Ju4w==",
|
"integrity": "sha512-HAFmuVEwNqNdmk+w4VCQ2pkLk1Vw4XYiiyxEp3z/xvl14aLTUBw2OfVH3vBcx+FtGsynQLkkhK410Nah1N2yyQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.24.1",
|
||||||
"@vue/shared": "3.4.19",
|
"@vue/shared": "3.4.23",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-core/node_modules/entities": {
|
"node_modules/@vue/compiler-core/node_modules/entities": {
|
||||||
|
@ -3103,37 +3103,37 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-dom": {
|
"node_modules/@vue/compiler-dom": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.23.tgz",
|
||||||
"integrity": "sha512-vm6+cogWrshjqEHTzIDCp72DKtea8Ry/QVpQRYoyTIg9k7QZDX6D8+HGURjtmatfgM8xgCFtJJaOlCaRYRK3QA==",
|
"integrity": "sha512-t0b9WSTnCRrzsBGrDd1LNR5HGzYTr7LX3z6nNBG+KGvZLqrT0mY6NsMzOqlVMBKKXKVuusbbB5aOOFgTY+senw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-core": "3.4.19",
|
"@vue/compiler-core": "3.4.23",
|
||||||
"@vue/shared": "3.4.19"
|
"@vue/shared": "3.4.23"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-sfc": {
|
"node_modules/@vue/compiler-sfc": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.23.tgz",
|
||||||
"integrity": "sha512-LQ3U4SN0DlvV0xhr1lUsgLCYlwQfUfetyPxkKYu7dkfvx7g3ojrGAkw0AERLOKYXuAGnqFsEuytkdcComei3Yg==",
|
"integrity": "sha512-fSDTKTfzaRX1kNAUiaj8JB4AokikzStWgHooMhaxyjZerw624L+IAP/fvI4ZwMpwIh8f08PVzEnu4rg8/Npssw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.23.9",
|
"@babel/parser": "^7.24.1",
|
||||||
"@vue/compiler-core": "3.4.19",
|
"@vue/compiler-core": "3.4.23",
|
||||||
"@vue/compiler-dom": "3.4.19",
|
"@vue/compiler-dom": "3.4.23",
|
||||||
"@vue/compiler-ssr": "3.4.19",
|
"@vue/compiler-ssr": "3.4.23",
|
||||||
"@vue/shared": "3.4.19",
|
"@vue/shared": "3.4.23",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"magic-string": "^0.30.6",
|
"magic-string": "^0.30.8",
|
||||||
"postcss": "^8.4.33",
|
"postcss": "^8.4.38",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/compiler-ssr": {
|
"node_modules/@vue/compiler-ssr": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.23.tgz",
|
||||||
"integrity": "sha512-P0PLKC4+u4OMJ8sinba/5Z/iDT84uMRRlrWzadgLA69opCpI1gG4N55qDSC+dedwq2fJtzmGald05LWR5TFfLw==",
|
"integrity": "sha512-hb6Uj2cYs+tfqz71Wj6h3E5t6OKvb4MVcM2Nl5i/z1nv1gjEhw+zYaNOV+Xwn+SSN/VZM0DgANw5TuJfxfezPg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.19",
|
"@vue/compiler-dom": "3.4.23",
|
||||||
"@vue/shared": "3.4.19"
|
"@vue/shared": "3.4.23"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/component-compiler-utils": {
|
"node_modules/@vue/component-compiler-utils": {
|
||||||
|
@ -3206,48 +3206,48 @@
|
||||||
"integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
|
"integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/reactivity": {
|
"node_modules/@vue/reactivity": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.23.tgz",
|
||||||
"integrity": "sha512-+VcwrQvLZgEclGZRHx4O2XhyEEcKaBi50WbxdVItEezUf4fqRh838Ix6amWTdX0CNb/b6t3Gkz3eOebfcSt+UA==",
|
"integrity": "sha512-GlXR9PL+23fQ3IqnbSQ8OQKLodjqCyoCrmdLKZk3BP7jN6prWheAfU7a3mrltewTkoBm+N7qMEb372VHIkQRMQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/shared": "3.4.19"
|
"@vue/shared": "3.4.23"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-core": {
|
"node_modules/@vue/runtime-core": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.23.tgz",
|
||||||
"integrity": "sha512-/Z3tFwOrerJB/oyutmJGoYbuoadphDcJAd5jOuJE86THNZji9pYjZroQ2NFsZkTxOq0GJbb+s2kxTYToDiyZzw==",
|
"integrity": "sha512-FeQ9MZEXoFzFkFiw9MQQ/FWs3srvrP+SjDKSeRIiQHIhtkzoj0X4rWQlRNHbGuSwLra6pMyjAttwixNMjc/xLw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/reactivity": "3.4.19",
|
"@vue/reactivity": "3.4.23",
|
||||||
"@vue/shared": "3.4.19"
|
"@vue/shared": "3.4.23"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/runtime-dom": {
|
"node_modules/@vue/runtime-dom": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.23.tgz",
|
||||||
"integrity": "sha512-IyZzIDqfNCF0OyZOauL+F4yzjMPN2rPd8nhqPP2N1lBn3kYqJpPHHru+83Rkvo2lHz5mW+rEeIMEF9qY3PB94g==",
|
"integrity": "sha512-RXJFwwykZWBkMiTPSLEWU3kgVLNAfActBfWFlZd0y79FTUxexogd0PLG4HH2LfOktjRxV47Nulygh0JFXe5f9A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/runtime-core": "3.4.19",
|
"@vue/runtime-core": "3.4.23",
|
||||||
"@vue/shared": "3.4.19",
|
"@vue/shared": "3.4.23",
|
||||||
"csstype": "^3.1.3"
|
"csstype": "^3.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/server-renderer": {
|
"node_modules/@vue/server-renderer": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.23.tgz",
|
||||||
"integrity": "sha512-eAj2p0c429RZyyhtMRnttjcSToch+kTWxFPHlzGMkR28ZbF1PDlTcmGmlDxccBuqNd9iOQ7xPRPAGgPVj+YpQw==",
|
"integrity": "sha512-LDwGHtnIzvKFNS8dPJ1SSU5Gvm36p2ck8wCZc52fc3k/IfjKcwCyrWEf0Yag/2wTFUBXrqizfhK9c/mC367dXQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-ssr": "3.4.19",
|
"@vue/compiler-ssr": "3.4.23",
|
||||||
"@vue/shared": "3.4.19"
|
"@vue/shared": "3.4.23"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "3.4.19"
|
"vue": "3.4.23"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@vue/shared": {
|
"node_modules/@vue/shared": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.23.tgz",
|
||||||
"integrity": "sha512-/KliRRHMF6LoiThEy+4c1Z4KB/gbPrGjWwJR+crg2otgrf/egKzRaCPvJ51S5oetgsgXLfc4Rm5ZgrKHZrtMSw=="
|
"integrity": "sha512-wBQ0gvf+SMwsCQOyusNw/GoXPV47WGd1xB5A1Pgzy0sQ3Bi5r5xm3n+92y3gCnB3MWqnRDdvfkRGxhKtbBRNgg=="
|
||||||
},
|
},
|
||||||
"node_modules/@vue/vue-loader-v15": {
|
"node_modules/@vue/vue-loader-v15": {
|
||||||
"name": "vue-loader",
|
"name": "vue-loader",
|
||||||
|
@ -8376,14 +8376,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.8",
|
"version": "0.30.10",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz",
|
||||||
"integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==",
|
"integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.15"
|
"@jridgewell/sourcemap-codec": "^1.4.15"
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=12"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/make-dir": {
|
"node_modules/make-dir": {
|
||||||
|
@ -9338,9 +9335,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.35",
|
"version": "8.4.38",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
|
||||||
"integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
|
"integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
@ -9358,7 +9355,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
"source-map-js": "^1.0.2"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^10 || ^12 || >=14"
|
"node": "^10 || ^12 || >=14"
|
||||||
|
@ -10554,9 +10551,9 @@
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/sass": {
|
"node_modules/sass": {
|
||||||
"version": "1.71.0",
|
"version": "1.75.0",
|
||||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.71.0.tgz",
|
"resolved": "https://registry.npmjs.org/sass/-/sass-1.75.0.tgz",
|
||||||
"integrity": "sha512-HKKIKf49Vkxlrav3F/w6qRuPcmImGVbIXJ2I3Kg0VMA+3Bav+8yE9G5XmP5lMj6nl4OlqbPftGAscNaNu28b8w==",
|
"integrity": "sha512-ShMYi3WkrDWxExyxSZPst4/okE9ts46xZmJDSawJQrnte7M1V9fScVB+uNXOVKRBt0PggHOwoZcn8mYX4trnBw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chokidar": ">=3.0.0 <4.0.0",
|
"chokidar": ">=3.0.0 <4.0.0",
|
||||||
"immutable": "^4.0.0",
|
"immutable": "^4.0.0",
|
||||||
|
@ -11023,9 +11020,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/source-map-js": {
|
"node_modules/source-map-js": {
|
||||||
"version": "1.0.2",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
|
||||||
"integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
|
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
|
@ -11979,15 +11976,15 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/vue": {
|
"node_modules/vue": {
|
||||||
"version": "3.4.19",
|
"version": "3.4.23",
|
||||||
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.19.tgz",
|
"resolved": "https://registry.npmjs.org/vue/-/vue-3.4.23.tgz",
|
||||||
"integrity": "sha512-W/7Fc9KUkajFU8dBeDluM4sRGc/aa4YJnOYck8dkjgZoXtVsn3OeTGni66FV1l3+nvPA7VBFYtPioaGKUmEADw==",
|
"integrity": "sha512-X1y6yyGJ28LMUBJ0k/qIeKHstGd+BlWQEOT40x3auJFTmpIhpbKLgN7EFsqalnJXq1Km5ybDEsp6BhuWKciUDg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/compiler-dom": "3.4.19",
|
"@vue/compiler-dom": "3.4.23",
|
||||||
"@vue/compiler-sfc": "3.4.19",
|
"@vue/compiler-sfc": "3.4.23",
|
||||||
"@vue/runtime-dom": "3.4.19",
|
"@vue/runtime-dom": "3.4.23",
|
||||||
"@vue/server-renderer": "3.4.19",
|
"@vue/server-renderer": "3.4.23",
|
||||||
"@vue/shared": "3.4.19"
|
"@vue/shared": "3.4.23"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "*"
|
"typescript": "*"
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
"lato-font": "^3.0.0",
|
"lato-font": "^3.0.0",
|
||||||
"mitt": "^2.1.0",
|
"mitt": "^2.1.0",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.7.2",
|
||||||
"sass": "^1.71.0",
|
"sass": "^1.75.0",
|
||||||
"sass-loader": "^10.5.2",
|
"sass-loader": "^10.5.2",
|
||||||
"vue": "^3.4.19",
|
"vue": "^3.4.23",
|
||||||
"vue-router": "^4.3.0",
|
"vue-router": "^4.3.0",
|
||||||
"vue-skycons": "^4.2.0",
|
"vue-skycons": "^4.2.0",
|
||||||
"w3css": "^2.7.0"
|
"w3css": "^2.7.0"
|
||||||
|
|
|
@ -87,8 +87,30 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
config() {
|
configuredCameras() {
|
||||||
return this.$root.config['camera.android.ipcam']
|
const config = this.$root.config['camera.android.ipcam']
|
||||||
|
let cameras = config.cameras || []
|
||||||
|
|
||||||
|
if (!cameras.length) {
|
||||||
|
const name = config.name || config.host
|
||||||
|
cameras[name] = {
|
||||||
|
'name': name,
|
||||||
|
'host': config.host,
|
||||||
|
'port': config.port,
|
||||||
|
'username': config.username,
|
||||||
|
'password': config.password,
|
||||||
|
'timeout': config.timeout,
|
||||||
|
'ssl': config.ssl,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cameras = cameras.reduce((cameras, cam) => {
|
||||||
|
const name = cam.name || cam.host
|
||||||
|
cameras[name] = cam
|
||||||
|
return cameras
|
||||||
|
}, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
return cameras
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -160,7 +182,7 @@ export default {
|
||||||
cam[attr] = cam[attr].replace('https://', 'http://')
|
cam[attr] = cam[attr].replace('https://', 'http://')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cam.name in this.config.cameras && this.config.cameras[cam.name].username) {
|
if (cam.name in this.configuredCameras && this.configuredCameras[cam.name].username) {
|
||||||
cam[attr] = 'http://' + this.config.cameras[cam.name].username + ':' +
|
cam[attr] = 'http://' + this.config.cameras[cam.name].username + ':' +
|
||||||
this.config.cameras[cam.name].password + '@' + cam[attr].substr(7)
|
this.config.cameras[cam.name].password + '@' + cam[attr].substr(7)
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -1,8 +0,0 @@
|
||||||
from platypush.message.response import Response
|
|
||||||
|
|
||||||
|
|
||||||
class CameraResponse(Response):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -1,191 +0,0 @@
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from platypush.message.response.camera import CameraResponse
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidCameraStatusResponse(CameraResponse):
|
|
||||||
"""
|
|
||||||
Example response:
|
|
||||||
|
|
||||||
.. code-block:: json
|
|
||||||
|
|
||||||
{
|
|
||||||
"stream_url": "https://192.168.1.30:8080/video",
|
|
||||||
"image_url": "https://192.168.1.30:8080/photo.jpg",
|
|
||||||
"audio_url": "https://192.168.1.30:8080/audio.wav",
|
|
||||||
"orientation": "landscape",
|
|
||||||
"idle": "off",
|
|
||||||
"audio_only": "off",
|
|
||||||
"overlay": "off",
|
|
||||||
"quality": "49",
|
|
||||||
"focus_homing": "off",
|
|
||||||
"ip_address": "192.168.1.30",
|
|
||||||
"motion_limit": "250",
|
|
||||||
"adet_limit": "200",
|
|
||||||
"night_vision": "off",
|
|
||||||
"night_vision_average": "2",
|
|
||||||
"night_vision_gain": "1.0",
|
|
||||||
"motion_detect": "off",
|
|
||||||
"motion_display": "off",
|
|
||||||
"video_chunk_len": "60",
|
|
||||||
"gps_active": "off",
|
|
||||||
"video_size": "1920x1080",
|
|
||||||
"mirror_flip": "none",
|
|
||||||
"ffc": "off",
|
|
||||||
"rtsp_video_formats": "",
|
|
||||||
"rtsp_audio_formats": "",
|
|
||||||
"video_connections": "0",
|
|
||||||
"audio_connections": "0",
|
|
||||||
"ivideon_streaming": "off",
|
|
||||||
"zoom": "100",
|
|
||||||
"crop_x": "50",
|
|
||||||
"crop_y": "50",
|
|
||||||
"coloreffect": "none",
|
|
||||||
"scenemode": "auto",
|
|
||||||
"focusmode": "continuous-video",
|
|
||||||
"whitebalance": "auto",
|
|
||||||
"flashmode": "off",
|
|
||||||
"antibanding": "off",
|
|
||||||
"torch": "off",
|
|
||||||
"focus_distance": "0.0",
|
|
||||||
"focal_length": "4.25",
|
|
||||||
"aperture": "1.7",
|
|
||||||
"filter_density": "0.0",
|
|
||||||
"exposure_ns": "9384",
|
|
||||||
"frame_duration": "33333333",
|
|
||||||
"iso": "100",
|
|
||||||
"manual_sensor": "off",
|
|
||||||
"photo_size": "1920x1080"
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
attrs = {
|
|
||||||
'orientation', 'idle', 'audio_only', 'overlay', 'quality', 'focus_homing',
|
|
||||||
'ip_address', 'motion_limit', 'adet_limit', 'night_vision',
|
|
||||||
'night_vision_average', 'night_vision_gain', 'motion_detect',
|
|
||||||
'motion_display', 'video_chunk_len', 'gps_active', 'video_size',
|
|
||||||
'mirror_flip', 'ffc', 'rtsp_video_formats', 'rtsp_audio_formats',
|
|
||||||
'video_connections', 'audio_connections', 'ivideon_streaming', 'zoom',
|
|
||||||
'crop_x', 'crop_y', 'coloreffect', 'scenemode', 'focusmode',
|
|
||||||
'whitebalance', 'flashmode', 'antibanding', 'torch', 'focus_distance',
|
|
||||||
'focal_length', 'aperture', 'filter_density', 'exposure_ns',
|
|
||||||
'frame_duration', 'iso', 'manual_sensor', 'photo_size',
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, *args,
|
|
||||||
name: str = None,
|
|
||||||
stream_url: str = None,
|
|
||||||
image_url: str = None,
|
|
||||||
audio_url: str = None,
|
|
||||||
orientation: str = None,
|
|
||||||
idle: str = None,
|
|
||||||
audio_only: str = None,
|
|
||||||
overlay: str = None,
|
|
||||||
quality: str = None,
|
|
||||||
focus_homing: str = None,
|
|
||||||
ip_address: str = None,
|
|
||||||
motion_limit: str = None,
|
|
||||||
adet_limit: str = None,
|
|
||||||
night_vision: str = None,
|
|
||||||
night_vision_average: str = None,
|
|
||||||
night_vision_gain: str = None,
|
|
||||||
motion_detect: str = None,
|
|
||||||
motion_display: str = None,
|
|
||||||
video_chunk_len: str = None,
|
|
||||||
gps_active: str = None,
|
|
||||||
video_size: str = None,
|
|
||||||
mirror_flip: str = None,
|
|
||||||
ffc: str = None,
|
|
||||||
rtsp_video_formats: str = None,
|
|
||||||
rtsp_audio_formats: str = None,
|
|
||||||
video_connections: str = None,
|
|
||||||
audio_connections: str = None,
|
|
||||||
ivideon_streaming: str = None,
|
|
||||||
zoom: str = None,
|
|
||||||
crop_x: str = None,
|
|
||||||
crop_y: str = None,
|
|
||||||
coloreffect: str = None,
|
|
||||||
scenemode: str = None,
|
|
||||||
focusmode: str = None,
|
|
||||||
whitebalance: str = None,
|
|
||||||
flashmode: str = None,
|
|
||||||
antibanding: str = None,
|
|
||||||
torch: str = None,
|
|
||||||
focus_distance: str = None,
|
|
||||||
focal_length: str = None,
|
|
||||||
aperture: str = None,
|
|
||||||
filter_density: str = None,
|
|
||||||
exposure_ns: str = None,
|
|
||||||
frame_duration: str = None,
|
|
||||||
iso: str = None,
|
|
||||||
manual_sensor: str = None,
|
|
||||||
photo_size: str = None,
|
|
||||||
**kwargs):
|
|
||||||
|
|
||||||
self.status = {
|
|
||||||
"name": name,
|
|
||||||
"stream_url": stream_url,
|
|
||||||
"image_url": image_url,
|
|
||||||
"audio_url": audio_url,
|
|
||||||
"orientation": orientation,
|
|
||||||
"idle": True if idle == "on" else False,
|
|
||||||
"audio_only": True if audio_only == "on" else False,
|
|
||||||
"overlay": True if overlay == "on" else False,
|
|
||||||
"quality": int(quality or 0),
|
|
||||||
"focus_homing": True if focus_homing == "on" else False,
|
|
||||||
"ip_address": ip_address,
|
|
||||||
"motion_limit": int(motion_limit or 0),
|
|
||||||
"adet_limit": int(adet_limit or 0),
|
|
||||||
"night_vision": True if night_vision == "on" else False,
|
|
||||||
"night_vision_average": float(night_vision_average or 0),
|
|
||||||
"night_vision_gain": float(night_vision_gain or 0),
|
|
||||||
"motion_detect": True if motion_detect == "on" else False,
|
|
||||||
"motion_display": True if motion_display == "on" else False,
|
|
||||||
"video_chunk_len": int(video_chunk_len or 0),
|
|
||||||
"gps_active": True if gps_active == "on" else False,
|
|
||||||
"video_size": video_size,
|
|
||||||
"mirror_flip": mirror_flip,
|
|
||||||
"ffc": True if ffc == "on" else False,
|
|
||||||
"rtsp_video_formats": rtsp_video_formats,
|
|
||||||
"rtsp_audio_formats": rtsp_audio_formats,
|
|
||||||
"video_connections": int(video_connections or 0),
|
|
||||||
"audio_connections": int(audio_connections or 0),
|
|
||||||
"ivideon_streaming": True if ivideon_streaming == "on" else False,
|
|
||||||
"zoom": int(zoom or 0),
|
|
||||||
"crop_x": int(crop_x or 0),
|
|
||||||
"crop_y": int(crop_y or 0),
|
|
||||||
"coloreffect": coloreffect,
|
|
||||||
"scenemode": scenemode,
|
|
||||||
"focusmode": focusmode,
|
|
||||||
"whitebalance": whitebalance,
|
|
||||||
"flashmode": True if flashmode == "on" else False,
|
|
||||||
"antibanding": True if antibanding == "on" else False,
|
|
||||||
"torch": True if torch == "on" else False,
|
|
||||||
"focus_distance": float(focus_distance or 0),
|
|
||||||
"focal_length": float(focal_length or 0),
|
|
||||||
"aperture": float(aperture or 0),
|
|
||||||
"filter_density": float(filter_density or 0),
|
|
||||||
"exposure_ns": int(exposure_ns or 0),
|
|
||||||
"frame_duration": int(frame_duration or 0),
|
|
||||||
"iso": int(iso or 0),
|
|
||||||
"manual_sensor": True if manual_sensor == "on" else False,
|
|
||||||
"photo_size": photo_size,
|
|
||||||
}
|
|
||||||
|
|
||||||
super().__init__(*args, output=self.status, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidCameraStatusListResponse(CameraResponse):
|
|
||||||
def __init__(self, status: List[AndroidCameraStatusResponse], **kwargs):
|
|
||||||
self.status = [s.status for s in status]
|
|
||||||
super().__init__(output=self.status, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class AndroidCameraPictureResponse(CameraResponse):
|
|
||||||
def __init__(self, image_file: str, *args, **kwargs):
|
|
||||||
self.image_file = image_file
|
|
||||||
super().__init__(*args, output={'image_file': image_file}, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -1,38 +0,0 @@
|
||||||
from platypush.message.response import Response
|
|
||||||
|
|
||||||
|
|
||||||
class PiholeStatusResponse(Response):
|
|
||||||
def __init__(self,
|
|
||||||
server: str,
|
|
||||||
status: str,
|
|
||||||
ads_percentage: float,
|
|
||||||
blocked: int,
|
|
||||||
cached: int,
|
|
||||||
domain_count: int,
|
|
||||||
forwarded: int,
|
|
||||||
queries: int,
|
|
||||||
total_clients: int,
|
|
||||||
total_queries: int,
|
|
||||||
unique_clients: int,
|
|
||||||
unique_domains: int,
|
|
||||||
version: str,
|
|
||||||
*args,
|
|
||||||
**kwargs):
|
|
||||||
super().__init__(*args, output={
|
|
||||||
'server': server,
|
|
||||||
'status': status,
|
|
||||||
'ads_percentage': ads_percentage,
|
|
||||||
'blocked': blocked,
|
|
||||||
'cached': cached,
|
|
||||||
'domain_count': domain_count,
|
|
||||||
'forwarded': forwarded,
|
|
||||||
'queries': queries,
|
|
||||||
'total_clients': total_clients,
|
|
||||||
'total_queries': total_queries,
|
|
||||||
'unique_clients': unique_clients,
|
|
||||||
'unique_domains': unique_domains,
|
|
||||||
'version': version,
|
|
||||||
}, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -1,11 +0,0 @@
|
||||||
from platypush.message.response import Response
|
|
||||||
|
|
||||||
|
|
||||||
class SpeechDetectedResponse(Response):
|
|
||||||
def __init__(self, *args, speech: str, **kwargs):
|
|
||||||
super().__init__(*args, output={
|
|
||||||
'speech': speech
|
|
||||||
}, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
|
|
@ -86,11 +86,11 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from platypush import hook, run
|
from platypush import when, run
|
||||||
from platypush.message.event.assistant import HotwordDetectedEvent
|
from platypush.message.event.assistant import HotwordDetectedEvent
|
||||||
|
|
||||||
# Turn on a light for 5 seconds when the hotword "Alexa" is detected
|
# Turn on a light for 5 seconds when the hotword "Alexa" is detected
|
||||||
@hook(HotwordDetectedEvent, hotword='Alexa')
|
@when(HotwordDetectedEvent, hotword='Alexa')
|
||||||
def on_hotword_detected(event: HotwordDetectedEvent, **context):
|
def on_hotword_detected(event: HotwordDetectedEvent, **context):
|
||||||
run("light.hue.on", lights=["Living Room"])
|
run("light.hue.on", lights=["Living Room"])
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
@ -109,12 +109,12 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from platypush import hook, run
|
from platypush import when, run
|
||||||
from platypush.message.event.assistant import HotwordDetectedEvent
|
from platypush.message.event.assistant import HotwordDetectedEvent
|
||||||
|
|
||||||
# Start a conversation using the Italian language model when the
|
# Start a conversation using the Italian language model when the
|
||||||
# "Buongiorno" hotword is detected
|
# "Buongiorno" hotword is detected
|
||||||
@hook(HotwordDetectedEvent, hotword='Buongiorno')
|
@when(HotwordDetectedEvent, hotword='Buongiorno')
|
||||||
def on_it_hotword_detected(event: HotwordDetectedEvent, **context):
|
def on_it_hotword_detected(event: HotwordDetectedEvent, **context):
|
||||||
event.assistant.start_conversation(model_file='path/to/it.pv')
|
event.assistant.start_conversation(model_file='path/to/it.pv')
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from platypush import hook, run
|
from platypush import when, run
|
||||||
from platypush.message.event.assistant import SpeechRecognizedEvent
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
|
|
||||||
# Turn on a light when the phrase "turn on the lights" is detected.
|
# Turn on a light when the phrase "turn on the lights" is detected.
|
||||||
|
@ -144,7 +144,7 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
# flexible when matching the phrases. For example, the following hook
|
# flexible when matching the phrases. For example, the following hook
|
||||||
# will be matched when the user says "turn on the lights", "turn on
|
# will be matched when the user says "turn on the lights", "turn on
|
||||||
# lights", "lights on", "lights on please", "turn on light" etc.
|
# lights", "lights on", "lights on please", "turn on light" etc.
|
||||||
@hook(SpeechRecognizedEvent, phrase='turn on (the)? lights?')
|
@when(SpeechRecognizedEvent, phrase='turn on (the)? lights?')
|
||||||
def on_turn_on_lights(event: SpeechRecognizedEvent, **context):
|
def on_turn_on_lights(event: SpeechRecognizedEvent, **context):
|
||||||
run("light.hue.on")
|
run("light.hue.on")
|
||||||
|
|
||||||
|
@ -154,10 +154,10 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from platypush import hook, run
|
from platypush import when, run
|
||||||
from platypush.message.event.assistant import SpeechRecognizedEvent
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
|
|
||||||
@hook(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
@when(SpeechRecognizedEvent, phrase='play ${title} by ${artist}')
|
||||||
def on_play_track_command(
|
def on_play_track_command(
|
||||||
event: SpeechRecognizedEvent, title: str, artist: str, **context
|
event: SpeechRecognizedEvent, title: str, artist: str, **context
|
||||||
):
|
):
|
||||||
|
@ -227,10 +227,10 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from platypush import hook, run
|
from platypush import when, run
|
||||||
from platypush.message.event.assistant import IntentRecognizedEvent
|
from platypush.message.event.assistant import IntentRecognizedEvent
|
||||||
|
|
||||||
@hook(IntentRecognizedEvent, intent='lights_ctrl', slots={'state': 'on'})
|
@when(IntentRecognizedEvent, intent='lights_ctrl', slots={'state': 'on'})
|
||||||
def on_turn_on_lights(event: IntentRecognizedEvent, **context):
|
def on_turn_on_lights(event: IntentRecognizedEvent, **context):
|
||||||
room = event.slots.get('room')
|
room = event.slots.get('room')
|
||||||
if room:
|
if room:
|
||||||
|
@ -255,10 +255,10 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
from platypush import hook, run
|
from platypush import when, run
|
||||||
from platypush.message.event.assistant import SpeechRecognizedEvent
|
from platypush.message.event.assistant import SpeechRecognizedEvent
|
||||||
|
|
||||||
@hook(SpeechRecognizedEvent, phrase='turn ${state} (the)? ${room} lights?')
|
@when(SpeechRecognizedEvent, phrase='turn ${state} (the)? ${room} lights?')
|
||||||
def on_turn_on_lights(event: SpeechRecognizedEvent, phrase, room, **context):
|
def on_turn_on_lights(event: SpeechRecognizedEvent, phrase, room, **context):
|
||||||
if room:
|
if room:
|
||||||
run("light.hue.on", groups=[room])
|
run("light.hue.on", groups=[room])
|
||||||
|
@ -331,7 +331,7 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
(re.compile(r".*"), ai_assist),
|
(re.compile(r".*"), ai_assist),
|
||||||
)
|
)
|
||||||
|
|
||||||
@hook(SpeechRecognizedEvent)
|
@when(SpeechRecognizedEvent)
|
||||||
def on_speech_recognized(event, **kwargs):
|
def on_speech_recognized(event, **kwargs):
|
||||||
for pattern, command in hooks:
|
for pattern, command in hooks:
|
||||||
if pattern.search(event.phrase):
|
if pattern.search(event.phrase):
|
||||||
|
@ -339,7 +339,7 @@ class AssistantPicovoicePlugin(AssistantPlugin, RunnablePlugin):
|
||||||
command(event, **kwargs)
|
command(event, **kwargs)
|
||||||
break
|
break
|
||||||
|
|
||||||
@hook(ResponseEndEvent)
|
@when(ResponseEndEvent)
|
||||||
def on_response_end(event: ResponseEndEvent, **__):
|
def on_response_end(event: ResponseEndEvent, **__):
|
||||||
# Check if the response is a question and start a follow-on turn if so.
|
# Check if the response is a question and start a follow-on turn if so.
|
||||||
# Note that the ``openai`` plugin by default is configured to keep
|
# Note that the ``openai`` plugin by default is configured to keep
|
||||||
|
|
|
@ -3,18 +3,29 @@ import os
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from requests.auth import HTTPBasicAuth
|
from requests.auth import HTTPBasicAuth
|
||||||
from typing import Optional, Union, Dict, List, Any
|
from typing import Optional, Sequence, Union, Dict, List, Any
|
||||||
|
|
||||||
from platypush.message.response.camera.android import AndroidCameraStatusResponse, AndroidCameraStatusListResponse, \
|
|
||||||
AndroidCameraPictureResponse
|
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
from platypush.schemas.camera.android.ipcam import CameraStatusSchema
|
||||||
|
|
||||||
|
|
||||||
class AndroidIpcam:
|
class AndroidIpcam:
|
||||||
|
"""
|
||||||
|
IPCam camera configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
args = {}
|
args = {}
|
||||||
|
|
||||||
def __init__(self, name: str, host: str, port: int = 8080, username: Optional[str] = None,
|
def __init__(
|
||||||
password: Optional[str] = None, timeout: int = 10, ssl: bool = True):
|
self,
|
||||||
|
name: str,
|
||||||
|
host: str,
|
||||||
|
port: int = 8080,
|
||||||
|
username: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
timeout: int = 10,
|
||||||
|
ssl: bool = True,
|
||||||
|
):
|
||||||
self.args = {
|
self.args = {
|
||||||
'name': name,
|
'name': name,
|
||||||
'host': host,
|
'host': host,
|
||||||
|
@ -41,12 +52,13 @@ class AndroidIpcam:
|
||||||
self.args[key] = value
|
self.args[key] = value
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return json.dumps(getattr(self, 'args') or {})
|
return json.dumps(self.args or {})
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def base_url(self) -> str:
|
def base_url(self) -> str:
|
||||||
return 'http{ssl}://{host}:{port}/'.format(
|
return 'http{ssl}://{host}:{port}/'.format(
|
||||||
ssl=('s' if self.ssl else ''), host=self.host, port=self.port)
|
ssl=('s' if self.ssl else ''), host=self.host, port=self.port
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stream_url(self) -> str:
|
def stream_url(self) -> str:
|
||||||
|
@ -67,16 +79,20 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
`IPCam <https://play.google.com/store/apps/details?id=com.pas.webcam>`_.
|
`IPCam <https://play.google.com/store/apps/details?id=com.pas.webcam>`_.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(
|
||||||
host: Optional[str] = None,
|
self,
|
||||||
port: Optional[int] = 8080,
|
name: Optional[str] = None,
|
||||||
username: Optional[str] = None,
|
host: Optional[str] = None,
|
||||||
password: Optional[str] = None,
|
port: int = 8080,
|
||||||
timeout: int = 10,
|
username: Optional[str] = None,
|
||||||
ssl: bool = True,
|
password: Optional[str] = None,
|
||||||
cameras: Optional[Dict[str, Dict[str, Any]]] = None,
|
timeout: int = 10,
|
||||||
**kwargs):
|
ssl: bool = True,
|
||||||
|
cameras: Optional[Sequence[dict]] = None,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
|
:param name: Custom name for the default camera (default: IP/hostname)
|
||||||
:param host: Camera host name or address
|
:param host: Camera host name or address
|
||||||
:param port: Camera port
|
:param port: Camera port
|
||||||
:param username: Camera username, if set
|
:param username: Camera username, if set
|
||||||
|
@ -84,28 +100,44 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
:param timeout: Connection timeout
|
:param timeout: Connection timeout
|
||||||
:param ssl: Use HTTPS instead of HTTP
|
:param ssl: Use HTTPS instead of HTTP
|
||||||
:param cameras: Alternatively, you can specify a list of IPCam cameras as a
|
:param cameras: Alternatively, you can specify a list of IPCam cameras as a
|
||||||
name->dict mapping. The keys will be unique names used to identify your
|
list of objects with ``name``, ``host``, ``port``, ``username``,
|
||||||
cameras, the values will contain dictionaries containing `host, `port`,
|
``password``, ``timeout`` and ``ssl`` attributes.
|
||||||
`username`, `password`, `timeout` and `ssl` attributes for each camera.
|
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.cameras: List[AndroidIpcam] = []
|
self.cameras: List[AndroidIpcam] = []
|
||||||
self._camera_name_to_idx: Dict[str, int] = {}
|
self._camera_name_to_idx: Dict[str, int] = {}
|
||||||
|
|
||||||
if not cameras:
|
if not cameras:
|
||||||
camera = AndroidIpcam(name=host, host=host, port=port, username=username,
|
assert host, 'You need to specify at least one camera'
|
||||||
password=password, timeout=timeout, ssl=ssl)
|
name = name or host
|
||||||
|
camera = AndroidIpcam(
|
||||||
|
name=name,
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
timeout=timeout,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
self.cameras.append(camera)
|
self.cameras.append(camera)
|
||||||
self._camera_name_to_idx[host] = 0
|
self._camera_name_to_idx[name] = 0
|
||||||
else:
|
else:
|
||||||
for name, camera in cameras.items():
|
for camera in cameras:
|
||||||
camera = AndroidIpcam(name=name, host=camera['host'], port=camera.get('port', port),
|
assert 'host' in camera, 'You need to specify the host for each camera'
|
||||||
username=camera.get('username'), password=camera.get('password'),
|
name = camera.get('name', camera['host'])
|
||||||
timeout=camera.get('timeout', timeout), ssl=camera.get('ssl', ssl))
|
camera = AndroidIpcam(
|
||||||
|
name=name,
|
||||||
|
host=camera['host'],
|
||||||
|
port=camera.get('port', port),
|
||||||
|
username=camera.get('username'),
|
||||||
|
password=camera.get('password'),
|
||||||
|
timeout=camera.get('timeout', timeout),
|
||||||
|
ssl=camera.get('ssl', ssl),
|
||||||
|
)
|
||||||
self._camera_name_to_idx[name] = len(self.cameras)
|
self._camera_name_to_idx[name] = len(self.cameras)
|
||||||
self.cameras.append(camera)
|
self.cameras.append(camera)
|
||||||
|
|
||||||
def _get_camera(self, camera: Union[int, str] = None) -> AndroidIpcam:
|
def _get_camera(self, camera: Optional[Union[int, str]] = None) -> AndroidIpcam:
|
||||||
if not camera:
|
if not camera:
|
||||||
camera = 0
|
camera = 0
|
||||||
|
|
||||||
|
@ -114,10 +146,14 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
|
|
||||||
return self.cameras[self._camera_name_to_idx[camera]]
|
return self.cameras[self._camera_name_to_idx[camera]]
|
||||||
|
|
||||||
def _exec(self, url: str, camera: Union[int, str] = None, *args, **kwargs) -> Union[Dict[str, Any], bool]:
|
def _exec(
|
||||||
|
self, url: str, *args, camera: Optional[Union[int, str]] = None, **kwargs
|
||||||
|
) -> Union[Dict[str, Any], bool]:
|
||||||
cam = self._get_camera(camera)
|
cam = self._get_camera(camera)
|
||||||
url = cam.base_url + url
|
url = cam.base_url + url
|
||||||
response = requests.get(url, auth=cam.auth, timeout=cam.timeout, verify=False, *args, **kwargs)
|
response = requests.get(
|
||||||
|
url, auth=cam.auth, timeout=cam.timeout, verify=False, *args, **kwargs
|
||||||
|
)
|
||||||
response.raise_for_status()
|
response.raise_for_status()
|
||||||
|
|
||||||
if response.headers.get('content-type') == 'application/json':
|
if response.headers.get('content-type') == 'application/json':
|
||||||
|
@ -125,87 +161,138 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
|
|
||||||
return response.text.find('Ok') != -1
|
return response.text.find('Ok') != -1
|
||||||
|
|
||||||
@action
|
def _change_setting(
|
||||||
def change_setting(self, key: str, value: Union[str, int, bool], camera: Union[int, str] = None) -> bool:
|
self,
|
||||||
"""
|
key: str,
|
||||||
Change a setting.
|
value: Union[str, int, bool],
|
||||||
:param key: Setting name
|
camera: Optional[Union[int, str]] = None,
|
||||||
:param value: Setting value
|
) -> bool:
|
||||||
:param camera: Camera index or configured name
|
|
||||||
:return: True on success, False otherwise
|
|
||||||
"""
|
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
payload = "on" if value else "off"
|
payload = "on" if value else "off"
|
||||||
else:
|
else:
|
||||||
payload = value
|
payload = value
|
||||||
|
|
||||||
return self._exec("settings/{key}?set={payload}".format(key=key, payload=payload), camera=camera)
|
return bool(
|
||||||
|
self._exec(
|
||||||
|
"settings/{key}?set={payload}".format(key=key, payload=payload),
|
||||||
|
camera=camera,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def status(self, camera: Union[int, str] = None) -> AndroidCameraStatusListResponse:
|
def change_setting(
|
||||||
|
self,
|
||||||
|
key: str,
|
||||||
|
value: Union[str, int, bool],
|
||||||
|
camera: Optional[Union[int, str]] = None,
|
||||||
|
) -> bool:
|
||||||
|
"""
|
||||||
|
Change a setting.
|
||||||
|
|
||||||
|
:param key: Setting name
|
||||||
|
:param value: Setting value
|
||||||
|
:param camera: Camera index or configured name
|
||||||
|
:return: True on success, False otherwise
|
||||||
|
"""
|
||||||
|
return self._change_setting(key, value, camera=camera)
|
||||||
|
|
||||||
|
@action
|
||||||
|
def status(self, camera: Optional[Union[int, str]] = None) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
:param camera: Camera index or name (default: status of all the cameras)
|
:param camera: Camera index or name (default: status of all the cameras)
|
||||||
:return: True if the camera is available, False otherwise
|
:return: .. schema:: camera.android.ipcam.CameraStatusSchema(many=True)
|
||||||
"""
|
"""
|
||||||
cameras = self._camera_name_to_idx.keys() if camera is None else [camera]
|
cameras = self._camera_name_to_idx.keys() if camera is None else [camera]
|
||||||
statuses = []
|
statuses = []
|
||||||
|
|
||||||
for c in cameras:
|
for c in cameras:
|
||||||
try:
|
try:
|
||||||
if isinstance(camera, int):
|
print('****** HERE ******')
|
||||||
|
print(self._camera_name_to_idx)
|
||||||
|
if isinstance(c, int):
|
||||||
cam = self.cameras[c]
|
cam = self.cameras[c]
|
||||||
else:
|
else:
|
||||||
cam = self.cameras[self._camera_name_to_idx[c]]
|
cam = self.cameras[self._camera_name_to_idx[c]]
|
||||||
|
|
||||||
status_data = self._exec('status.json', params={'show_avail': 1}, camera=cam.name).get('curvals', {})
|
response = self._exec(
|
||||||
status = AndroidCameraStatusResponse(
|
'status.json', params={'show_avail': 1}, camera=cam.name
|
||||||
name=cam.name,
|
)
|
||||||
stream_url=cam.stream_url,
|
assert isinstance(response, dict), f'Invalid response: {response}'
|
||||||
image_url=cam.image_url,
|
|
||||||
audio_url=cam.audio_url,
|
status_data = response.get('curvals', {})
|
||||||
**{k: v for k, v in status_data.items()
|
status = CameraStatusSchema().dump(
|
||||||
if k in AndroidCameraStatusResponse.attrs})
|
{
|
||||||
|
'name': cam.name,
|
||||||
|
'stream_url': cam.stream_url,
|
||||||
|
'image_url': cam.image_url,
|
||||||
|
'audio_url': cam.audio_url,
|
||||||
|
**status_data,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
statuses.append(status)
|
statuses.append(status)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.warning('Could not get the status of {}: {}'.format(c, str(e)))
|
self.logger.warning(
|
||||||
|
'Could not get the status of %s: %s: %s', c, type(e), e
|
||||||
|
)
|
||||||
|
|
||||||
return AndroidCameraStatusListResponse(statuses)
|
return statuses
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_front_facing_camera(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_front_facing_camera(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable the front-facing camera."""
|
"""Enable/disable the front-facing camera."""
|
||||||
return self.change_setting('ffc', activate, camera=camera)
|
return self._change_setting('ffc', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_torch(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_torch(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable the torch."""
|
"""Enable/disable the torch."""
|
||||||
url = 'enabletorch' if activate else 'disabletorch'
|
url = 'enabletorch' if activate else 'disabletorch'
|
||||||
return self._exec(url, camera=camera)
|
return bool(self._exec(url, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_focus(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_focus(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable the focus."""
|
"""Enable/disable the focus."""
|
||||||
url = 'focus' if activate else 'nofocus'
|
url = 'focus' if activate else 'nofocus'
|
||||||
return self._exec(url, camera=camera)
|
return bool(self._exec(url, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def start_recording(self, tag: Optional[str] = None, camera: Union[int, str] = None) -> bool:
|
def start_recording(
|
||||||
|
self, tag: Optional[str] = None, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Start recording."""
|
"""Start recording."""
|
||||||
params = {'force': 1}
|
params = {
|
||||||
if tag:
|
'force': 1,
|
||||||
params['tag'] = tag
|
**({'tag': tag} if tag else {}),
|
||||||
|
}
|
||||||
return self._exec('startvideo', params=params, camera=camera)
|
return bool(self._exec('startvideo', params=params, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def stop_recording(self, camera: Union[int, str] = None) -> bool:
|
def stop_recording(self, camera: Optional[Union[int, str]] = None) -> bool:
|
||||||
"""Stop recording."""
|
"""Stop recording."""
|
||||||
return self._exec('stopvideo', params={'force': 1}, camera=camera)
|
return bool(self._exec('stopvideo', params={'force': 1}, camera=camera))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def take_picture(self, image_file: str, camera: Union[int, str] = None) -> AndroidCameraPictureResponse:
|
def take_picture(
|
||||||
"""Take a picture and save it on the local device."""
|
self, image_file: str, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Take a picture and save it on the local device.
|
||||||
|
|
||||||
|
:return: dict
|
||||||
|
|
||||||
|
.. code-block:: json
|
||||||
|
|
||||||
|
{
|
||||||
|
"image_file": "/path/to/image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
cam = self._get_camera(camera)
|
cam = self._get_camera(camera)
|
||||||
image_file = os.path.abspath(os.path.expanduser(image_file))
|
image_file = os.path.abspath(os.path.expanduser(image_file))
|
||||||
os.makedirs(os.path.dirname(image_file), exist_ok=True)
|
os.makedirs(os.path.dirname(image_file), exist_ok=True)
|
||||||
|
@ -214,47 +301,64 @@ class CameraAndroidIpcamPlugin(Plugin):
|
||||||
|
|
||||||
with open(image_file, 'wb') as f:
|
with open(image_file, 'wb') as f:
|
||||||
f.write(response.content)
|
f.write(response.content)
|
||||||
return AndroidCameraPictureResponse(image_file=image_file)
|
|
||||||
|
return {'image_file': image_file}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_night_vision(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_night_vision(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable night vision."""
|
"""Enable/disable night vision."""
|
||||||
return self.change_setting('night_vision', activate, camera=camera)
|
return self._change_setting('night_vision', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_overlay(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_overlay(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable video overlay."""
|
"""Enable/disable video overlay."""
|
||||||
return self.change_setting('overlay', activate, camera=camera)
|
return self._change_setting('overlay', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_gps(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_gps(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable GPS."""
|
"""Enable/disable GPS."""
|
||||||
return self.change_setting('gps_active', activate, camera=camera)
|
return self._change_setting('gps_active', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_quality(self, quality: int = 100, camera: Union[int, str] = None) -> bool:
|
def set_quality(
|
||||||
|
self, quality: int = 100, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Set video quality."""
|
"""Set video quality."""
|
||||||
return self.change_setting('quality', int(quality), camera=camera)
|
return self._change_setting('quality', int(quality), camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_motion_detect(self, activate: bool = True, camera: Union[int, str] = None) -> bool:
|
def set_motion_detect(
|
||||||
|
self, activate: bool = True, camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Enable/disable motion detect."""
|
"""Enable/disable motion detect."""
|
||||||
return self.change_setting('motion_detect', activate, camera=camera)
|
return self._change_setting('motion_detect', activate, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_orientation(self, orientation: str = 'landscape', camera: Union[int, str] = None) -> bool:
|
def set_orientation(
|
||||||
|
self, orientation: str = 'landscape', camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Set video orientation."""
|
"""Set video orientation."""
|
||||||
return self.change_setting('orientation', orientation, camera=camera)
|
return self._change_setting('orientation', orientation, camera=camera)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_zoom(self, zoom: float, camera: Union[int, str] = None) -> bool:
|
def set_zoom(self, zoom: float, camera: Optional[Union[int, str]] = None) -> bool:
|
||||||
"""Set the zoom level."""
|
"""Set the zoom level."""
|
||||||
return self._exec('settings/ptz', params={'zoom': float(zoom)}, camera=camera)
|
return bool(
|
||||||
|
self._exec('settings/ptz', params={'zoom': float(zoom)}, camera=camera)
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def set_scenemode(self, scenemode: str = 'auto', camera: Union[int, str] = None) -> bool:
|
def set_scenemode(
|
||||||
|
self, scenemode: str = 'auto', camera: Optional[Union[int, str]] = None
|
||||||
|
) -> bool:
|
||||||
"""Set video orientation."""
|
"""Set video orientation."""
|
||||||
return self.change_setting('scenemode', scenemode, camera=camera)
|
return self._change_setting('scenemode', scenemode, camera=camera)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
import hashlib
|
import hashlib
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from enum import Enum
|
from typing import Any, Dict, Optional, Union, List
|
||||||
from typing import Optional, Union, List
|
|
||||||
|
|
||||||
from platypush.message.response.pihole import PiholeStatusResponse
|
from platypush.schemas.pihole import PiholeStatusSchema
|
||||||
from platypush.plugins import Plugin, action
|
from platypush.plugins import Plugin, action
|
||||||
|
|
||||||
|
|
||||||
class PiholeStatus(Enum):
|
|
||||||
ENABLED = 'enabled'
|
|
||||||
DISABLED = 'disabled'
|
|
||||||
|
|
||||||
|
|
||||||
class PiholePlugin(Plugin):
|
class PiholePlugin(Plugin):
|
||||||
"""
|
"""
|
||||||
Plugin for interacting with a `Pi-Hole <https://pi-hole.net>`_ DNS server for advertisement and content blocking.
|
Plugin for interacting with a `Pi-Hole <https://pi-hole.net>`_ DNS server for advertisement and content blocking.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server: Optional[str] = None, password: Optional[str] = None, api_key: Optional[str] = None,
|
def __init__(
|
||||||
ssl: bool = False, verify_ssl: bool = True, **kwargs):
|
self,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: bool = False,
|
||||||
|
verify_ssl: bool = True,
|
||||||
|
timeout: int = 10,
|
||||||
|
**kwargs,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
:param server: Default Pi-hole server IP address.
|
:param server: Default Pi-hole server IP address.
|
||||||
:param password: Password for the default Pi-hole server.
|
:param password: Password for the default Pi-hole server.
|
||||||
|
@ -27,6 +29,7 @@ class PiholePlugin(Plugin):
|
||||||
http://pi-hole-server/admin/scripts/pi-hole/php/api_token.php
|
http://pi-hole-server/admin/scripts/pi-hole/php/api_token.php
|
||||||
:param ssl: Set to true if the host uses HTTPS (default: False).
|
:param ssl: Set to true if the host uses HTTPS (default: False).
|
||||||
:param verify_ssl: Set to False to disable SSL certificate check.
|
:param verify_ssl: Set to False to disable SSL certificate check.
|
||||||
|
:param timeout: Default timeout for the HTTP requests.
|
||||||
"""
|
"""
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
self.server = server
|
self.server = server
|
||||||
|
@ -34,23 +37,36 @@ class PiholePlugin(Plugin):
|
||||||
self.api_key = api_key
|
self.api_key = api_key
|
||||||
self.ssl = ssl
|
self.ssl = ssl
|
||||||
self.verify_ssl = verify_ssl
|
self.verify_ssl = verify_ssl
|
||||||
|
self.timeout = timeout
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _get_token(password: Optional[str] = None, api_key: Optional[str] = None) -> str:
|
def _get_token(
|
||||||
|
password: Optional[str] = None, api_key: Optional[str] = None
|
||||||
|
) -> str:
|
||||||
if not password:
|
if not password:
|
||||||
return api_key or ''
|
return api_key or ''
|
||||||
return hashlib.sha256(hashlib.sha256(str(password).encode()).hexdigest().encode()).hexdigest() # lgtm [py/weak-sensitive-data-hashing]
|
return hashlib.sha256(
|
||||||
|
hashlib.sha256(str(password).encode()).hexdigest().encode()
|
||||||
|
).hexdigest() # lgtm [py/weak-sensitive-data-hashing]
|
||||||
|
|
||||||
def _get_url(self, name: str, server: Optional[str] = None, password: Optional[str] = None,
|
def _get_url(
|
||||||
ssl: Optional[bool] = None, api_key: Optional[str] = None, **kwargs) -> str:
|
self,
|
||||||
|
name: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
**kwargs,
|
||||||
|
) -> str:
|
||||||
if not server:
|
if not server:
|
||||||
server = self.server
|
server = self.server
|
||||||
password = password or self.password
|
password = password or self.password
|
||||||
api_key = api_key or self.api_key
|
api_key = api_key or self.api_key
|
||||||
ssl = ssl if ssl is not None else self.ssl
|
ssl = ssl if ssl is not None else self.ssl
|
||||||
|
|
||||||
args = '&'.join(['{key}={value}'.format(key=key, value=value) for key, value in kwargs.items()
|
args = '&'.join(
|
||||||
if value is not None])
|
[f'{key}={value}' for key, value in kwargs.items() if value is not None]
|
||||||
|
)
|
||||||
|
|
||||||
if args:
|
if args:
|
||||||
args = '&' + args
|
args = '&' + args
|
||||||
|
@ -59,17 +75,20 @@ class PiholePlugin(Plugin):
|
||||||
if token:
|
if token:
|
||||||
token = '&auth=' + token
|
token = '&auth=' + token
|
||||||
|
|
||||||
return 'http{ssl}://{server}/admin/api.php?{name}{token}{args}'.format(
|
return f'http{"s" if ssl else ""}://{server}/admin/api.php?{name}{token}{args}'
|
||||||
ssl='s' if ssl else '', server=server, name=name, token=token, args=args)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _normalize_number(n: Union[str, int]):
|
def _normalize_number(n: Union[str, int]):
|
||||||
return int(str(n).replace(',', ''))
|
return int(str(n).replace(',', ''))
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def status(self, server: Optional[str] = None, password: Optional[str] = None, api_key: Optional[str] = None,
|
def status(
|
||||||
ssl: bool = None) \
|
self,
|
||||||
-> PiholeStatusResponse:
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Get the status and statistics of a running Pi-hole server.
|
Get the status and statistics of a running Pi-hole server.
|
||||||
|
|
||||||
|
@ -77,32 +96,64 @@ class PiholePlugin(Plugin):
|
||||||
:param password: Server password (default: default configured ``password`` value).
|
:param password: Server password (default: default configured ``password`` value).
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
:return: :class:`platypush.message.response.pihole.PiholeStatusResponse`
|
:return: .. schema:: pihole.PiholeStatusSchema
|
||||||
"""
|
"""
|
||||||
status = requests.get(self._get_url('summary', server=server, password=password, api_key=api_key,
|
status = requests.get(
|
||||||
ssl=ssl), verify=self.verify_ssl).json()
|
self._get_url(
|
||||||
version = requests.get(self._get_url('versions', server=server, password=password, api_key=api_key,
|
'summary', server=server, password=password, api_key=api_key, ssl=ssl
|
||||||
ssl=ssl), verify=self.verify_ssl).json()
|
),
|
||||||
|
verify=self.verify_ssl,
|
||||||
|
timeout=self.timeout,
|
||||||
|
).json()
|
||||||
|
|
||||||
return PiholeStatusResponse(
|
version = requests.get(
|
||||||
server=server or self.server,
|
self._get_url(
|
||||||
status=PiholeStatus(status.get('status')).value,
|
'versions', server=server, password=password, api_key=api_key, ssl=ssl
|
||||||
ads_percentage=float(status.get('ads_percentage_today')),
|
),
|
||||||
blocked=self._normalize_number(status.get('ads_blocked_today')),
|
verify=self.verify_ssl,
|
||||||
cached=self._normalize_number(status.get('queries_cached')),
|
timeout=self.timeout,
|
||||||
domain_count=self._normalize_number(status.get('domains_being_blocked')),
|
).json()
|
||||||
forwarded=self._normalize_number(status.get('queries_forwarded')),
|
|
||||||
queries=self._normalize_number(status.get('dns_queries_today')),
|
return dict(
|
||||||
total_clients=self._normalize_number(status.get('clients_ever_seen')),
|
PiholeStatusSchema().dump(
|
||||||
total_queries=self._normalize_number(status.get('dns_queries_all_types')),
|
{
|
||||||
unique_clients=self._normalize_number(status.get('unique_clients')),
|
'server': server or self.server,
|
||||||
unique_domains=self._normalize_number(status.get('unique_domains')),
|
'status': status.get('status'),
|
||||||
version=version.get('core_current'),
|
'ads_percentage': float(status.get('ads_percentage_today')),
|
||||||
|
'blocked': self._normalize_number(status.get('ads_blocked_today')),
|
||||||
|
'cached': self._normalize_number(status.get('queries_cached')),
|
||||||
|
'domain_count': self._normalize_number(
|
||||||
|
status.get('domains_being_blocked')
|
||||||
|
),
|
||||||
|
'forwarded': self._normalize_number(
|
||||||
|
status.get('queries_forwarded')
|
||||||
|
),
|
||||||
|
'queries': self._normalize_number(status.get('dns_queries_today')),
|
||||||
|
'total_clients': self._normalize_number(
|
||||||
|
status.get('clients_ever_seen')
|
||||||
|
),
|
||||||
|
'total_queries': self._normalize_number(
|
||||||
|
status.get('dns_queries_all_types')
|
||||||
|
),
|
||||||
|
'unique_clients': self._normalize_number(
|
||||||
|
status.get('unique_clients')
|
||||||
|
),
|
||||||
|
'unique_domains': self._normalize_number(
|
||||||
|
status.get('unique_domains')
|
||||||
|
),
|
||||||
|
'version': version.get('core_current'),
|
||||||
|
}
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def enable(self, server: Optional[str] = None, password: Optional[str] = None, api_key: Optional[str] = None,
|
def enable(
|
||||||
ssl: bool = None):
|
self,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Enable a Pi-hole server.
|
Enable a Pi-hole server.
|
||||||
|
|
||||||
|
@ -111,20 +162,31 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
response = requests.get(self._get_url('enable', server=server, password=password, api_key=api_key, ssl=ssl),
|
response = requests.get(
|
||||||
verify=self.verify_ssl)
|
self._get_url(
|
||||||
|
'enable', server=server, password=password, api_key=api_key, ssl=ssl
|
||||||
|
),
|
||||||
|
verify=self.verify_ssl,
|
||||||
|
timeout=self.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status = (response.json() or {}).get('status')
|
status = (response.json() or {}).get('status')
|
||||||
assert status == 'enabled', 'Wrong credentials'
|
assert status == 'enabled', 'Wrong credentials'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise AssertionError('Could not enable the server: {}'.format(response.text or str(e)))
|
raise AssertionError(f'Could not enable the server: {response.text or e}')
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def disable(self, server: Optional[str] = None, password: Optional[str] = None, api_key: Optional[str] = None,
|
def disable(
|
||||||
seconds: Optional[int] = None, ssl: bool = None):
|
self,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
seconds: Optional[int] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Disable a Pi-hole server.
|
Disable a Pi-hole server.
|
||||||
|
|
||||||
|
@ -135,46 +197,79 @@ class PiholePlugin(Plugin):
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
if seconds:
|
if seconds:
|
||||||
response = requests.get(self._get_url('', server=server, password=password, api_key=api_key,
|
response = requests.get(
|
||||||
ssl=ssl, disable=seconds), verify=self.verify_ssl)
|
self._get_url(
|
||||||
|
'',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
disable=seconds,
|
||||||
|
),
|
||||||
|
verify=self.verify_ssl,
|
||||||
|
timeout=self.timeout,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
response = requests.get(self._get_url('disable', server=server, password=password, api_key=api_key,
|
response = requests.get(
|
||||||
ssl=ssl), verify=self.verify_ssl)
|
self._get_url(
|
||||||
|
'disable',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
),
|
||||||
|
verify=self.verify_ssl,
|
||||||
|
timeout=self.timeout,
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
status = (response.json() or {}).get('status')
|
status = (response.json() or {}).get('status')
|
||||||
assert status == 'disabled', 'Wrong credentials'
|
assert status == 'disabled', 'Wrong credentials'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise AssertionError('Could not disable the server: {}'.format(response.text or str(e)))
|
raise AssertionError(f'Could not disable the server: {response.text or e}')
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
def _list_manage(self, domain: str, list_name: str, endpoint: str, server: Optional[str] = None,
|
def _list_manage(
|
||||||
password: Optional[str] = None, api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
data = {
|
domain: str,
|
||||||
'list': list_name,
|
list_name: str,
|
||||||
'domain': domain
|
endpoint: str,
|
||||||
}
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
|
data: Dict[str, Any] = {'list': list_name, 'domain': domain}
|
||||||
|
|
||||||
if password or self.password:
|
if password or self.password:
|
||||||
data['pw'] = password or self.password
|
data['pw'] = password or self.password
|
||||||
elif api_key or self.api_key:
|
elif api_key or self.api_key:
|
||||||
data['auth'] = api_key or self.api_key
|
data['auth'] = api_key or self.api_key
|
||||||
|
|
||||||
base_url = "http{ssl}://{host}/admin/scripts/pi-hole/php/{endpoint}.php".format(
|
base_url = (
|
||||||
ssl='s' if ssl or self.ssl else '', host=server or self.server, endpoint=endpoint
|
f"http{'s' if ssl or (ssl is None and self.ssl) else ''}://"
|
||||||
|
f"{server or self.server}/admin/scripts/pi-hole/php/{endpoint}.php"
|
||||||
)
|
)
|
||||||
|
|
||||||
with requests.session() as s:
|
with requests.session() as s:
|
||||||
s.get(base_url, verify=self.verify_ssl)
|
s.get(base_url, verify=self.verify_ssl)
|
||||||
response = requests.post(base_url, data=data, verify=self.verify_ssl).text.strip()
|
response = requests.post(
|
||||||
|
base_url, data=data, verify=self.verify_ssl, timeout=self.timeout
|
||||||
|
).text.strip()
|
||||||
|
|
||||||
return {'response': response}
|
return {'response': response}
|
||||||
|
|
||||||
def _list_get(self, list_name: str, server: Optional[str] = None, ssl: bool = None) -> List[str]:
|
def _list_get(
|
||||||
response = requests.get("http{ssl}://{host}/admin/scripts/pi-hole/php/get.php?list={list}".format(
|
self, list_name: str, server: Optional[str] = None, ssl: Optional[bool] = None
|
||||||
ssl='s' if ssl or self.ssl else '', host=server or self.server, list=list_name
|
) -> List[str]:
|
||||||
), verify=self.verify_ssl).json()
|
response = requests.get(
|
||||||
|
f"http{'s' if ssl or (ssl is None and self.ssl) else ''}"
|
||||||
|
f"://{server or self.server}/admin/scripts/pi-hole/php/"
|
||||||
|
f"get.php?list={list_name}",
|
||||||
|
verify=self.verify_ssl,
|
||||||
|
timeout=10,
|
||||||
|
).json()
|
||||||
|
|
||||||
ret = set()
|
ret = set()
|
||||||
for ll in response:
|
for ll in response:
|
||||||
|
@ -182,7 +277,9 @@ class PiholePlugin(Plugin):
|
||||||
return list(ret)
|
return list(ret)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_blacklist(self, server: Optional[str] = None, ssl: bool = None) -> List[str]:
|
def get_blacklist(
|
||||||
|
self, server: Optional[str] = None, ssl: Optional[bool] = None
|
||||||
|
) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Get the content of the blacklist.
|
Get the content of the blacklist.
|
||||||
|
|
||||||
|
@ -192,7 +289,9 @@ class PiholePlugin(Plugin):
|
||||||
return self._list_get(list_name='black', server=server, ssl=ssl)
|
return self._list_get(list_name='black', server=server, ssl=ssl)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_whitelist(self, server: Optional[str] = None, ssl: bool = None) -> List[str]:
|
def get_whitelist(
|
||||||
|
self, server: Optional[str] = None, ssl: Optional[bool] = None
|
||||||
|
) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Get the content of the whitelist.
|
Get the content of the whitelist.
|
||||||
|
|
||||||
|
@ -202,7 +301,9 @@ class PiholePlugin(Plugin):
|
||||||
return self._list_get(list_name='white', server=server, ssl=ssl)
|
return self._list_get(list_name='white', server=server, ssl=ssl)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def get_list(self, list_name: str, server: Optional[str] = None, ssl: bool = None) -> List[str]:
|
def get_list(
|
||||||
|
self, list_name: str, server: Optional[str] = None, ssl: Optional[bool] = None
|
||||||
|
) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Get the content of a list stored on the server.
|
Get the content of a list stored on the server.
|
||||||
|
|
||||||
|
@ -213,8 +314,14 @@ class PiholePlugin(Plugin):
|
||||||
return self._list_get(list_name=list_name, server=server, ssl=ssl)
|
return self._list_get(list_name=list_name, server=server, ssl=ssl)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def blacklist_add(self, domain: str, server: Optional[str] = None, password: Optional[str] = None,
|
def blacklist_add(
|
||||||
api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
|
domain: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Add a domain to the blacklist.
|
Add a domain to the blacklist.
|
||||||
|
|
||||||
|
@ -224,12 +331,25 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
return self._list_manage(domain=domain, list_name='black', endpoint='add', server=server, password=password,
|
return self._list_manage(
|
||||||
api_key=api_key, ssl=ssl)
|
domain=domain,
|
||||||
|
list_name='black',
|
||||||
|
endpoint='add',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def blacklist_remove(self, domain: str, server: Optional[str] = None, password: Optional[str] = None,
|
def blacklist_remove(
|
||||||
api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
|
domain: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Remove a domain from the blacklist.
|
Remove a domain from the blacklist.
|
||||||
|
|
||||||
|
@ -239,12 +359,25 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
return self._list_manage(domain=domain, list_name='black', endpoint='sub', server=server, password=password,
|
return self._list_manage(
|
||||||
api_key=api_key, ssl=ssl)
|
domain=domain,
|
||||||
|
list_name='black',
|
||||||
|
endpoint='sub',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def whitelist_add(self, domain: str, server: Optional[str] = None, password: Optional[str] = None,
|
def whitelist_add(
|
||||||
api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
|
domain: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Add a domain to the whitelist.
|
Add a domain to the whitelist.
|
||||||
|
|
||||||
|
@ -254,12 +387,25 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
return self._list_manage(domain=domain, list_name='white', endpoint='add', server=server, password=password,
|
return self._list_manage(
|
||||||
api_key=api_key, ssl=ssl)
|
domain=domain,
|
||||||
|
list_name='white',
|
||||||
|
endpoint='add',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def whitelist_remove(self, domain: str, server: Optional[str] = None, password: Optional[str] = None,
|
def whitelist_remove(
|
||||||
api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
|
domain: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Remove a domain from the whitelist.
|
Remove a domain from the whitelist.
|
||||||
|
|
||||||
|
@ -269,12 +415,26 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
return self._list_manage(domain=domain, list_name='white', endpoint='sub', server=server, password=password,
|
return self._list_manage(
|
||||||
api_key=api_key, ssl=ssl)
|
domain=domain,
|
||||||
|
list_name='white',
|
||||||
|
endpoint='sub',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def list_add(self, list_name: str, domain: str, server: Optional[str] = None, password: Optional[str] = None,
|
def list_add(
|
||||||
api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
|
list_name: str,
|
||||||
|
domain: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Add a domain to a custom list stored on the server.
|
Add a domain to a custom list stored on the server.
|
||||||
|
|
||||||
|
@ -285,12 +445,26 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
return self._list_manage(domain=domain, list_name=list_name, endpoint='add', server=server, password=password,
|
return self._list_manage(
|
||||||
api_key=api_key, ssl=ssl)
|
domain=domain,
|
||||||
|
list_name=list_name,
|
||||||
|
endpoint='add',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
|
|
||||||
@action
|
@action
|
||||||
def list_remove(self, list_name: str, domain: str, server: Optional[str] = None, password: Optional[str] = None,
|
def list_remove(
|
||||||
api_key: Optional[str] = None, ssl: bool = None):
|
self,
|
||||||
|
list_name: str,
|
||||||
|
domain: str,
|
||||||
|
server: Optional[str] = None,
|
||||||
|
password: Optional[str] = None,
|
||||||
|
api_key: Optional[str] = None,
|
||||||
|
ssl: Optional[bool] = None,
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Remove a domain from a custom list stored on the server.
|
Remove a domain from a custom list stored on the server.
|
||||||
|
|
||||||
|
@ -301,8 +475,15 @@ class PiholePlugin(Plugin):
|
||||||
:param api_key: Server API key (default: default configured ``api_key`` value).
|
:param api_key: Server API key (default: default configured ``api_key`` value).
|
||||||
:param ssl: Set to True if the server uses SSL (default: False).
|
:param ssl: Set to True if the server uses SSL (default: False).
|
||||||
"""
|
"""
|
||||||
return self._list_manage(domain=domain, list_name=list_name, endpoint='sub', server=server, password=password,
|
return self._list_manage(
|
||||||
api_key=api_key, ssl=ssl)
|
domain=domain,
|
||||||
|
list_name=list_name,
|
||||||
|
endpoint='sub',
|
||||||
|
server=server,
|
||||||
|
password=password,
|
||||||
|
api_key=api_key,
|
||||||
|
ssl=ssl,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# vim:sw=4:ts=4:et:
|
# vim:sw=4:ts=4:et:
|
||||||
|
|
0
platypush/schemas/camera/android/__init__.py
Normal file
0
platypush/schemas/camera/android/__init__.py
Normal file
332
platypush/schemas/camera/android/ipcam.py
Normal file
332
platypush/schemas/camera/android/ipcam.py
Normal file
|
@ -0,0 +1,332 @@
|
||||||
|
from marshmallow import EXCLUDE, fields, pre_dump
|
||||||
|
from marshmallow.schema import Schema
|
||||||
|
from marshmallow.validate import OneOf
|
||||||
|
|
||||||
|
from platypush.schemas import StrippedString
|
||||||
|
|
||||||
|
|
||||||
|
class CameraStatusSchema(Schema):
|
||||||
|
"""
|
||||||
|
Schema for the camera status.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta: # type: ignore
|
||||||
|
"""
|
||||||
|
Exclude unknown fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
name = StrippedString(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Name or IP of the camera',
|
||||||
|
'example': 'Front Door',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
stream_url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'URL to the video stream',
|
||||||
|
'example': 'http://192.168.1.10:8080/video',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
image_url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'URL to get a snapshot from the camera',
|
||||||
|
'example': 'http://192.168.1.10:8080/photo.jpg',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_url = fields.Url(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'URL to get audio from the camera',
|
||||||
|
'example': 'http://192.168.1.10:8080/audio.wav',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
orientation = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Orientation of the camera',
|
||||||
|
'example': 'landscape',
|
||||||
|
'validate': OneOf(['landscape', 'portrait']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
idle = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Idle status of the camera',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_only = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the camera is in audio-only mode',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
overlay = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the camera is in overlay mode',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
quality = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Quality of the video stream, in percent',
|
||||||
|
'example': 49,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
night_vision = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether night vision is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
night_vision_average = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Average brightness for night vision',
|
||||||
|
'example': 2,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
night_vision_gain = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Brightness gain for night vision',
|
||||||
|
'example': 1.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ip_address = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'IP address of the camera',
|
||||||
|
'example': '192.168.1.10',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_limit = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Motion limit',
|
||||||
|
'example': 250,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_detect = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether motion detection is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
motion_display = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether motion display is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
gps_active = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether GPS is active',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
video_size = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Size of the video stream',
|
||||||
|
'example': '1920x1080',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
photo_size = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Size of the photo',
|
||||||
|
'example': '1920x1080',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
mirror_flip = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Mirror/flip mode',
|
||||||
|
'example': 'none',
|
||||||
|
'validate': OneOf(['none', 'horizontal', 'vertical', 'both']),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
video_connections = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of active video connections',
|
||||||
|
'example': 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
audio_connections = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of active audio connections',
|
||||||
|
'example': 0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
zoom = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Zoom level, as a percentage',
|
||||||
|
'example': 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
crop_x = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Crop X, as a percentage',
|
||||||
|
'example': 50,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
crop_y = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Crop Y, as a percentage',
|
||||||
|
'example': 50,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
coloreffect = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Color effect',
|
||||||
|
'example': 'none',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
scenemode = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Scene mode',
|
||||||
|
'example': 'auto',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
focusmode = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Focus mode',
|
||||||
|
'example': 'continuous-video',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
whitebalance = fields.Str(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'White balance',
|
||||||
|
'example': 'auto',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
flashmode = fields.Str(
|
||||||
|
required=True,
|
||||||
|
validate=OneOf(['off', 'on', 'auto']),
|
||||||
|
metadata={
|
||||||
|
'description': 'Flash mode',
|
||||||
|
'example': 'off',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
torch = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the torch is enabled',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
focus_distance = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Focus distance',
|
||||||
|
'example': 0.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
focal_length = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Focal length',
|
||||||
|
'example': 4.25,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
aperture = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Aperture',
|
||||||
|
'example': 1.7,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
filter_density = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Filter density',
|
||||||
|
'example': 0.0,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
exposure_ns = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Exposure time in nanoseconds',
|
||||||
|
'example': 9384,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
iso = fields.Int(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'ISO',
|
||||||
|
'example': 100,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
manual_sensor = fields.Bool(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Whether the sensor is in manual mode',
|
||||||
|
'example': False,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
@pre_dump
|
||||||
|
def normalize_bools(self, data, **_):
|
||||||
|
for k, v in data.items():
|
||||||
|
if k != 'flashmode' and isinstance(v, str) and v.lower() in ['on', 'off']:
|
||||||
|
data[k] = v.lower() == 'on'
|
||||||
|
return data
|
140
platypush/schemas/pihole.py
Normal file
140
platypush/schemas/pihole.py
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
from marshmallow import EXCLUDE, fields
|
||||||
|
from marshmallow.schema import Schema
|
||||||
|
from marshmallow.validate import OneOf
|
||||||
|
|
||||||
|
from platypush.schemas import StrippedString
|
||||||
|
|
||||||
|
|
||||||
|
class PiholeStatusSchema(Schema):
|
||||||
|
"""
|
||||||
|
Schema for a Pi-hole status response.
|
||||||
|
|
||||||
|
|
||||||
|
"output": {
|
||||||
|
"server": "dns.fabiomanganiello.com",
|
||||||
|
"status": "enabled",
|
||||||
|
"ads_percentage": 6.7,
|
||||||
|
"blocked": 37191,
|
||||||
|
"cached": 361426,
|
||||||
|
"domain_count": 1656690,
|
||||||
|
"forwarded": 150187,
|
||||||
|
"queries": 552076,
|
||||||
|
"total_clients": 57,
|
||||||
|
"total_queries": 552076,
|
||||||
|
"unique_clients": 41,
|
||||||
|
"unique_domains": 39348,
|
||||||
|
"version": "5.18.2"
|
||||||
|
},
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta: # type: ignore
|
||||||
|
"""
|
||||||
|
Exclude unknown fields.
|
||||||
|
"""
|
||||||
|
|
||||||
|
unknown = EXCLUDE
|
||||||
|
|
||||||
|
server = StrippedString(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Hostname or IP of the Pi-hole server',
|
||||||
|
'example': '192.168.1.254',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
status = fields.String(
|
||||||
|
required=True,
|
||||||
|
validate=OneOf(['enabled', 'disabled']),
|
||||||
|
metadata={
|
||||||
|
'description': 'Status of the Pi-hole server',
|
||||||
|
'example': 'enabled',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
ads_percentage = fields.Float(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Percentage of ads blocked by the Pi-hole server',
|
||||||
|
'example': 6.7,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
blocked = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of blocked queries',
|
||||||
|
'example': 37191,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
cached = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of cached queries',
|
||||||
|
'example': 361426,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
domain_count = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of domains resolved the Pi-hole server',
|
||||||
|
'example': 1656690,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
forwarded = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of forwarded queries',
|
||||||
|
'example': 150187,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
queries = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of processed queries since the latest restart',
|
||||||
|
'example': 552076,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
total_clients = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of connected clients',
|
||||||
|
'example': 57,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
total_queries = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Total number of queries processed by the Pi-hole server',
|
||||||
|
'example': 552076,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
unique_clients = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of unique IP addresses connected to the Pi-hole server',
|
||||||
|
'example': 41,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
unique_domains = fields.Integer(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Number of unique domains resolved by the Pi-hole server',
|
||||||
|
'example': 39348,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
version = StrippedString(
|
||||||
|
required=True,
|
||||||
|
metadata={
|
||||||
|
'description': 'Version of the Pi-hole server',
|
||||||
|
'example': '5.18.2',
|
||||||
|
},
|
||||||
|
)
|
Loading…
Reference in a new issue