Compare commits

..

772 commits

Author SHA1 Message Date
82e796e20b
[Automatic] Updated UI files 2024-11-22 10:02:13 +00:00
197158fa36
Merge pull request #456 from blacklight/snyk-upgrade-3851baf7186c338443617aeaab267e82
[Snyk] Upgrade @fortawesome/fontawesome-free from 6.5.2 to 6.6.0
2024-11-22 10:46:51 +01:00
21c0787138
Merge branch 'master' into snyk-upgrade-3851baf7186c338443617aeaab267e82 2024-11-22 10:46:44 +01:00
f511e2eaf1
Merge pull request #455 from blacklight/snyk-upgrade-06847a2658543dd161a81487b6e34507
[Snyk] Upgrade core-js from 3.37.1 to 3.38.1
2024-11-22 10:45:38 +01:00
eae57003b5
Merge branch 'master' into snyk-upgrade-06847a2658543dd161a81487b6e34507 2024-11-22 10:45:30 +01:00
c8b6b1fcdd
Merge pull request #454 from blacklight/snyk-upgrade-d8e59147d78b4c5c7864d550ec543844
[Snyk] Upgrade axios from 1.7.4 to 1.7.7
2024-11-22 10:44:30 +01:00
cdab6aab3c
Merge pull request #453 from blacklight/snyk-upgrade-873f5c9ba68f797b57ac46cbfe818acc
[Snyk] Upgrade vue-router from 4.4.0 to 4.4.5
2024-11-22 10:44:14 +01:00
5a32d94991
Merge branch 'master' into snyk-upgrade-873f5c9ba68f797b57ac46cbfe818acc 2024-11-22 10:44:07 +01:00
899d137df7
Merge pull request #452 from blacklight/snyk-upgrade-7282a6d783b3d768cd8dc2f32c574432
[Snyk] Upgrade vue from 3.4.31 to 3.5.12
2024-11-22 10:43:06 +01:00
ac11185af7
[Automatic] Updated UI files 2024-11-20 01:57:17 +00:00
575c0ab730
[UI] FileEditor improvements.
- Switched to dark mode (I couldn't find any decent light theme
  combination that works with the Platypush UI).

- Support passing static `content` and `content-type` rather than only a
  `file`.

- Pass the `with-save` property from `EditorModal` to `Editor`.
2024-11-20 02:52:46 +01:00
010b52ed19
[irc] A temporary workaround for SSL contexts. 2024-11-20 01:35:44 +01:00
61b2afce91
[irc] Fixed Python 3.12 compatibility.
`ssl.wrap_socket()` has been removed in Python 3.12 - see
https://docs.python.org/3.12/whatsnew/3.12.html#ssl.

Instead, the integration should create its own `SSLContext` and use
the `wrap_socket()` from that object.
2024-11-19 23:48:07 +01:00
66a585e653
[Automatic] Updated components cache 2024-11-17 22:58:56 +00:00
acaca67c61
⚠️ Ensure that Websocket connections are always terminated upon auth failure.
In Tornado there can apparently be some race condition where `open` on a
Websocket handler does a `self.close()`, but the client is still sending
some bytes.

In that case, it may happen that the extra message is still processed.

This commit prevents the race condition by raising an exception in
`open` upon authentication failure instead of doing `close()+return`.
2024-11-17 23:52:47 +01:00
171efec739
[chore] Fixed pyproject deprecation warning.
Dynamic entrypoints in `setup.py` should now explicitly be listed as
`dynamic` under the `project` section.
2024-11-17 00:50:05 +01:00
225761c839
Bump version: 1.3.3 → 1.3.4 2024-11-17 00:32:34 +01:00
457c5cb3e3
🐛 Fixed import error on pip install platypush.
pyproject needs a version attribute from a Platypush module, while
`version.py` is currently in the source root instead.
2024-11-17 00:32:23 +01:00
fd07709811
[Build] Added multiarch support for Docker images. 2024-11-12 00:42:34 +01:00
197e1e91dd
[Build] Removed testing repo from Alpine images.
The testing repo is only required by `py3-marshmallow`, which is not yet
included in the community repo, but it can end up breaking some builds
because of the incompatibility with the packages in the base repo.
2024-11-11 23:45:44 +01:00
ad59ff8e4d
README command typo. 2024-11-11 21:51:52 +01:00
d3247a96a0
Bump version: 1.3.2 → 1.3.3 2024-11-11 20:28:01 +01:00
6199991084
Updated CHANGELOG. 2024-11-11 20:27:48 +01:00
55118119d0
[Automatic] Updated components cache 2024-11-11 19:25:23 +00:00
3e02304ac2
🐛 [Auth] Fixed API token generation when 2FA is enabled.
It should suffice for the user to provide username+password when
creating a new API token, even if 2FA is enabled.

That's because user authentication has already occurred by the time that
that check is made, and the user is already logged through a valid
session or API token, so adding an 2FA code check isn't required.

This also ensures that the UI doesn't break with a 401 on
`/#settings?page=tokens&type=api` when creating a new token.
2024-11-11 20:21:26 +01:00
snyk-bot
29284907ca
fix: upgrade @fortawesome/fontawesome-free from 6.5.2 to 6.6.0
Snyk has created this PR to upgrade @fortawesome/fontawesome-free from 6.5.2 to 6.6.0.

See this package in npm:
@fortawesome/fontawesome-free

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-11-11 05:27:02 +00:00
snyk-bot
a1fcbc8ca4
fix: upgrade core-js from 3.37.1 to 3.38.1
Snyk has created this PR to upgrade core-js from 3.37.1 to 3.38.1.

See this package in npm:
core-js

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-11-11 05:26:58 +00:00
snyk-bot
8ebae0086e
fix: upgrade axios from 1.7.4 to 1.7.7
Snyk has created this PR to upgrade axios from 1.7.4 to 1.7.7.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-11-11 05:26:53 +00:00
snyk-bot
0b12fd7fe5
fix: upgrade vue-router from 4.4.0 to 4.4.5
Snyk has created this PR to upgrade vue-router from 4.4.0 to 4.4.5.

See this package in npm:
vue-router

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-11-11 05:26:49 +00:00
snyk-bot
032572a869
fix: upgrade vue from 3.4.31 to 3.5.12
Snyk has created this PR to upgrade vue from 3.4.31 to 3.5.12.

See this package in npm:
vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-11-11 05:26:45 +00:00
697a260026
[CI/CD] Fixed shell test. 2024-11-10 22:31:18 +01:00
dce2861021
[CI/CD] Trying the locally mounted docker.sock instead of the TCP interface. 2024-11-10 22:24:41 +01:00
cc2548e159
Fixed base image for update-image-registry. 2024-11-10 21:57:57 +01:00
2dee5a25ec
Removed unrequired git command from update-image-registry.sh. 2024-11-10 21:54:52 +01:00
2410d892f5
Bump version: 1.3.1 → 1.3.2 2024-11-10 21:49:28 +01:00
4d8ad87e42
Revert "Bump version: 1.3.1 → 1.3.2"
This reverts commit 9629e04211, as the
build process encountered some regressions.
2024-11-10 21:49:05 +01:00
40849b1502
[CI/CD] Added Alpine testing repository when required. 2024-11-10 21:44:27 +01:00
9629e04211
Bump version: 1.3.1 → 1.3.2 2024-11-10 21:32:49 +01:00
771912c448
Updated CHANGELOG. 2024-11-10 21:32:30 +01:00
2bf61e37fa
[media.jellyfin] Added documentation about API keys limitations. 2024-11-10 21:32:01 +01:00
a7cb15f67d
Improved docker-compose.yml documentation. 2024-11-10 21:29:19 +01:00
27ee490264
[docs] Updated README with new Docker documentation.
Closes: #434
2024-11-10 20:37:28 +01:00
cd2b0a2666
[tests] Fixed Alpine repo name. 2024-11-10 19:03:54 +01:00
2fc91ba4f6
Fixed zbar dependency name for Arch. 2024-11-10 19:02:45 +01:00
559063ed90
Refactored required dependencies for qrcode. 2024-11-10 19:01:00 +01:00
46321559ab
[otp] Explicitly cast the output of otp.generate_secret to string.
Some versions of `pyotp` may return an intermediate
`pyotp.random_base32` object that is not JSON-serializable.
2024-11-10 18:52:10 +01:00
38756119c4
Pass --ignore-installed to pip in most of the cases.
The only case where it's fine to overwrite existing Python packages with
pip versions is when Platypush is running in a virtual environment.

Otherwise, keep the system-installed version if it's available, unless
its version is explicitly incompatible with the one reported in
`requirements.txt`.
2024-11-10 16:21:50 +01:00
b887122a7f
Added pygments to required dependencies.
It is required to correctly render code blocks in the documentation
available to the UI.
2024-11-10 15:55:57 +01:00
8bc5e5b8c5
[#434] Added pipeline to update registry images. 2024-11-10 15:23:04 +01:00
3680a9b1d1
Added pyotp and qrcode to core requirements.
A 2FA should be encouraged, thus it makes sense for its dependencies to
be installed by default.
2024-11-10 14:16:04 +01:00
d80bb9a476
[CI/CD] Use Alpine testing repo.
py3-marshmallow is still available only on the testing repo, and it's
now a hard dependency.
2024-11-10 13:20:47 +01:00
a99bde7014
[Automatic] Updated UI files 2024-11-09 23:32:26 +00:00
d799d50391
[Jellyfin UI] Implemented add to/remove from playlist.
Closes: #414
2024-11-10 00:30:14 +01:00
2df88c1911
[YouTube UI] Propagate view event from Subscriptions component. 2024-11-10 00:30:14 +01:00
478e4afb0d
[Automatic] Updated components cache 2024-11-09 22:19:01 +00:00
df580c2907
[media.jellyfin] Added get_playlists and remove_from_playlist actions. 2024-11-09 23:17:43 +01:00
459fe9427c
[youtube] Changed remove_from_playlist API.
To be compatible with the naming conventions of the other media plugins:

- `video_id` -> `item_ids`
- `index` -> `indices`
2024-11-09 23:17:42 +01:00
f47dbc842a
[youtube] Renamed add_to_playlist arguments.
`video_id` -> `item_ids`

This is more consistent with the naming conventions of the other media
plugins.
2024-11-09 23:17:42 +01:00
34aea3cd02
[Automatic] Updated UI files 2024-11-09 15:27:25 +00:00
9999025c0a
[media.jellyfin] Playlist track move implementation [UI]. 2024-11-09 16:24:53 +01:00
1230236ca5
[media.jellyfin] Implement playlist track move [backend]. 2024-11-09 16:23:57 +01:00
b646b5f3d7
[media.jellyfin] Playlist implementation [UI]. 2024-11-08 01:59:55 +01:00
bc42ba16d7
[Automatic] Updated components cache 2024-11-07 23:55:34 +00:00
b967cb1969
[media.jellyfin] Playlists support (backend implementation). 2024-11-08 00:54:18 +01:00
7c7b80c942
[Automatic] Updated components cache 2024-11-06 20:26:27 +00:00
09413bc0cc
[http.webpage] Added headers option.
A `headers` parameter has been added both to the `http.webpage` plugin
configuration and to the `http.webpage.simplify` action.

It can be used to pass extra headers to the Mercury API (e.g.
`User-Agent` or `Cookie`).

Moreover, the default `User-Agent` sent by Mercury has been changed to
an iPhone to increase the success rate of the scraping process.
2024-11-06 21:25:11 +01:00
c3766ee423
[Automatic] Updated UI files 2024-11-05 11:15:31 +00:00
dc3e896d51
Merge pull request #451 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/multi-9f37c16f8f
Bump cookie and express in /platypush/backend/http/webapp
2024-11-05 12:10:43 +01:00
65da4bb33f
Merge pull request #450 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/rollup-2.79.2
Bump rollup from 2.79.1 to 2.79.2 in /platypush/backend/http/webapp
2024-11-05 12:10:27 +01:00
dependabot[bot]
e33a2c1a70
Bump cookie and express in /platypush/backend/http/webapp
Bumps [cookie](https://github.com/jshttp/cookie) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `cookie` from 0.6.0 to 0.7.1
- [Release notes](https://github.com/jshttp/cookie/releases)
- [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1)

Updates `express` from 4.19.2 to 4.21.1
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.1)

---
updated-dependencies:
- dependency-name: cookie
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-05 11:10:19 +00:00
7147af7f3b
Merge pull request #449 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/webpack-5.95.0
Bump webpack from 5.88.2 to 5.95.0 in /platypush/backend/http/webapp
2024-11-05 12:10:10 +01:00
7b64b35f53
Merge pull request #448 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/multi-9423f4c335
Bump body-parser and express in /platypush/backend/http/webapp
2024-11-05 12:09:32 +01:00
c28745e710
Merge pull request #447 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/multi-cf87d80143
Bump send and express in /platypush/backend/http/webapp
2024-11-05 12:09:16 +01:00
cfce4e11e2
Merge pull request #446 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/multi-d66d039ac5
Bump serve-static and express in /platypush/backend/http/webapp
2024-11-05 12:08:52 +01:00
845e40fd95
Merge pull request #443 from blacklight/snyk-fix-5ef3afe3fbfad34ca892e17f8d68fd7a
[Snyk] Security upgrade axios from 1.7.1 to 1.7.4
2024-11-05 12:08:22 +01:00
53817413a4
Merge branch 'master' into snyk-fix-5ef3afe3fbfad34ca892e17f8d68fd7a 2024-11-05 12:08:13 +01:00
4836c6d036
Merge pull request #442 from blacklight/snyk-fix-307eb1aee4e5140527394bd3a929643b
[Snyk] Security upgrade zipp from 3.15.0 to 3.19.1
2024-11-05 12:07:17 +01:00
c1416fdcf9
Merge pull request #441 from blacklight/snyk-upgrade-ba475f1c345b09c787ee96292badb5b6
[Snyk] Upgrade axios from 1.7.1 to 1.7.2
2024-11-05 12:06:42 +01:00
a4a56fdbee
Merge branch 'master' into snyk-upgrade-ba475f1c345b09c787ee96292badb5b6 2024-11-05 12:06:33 +01:00
227fdf4271
Merge pull request #440 from blacklight/snyk-upgrade-0bb24e2b5457007f3b0c55c99e017fb1
[Snyk] Upgrade vue from 3.4.29 to 3.4.31
2024-11-05 12:04:36 +01:00
bb02e7c9e4
Merge branch 'master' into snyk-upgrade-0bb24e2b5457007f3b0c55c99e017fb1 2024-11-05 12:03:15 +01:00
81bf0e9561
Merge pull request #439 from blacklight/snyk-upgrade-c2f62e1f7d3dac8bdb3d5bbc73b40e8f
[Snyk] Upgrade sass from 1.77.3 to 1.77.6
2024-11-05 12:01:14 +01:00
5c1aeed30c
Merge pull request #438 from blacklight/snyk-upgrade-1a34e4a33d2d3e97e1e1d82394a1163c
[Snyk] Upgrade vue-router from 4.3.3 to 4.4.0
2024-11-05 11:59:07 +01:00
6dacddad97
[Automatic] Updated UI files 2024-11-04 00:43:27 +00:00
4759da30eb
[Execute UI] Fixed glitch when switching action args. 2024-11-04 01:41:19 +01:00
d15d744017
[Automatic] Updated components cache 2024-11-03 23:29:40 +00:00
3e54d5d7b3
[switch.tplink] Extended exception handling to all SmartDevice methods. 2024-11-04 00:28:29 +01:00
43e88c71c6
[Automatic] Updated components cache 2024-11-03 23:16:50 +00:00
71bafec11e
[switch.tplink] Better error handling.
The whole `_update_devices` for loop should be covered by a try-except
block. That's because custom attribute getters may be invoked also after
expanding the results, resulting in unhandled `SmartDeviceException`.
2024-11-04 00:15:37 +01:00
2883c4f086
Added .ignore file.
Mostly to filter out generated `dist/*` files under the webapp folder,
which may cause polluted results when using ripgrep to search things.
2024-11-04 00:15:37 +01:00
e2382c5ea2
[Automatic] Updated components cache 2024-11-03 09:44:34 +00:00
dd02be12fc
[db] Wrap SQL statements into connection.begin() blocks.
The latest release of SQLAlchemy 2.x has apparently removed the
`autocommit` implicit logic for good.

Mutations should be explicitly wrapped into `with ... begin()` blocks,
or they will be rolled back when the connection is closed.
2024-11-03 10:38:22 +01:00
1cb42f8923
[Automatic] Updated UI files 2024-11-02 21:09:28 +00:00
6936a67182
[UI] null check on datetime* functions. 2024-11-02 15:50:37 +01:00
13856365fc
[Automatic] Updated components cache 2024-10-22 00:58:31 +00:00
f98f9c3b96
[media.vlc] Always set the stop event when the player quits. 2024-10-22 02:57:19 +02:00
1626fc737e
[media.mpv] media.mpv.play should toggle pause when called with no resource. 2024-10-22 02:57:19 +02:00
c04aaba2bc
[Automatic] Updated components cache 2024-10-22 00:32:57 +00:00
2b7df634fc
A better way to ensure that the components cache won't be rewritten unless changed.
- Compare the serialized versions of the cache before and after, minus
  the saved/loaded timestamps, rather than marking the cache as dirty
  after a set.

- `Date` and `DateTime` schema fields should have static default values,
  or those values will change on every run.

- Always sort all the sets before serializing them.
2024-10-22 02:31:45 +02:00
8feee6ce39
[media.mpv] Better synchronization on player stop/idle events. 2024-10-22 02:31:45 +02:00
5fac324b43
[chore] LINT fixes. 2024-10-22 02:31:45 +02:00
3dab232d33
[Automatic] Updated components cache 2024-10-21 22:09:16 +00:00
11e25a79a5
[CI/CD] Better logic to detect changes in the components cache. 2024-10-22 00:08:03 +02:00
e73872b65e
[Automatic] Updated components cache 2024-10-21 21:48:11 +00:00
c96e83107e
[media.mpv] Make player.wait_for_shutdown call back-compatible.
Older versions of python-mpv don't support the timeout parameter.
2024-10-21 23:46:56 +02:00
2c05b7a225
[Automatic] Updated components cache 2024-10-21 21:44:00 +00:00
84a5eeb86a
[linode] Small bug fix on schema deserialization. 2024-10-21 23:42:47 +02:00
3543052c11
[Automatic] Updated components cache 2024-10-21 21:35:48 +00:00
fe3d3d6c16
[linode] Recursively expand MappedObjects before serializing. 2024-10-21 23:34:35 +02:00
bd7644b7cc
[Automatic] Updated components cache 2024-10-21 21:02:08 +00:00
83ced6a320
[media.mpv] Made the event API compatible with all python-mpv versions.
The format of the `MpvEvent*` classes, the data passed to the event
callback and the available event type enum fields have all changed
between python-mpv < 1.0.0 and >= 1.0.0.

This change makes things work with all mpv + python-mpv versions.
2024-10-21 23:00:56 +02:00
47728d5bbd
[Automatic] Updated components cache 2024-10-21 19:12:45 +00:00
89bcdbe1ac
[media.vlc] Fixed a non-easily reproducible deadlock.
The VLC event callback handler shouldn't try and access the media and/or
the MRL while processing a `MediaPlayerTitleChanged` event.

It seems that in some particular streaming cases (mostly reproducible
with Jellyfin media URLs) this may result in deadlocks - probably
because the media metadata is being handled within the HTTP request, and
the callback handler runs within the same context.
2024-10-21 21:11:28 +02:00
8fa2080652
[media.vlc] A more robust logic for the player monitor thread.
The player may sometimes stop, or the VLC instance crash, without
sending a stop event.

This may leave the Platypush plugin in a PLAYING state, and the instance
may not be properly cleaned up.

This commit adds a polling logic to the monitor thread to verify every
second if the player is still running, and terminate the instance if
that's not the case.
2024-10-21 21:11:28 +02:00
25b77f068b
[Automatic] Updated components cache 2024-10-20 01:27:11 +00:00
76b86aec0e
[rss] Moved _parse_subscriptions call inside of main.
Rather than the plugin constructor.

This is to ensure that the application startup won't be have to wait for
the feed parsing logic to complete.
2024-10-20 03:25:57 +02:00
897f3a6f47
[Automatic] Updated UI files 2024-10-20 00:53:54 +00:00
8e9fb65db5
[#414] Support for Jellyfin book items [frontend]. 2024-10-20 02:51:48 +02:00
51464b808c
[Automatic] Updated UI files 2024-10-20 00:37:58 +00:00
825593a445
[#414] Support for Jellyfin book items [backend]. 2024-10-20 02:35:52 +02:00
ec8fe401d2
[Media UI] Support audio types in the embed player. 2024-10-20 02:35:52 +02:00
3a9d5700ea
[#414] Support paginated results on scroll in the media Collections component. 2024-10-20 02:35:51 +02:00
d171795e7c
[#414] Added embedded player support for Jellyfin and YouTube media. 2024-10-20 02:35:51 +02:00
4015cf356d
[Automatic] Updated UI files 2024-10-19 15:05:19 +00:00
fa942b06e3
[media] Support HEAD method on media stream endpoints. 2024-10-19 17:03:12 +02:00
4373d4ceaa
[media] Clear the stream media cache on the first update.
The stream media cache can easily grow in MB in size. Storing it in
Redis means impacting the performance of the application, as on every
web media streaming event it'll have to fetch and deserialize MBs of
data, and Redis may also flush the .rdb file to disk several times in
the process.
2024-10-19 17:03:12 +02:00
7620e1ead7
[#414] Added support for downloading Jellyfin items. 2024-10-19 17:03:12 +02:00
e314a7bca9
[Automatic] Updated components cache 2024-10-15 21:26:42 +00:00
3e61bd7a9b
[media.jellyfin] Several improvements on schema definitions. 2024-10-15 23:25:29 +02:00
12c800b7a9
[Automatic] Updated UI files 2024-10-15 20:19:06 +00:00
7b8d92b120
[#414] Added support for photo collections in Jellyfin UI. 2024-10-15 22:16:57 +02:00
3ffb061e2a
[Automatic] Updated components cache 2024-10-14 21:09:41 +00:00
9716b1da35
[media.jellyfin] Added support for photo items. 2024-10-14 23:00:50 +02:00
e30ae16ef7
[Automatic] Updated UI files 2024-10-14 19:51:04 +00:00
86559a623a
[#433] Added music UI to Jellyfin. 2024-10-14 21:48:54 +02:00
4f81a73fb9
[UI] Added formatDuration utility. 2024-10-14 21:48:54 +02:00
117dfad64e
[UI] Dropdown's hidden class should be set as a property. 2024-10-14 21:48:54 +02:00
9b99c1e19d
[procedures] Sync only after the db engine is initialized. 2024-10-14 21:48:54 +02:00
585c2f733f
[media.jellyfin] Added more metadata to the returned items. 2024-10-14 21:48:53 +02:00
cad864f220
[Automatic] Updated components cache 2024-10-14 16:23:26 +00:00
e3e3638ffe
[switch.tplink] Demoted log trace for missing current_consumption property from warning to debug 2024-10-14 18:22:12 +02:00
c605a65bf1
[Automatic] Updated components cache 2024-10-14 16:09:26 +00:00
f032957d0b
[switch.tplink] Don't thrown an exception if a device doesn't support current_consumption. 2024-10-14 18:07:57 +02:00
f364be17e3
[Automatic] Updated components cache 2024-10-13 14:11:51 +00:00
0aae905754
[media.vlc] Better state management. 2024-10-13 16:10:35 +02:00
0ab160569a
[media.vlc] A more robust close/stop player logic. 2024-10-13 16:10:35 +02:00
9cadf98d52
[Automatic] Updated components cache 2024-10-12 22:25:43 +00:00
44557e812f
Added EventProcessor.remove_hook method. 2024-10-13 00:24:24 +02:00
14b25ac891
[Automatic] Updated UI files 2024-10-03 19:24:39 +00:00
e16247529e
[UI] 🐛 Moved ProcedureEditor component out of the v-else branch.
Otherwise adding new procedures through the floating add button won't
work unless some procedures are already present.
2024-10-03 21:21:27 +02:00
9ebe251d46
Several Docker image improvements.
- Reduced size of the Ubuntu image by removing some unneeded packages
  (docutils, manpages, babel, fonts, python-pil etc.) that take a lot of
  space.

- Better self-documented docker-compose.yml.

- Added reference to the registry.platypush.tech/platypush image in
  docker-compose.yml (README reference will follow).

- Fixed grep condition in the Docker prepare script.

- Pass `--no-deps` to `pip install platypush`. The dependencies of the
  application, now that `marshmallow_dataclasses` has been removed, are
  all available in the package managers of the supported images (with
  the exception for croniter in Alpine Linux for now), so they can all
  be installed via system package manager rather than pip. This also
  prevents Ubuntu builds from breaking because system-installed packages
  are being overwritten with pip-installed copies.
2024-10-03 20:35:01 +02:00
2308c4e927
Deeper cleanup of cache and tmp files in Dockerfile. 2024-09-30 22:23:15 +02:00
9e6ae42660
[Automatic] Updated UI files 2024-09-28 22:03:28 +00:00
c88a6aa3e6
[#414] [UI] Added support for generic Jellyfin media collections. 2024-09-29 00:01:16 +02:00
9e78a9a297
[Automatic] Updated UI files 2024-09-28 21:54:26 +00:00
1a53c59382
[#414] Added ability to sort Jellyin results.
This also adds a new `FloatingDropdownButton` component.
2024-09-28 23:52:18 +02:00
bf82ad9bf0
[WIP] [#414] [UI] Initial implementation of the Jellyfin UI. 2024-09-28 23:52:13 +02:00
dependabot[bot]
0d62760036
Bump rollup from 2.79.1 to 2.79.2 in /platypush/backend/http/webapp
Bumps [rollup](https://github.com/rollup/rollup) from 2.79.1 to 2.79.2.
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v2.79.1...v2.79.2)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-28 01:53:39 +00:00
dependabot[bot]
1b735779d3
Bump webpack from 5.88.2 to 5.95.0 in /platypush/backend/http/webapp
Bumps [webpack](https://github.com/webpack/webpack) from 5.88.2 to 5.95.0.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.88.2...v5.95.0)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-26 00:37:34 +00:00
841516d9de
[Automatic] Updated components cache 2024-09-25 23:53:57 +00:00
c134f29c72
Bump version: 1.3.0 → 1.3.1 2024-09-26 01:52:09 +02:00
dc104a9c8e
[linode] A more robust way to deal both with dict and object results. 2024-09-26 01:50:52 +02:00
afdacc90d6
Added missing RST file 2024-09-26 01:43:27 +02:00
117d3f07d9
Added docs for new procedures plugin 2024-09-26 01:43:08 +02:00
b44d21d31f
Updated CHANGELOG 2024-09-26 01:43:07 +02:00
f13e00f22e
[Automatic] Updated components cache 2024-09-25 23:39:06 +00:00
54e3703462
[#344] Removed remaining references of marshmallow_dataclass.
Closes: #344
2024-09-26 01:36:51 +02:00
a7f03a1af9
[#344] Removed marshmallow_dataclass dependency from system plugin. 2024-09-25 23:55:32 +02:00
63d9c1e348
[Automatic] Updated components cache 2024-09-24 10:04:41 +00:00
9244eedc49 tts.openai.say doesn't need a language parameter - and its presence breaks playback 2024-09-24 12:03:24 +02:00
bc01cddee0
Bump version: 1.2.3 → 1.3.0 2024-09-23 21:38:00 +02:00
f81c0dad4c
[Automatic] Updated UI files 2024-09-23 01:52:22 +00:00
f42ed34f75 Merge pull request '[UI] Expose procedures as entities' (#428) from 341/procedure-entities-ui into master
Reviewed-on: platypush/platypush#428
2024-09-23 03:50:14 +02:00
54a4c21882 Merge branch 'master' into 341/procedure-entities-ui 2024-09-23 03:48:51 +02:00
c48efe07bd
[Automatic] Updated components cache 2024-09-23 01:46:43 +00:00
e1547da794
Merge branch 'master' into 341/procedure-entities-ui 2024-09-23 03:46:15 +02:00
62737b5a95 Merge pull request '[Backend] Expose procedures as entities' (#426) from 341/procedure-entities into master
Reviewed-on: platypush/platypush#426
2024-09-23 03:45:30 +02:00
8d8e1878bb
Updated CHANGELOG 2024-09-23 03:44:05 +02:00
393493550c
Merge branch '341/procedure-entities' into 341/procedure-entities-ui 2024-09-23 03:21:34 +02:00
38c1ebb90c
[alarm] Don't fail if no audio file is provided. 2024-09-23 03:20:55 +02:00
419c227ff8
[UI] Don't propagate click events in FileBrowser.
That could otherwise close the parent component when `FileBrowser` is
running in a `Modal`.
2024-09-23 03:19:23 +02:00
d1da4803cf
[#341] Adapted alarm UI to the changes to the ProcedureEditor component. 2024-09-23 03:17:20 +02:00
b6c0029208
[UI] Better color schemes for highlighed text. 2024-09-22 03:44:36 +02:00
8b621cbf30
[UI] Propagate dragend events from Tile components. 2024-09-22 03:43:46 +02:00
e5a2127ecb
[UI] s/stackoverflow-dark/monokai-sublime/ in extensions config page.
The dark blue YAML style used by `stackoverflow-dark` isn't very
readable on black background.
2024-09-22 02:49:14 +02:00
91c66cdd48
[UI] Fixed deprecated usage of the hljs.highlight API. 2024-09-22 02:36:43 +02:00
f6b3d34eff
[UI] Better handling of nested modals on the entities page.
If a modal was spawned from within an entity in a group, then the whole
group needs to get its zIndex bumped.

Otherwise, the modal component will be "caged" within the scope of the
parent and other entity groups will be rendered above it.
2024-09-22 01:43:25 +02:00
839948e4e6
[UI] Emit one and only one open/close modal event. 2024-09-22 01:41:52 +02:00
0d0665ca7c
[UI] Keep track of the number of stack modals. 2024-09-22 01:40:52 +02:00
6dd1d481d5
[#341] Added support for dynamic context in procedure editor components. 2024-09-21 20:45:50 +02:00
dependabot[bot]
6d11f8d2ef
Bump body-parser and express in /platypush/backend/http/webapp
Bumps [body-parser](https://github.com/expressjs/body-parser) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `body-parser` from 1.20.2 to 1.20.3
- [Release notes](https://github.com/expressjs/body-parser/releases)
- [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md)
- [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: body-parser
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-21 10:38:59 +00:00
dependabot[bot]
5a3b6c3c07
Bump send and express in /platypush/backend/http/webapp
Bumps [send](https://github.com/pillarjs/send) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `send` from 0.18.0 to 0.19.0
- [Release notes](https://github.com/pillarjs/send/releases)
- [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md)
- [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: send
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 21:41:25 +00:00
dependabot[bot]
0758dcae92
Bump serve-static and express in /platypush/backend/http/webapp
Bumps [serve-static](https://github.com/expressjs/serve-static) and [express](https://github.com/expressjs/express). These dependencies needed to be updated together.

Updates `serve-static` from 1.15.0 to 1.16.2
- [Release notes](https://github.com/expressjs/serve-static/releases)
- [Changelog](https://github.com/expressjs/serve-static/blob/v1.16.2/HISTORY.md)
- [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...v1.16.2)

Updates `express` from 4.19.2 to 4.21.0
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/4.21.0/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.19.2...4.21.0)

---
updated-dependencies:
- dependency-name: serve-static
  dependency-type: indirect
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-20 21:17:35 +00:00
0e40d77bc7
Merge branch '341/procedure-entities' into 341/procedure-entities-ui 2024-09-16 03:20:03 +02:00
be8140ddb5
[procedure] Several improvements to the procedure engine.
- Add `set` statement, which can be used to set context variables within
  YAML procedures. Example:

```yaml
procedure.test:
  - set:
      foo: bar

  - action: logger.info
    args:
      msg: ${bar}
```

- More reliable flow control for nested break/continue/return.

- Propagate changes to context variables also to upstream procedures.
2024-09-16 03:16:53 +02:00
e7e76087c0
[#341] Added support for setting variables in procedure editor. 2024-09-16 03:08:46 +02:00
dfbbea93fd
[#341] Added UI for while loops in procedure editor. 2024-09-15 02:06:23 +02:00
ab07fc0fa3
Merge branch '341/procedure-entities' into 341/procedure-entities-ui 2024-09-13 18:22:18 +02:00
771e32e368
[#341] procedure._serialize_action should also support strings. 2024-09-13 18:21:27 +02:00
156a6379d0
[#341] Added for for/break/continue statements in procedure editor. 2024-09-13 18:19:02 +02:00
c4610254f8
Merge branch '341/procedure-entities' into 341/procedure-entities-ui 2024-09-12 02:16:32 +02:00
853fce2521
[procedures] Fixed if queue flushing logic.
Any pending `if`s in the parsing queue of a procedure should also be
cleared if the current statement in the procedure is a
break/continue/return.

In such case we should terminate the current branch, and that involves
ensuring that any `if`s branches that are still being parsed are
inserted before the branch-terminating statement.
2024-09-12 02:14:40 +02:00
b337bf7a53
[#341] Added return block to ProcedureEditor. 2024-09-12 02:02:27 +02:00
152c2020de
Merge branch '341/procedure-entities' into 341/procedure-entities-ui 2024-09-10 22:55:53 +02:00
daa030ff4c
[UI] Improved Draggable component events.
`Draggable` components should emit `dragend`, not `drop` events.

`drop` should only be emitted by `Droppable` components, or the receiver
of a component that uses both won't be able to tell if a `drop` event
came from a component being dragged, or from an element where a dragged
element was dropped.
2024-09-10 22:54:12 +02:00
471ec1370c
[Procedure Editor] Added support for conditions and nested blocks. 2024-09-10 22:53:14 +02:00
202cff093f
[UI] Modals should react on escape only if the container element is present. 2024-09-10 22:42:50 +02:00
6eb8b7954d
[UI] Added styles for tiles. 2024-09-10 22:41:53 +02:00
0bc714d0e3
[UI] Added fold animation. 2024-09-10 22:41:35 +02:00
1e9f7fb2c6
[procedure] Added support for custom values on the return statement.
This enables constructs like this in procedures:

```yaml
- return

- return 1

- return: ${output}
```
2024-09-10 19:55:26 +02:00
946c7b1783
[procedure] Ignore id field in Procedure.build.
The reason is that an `id` specified on procedure level will be applied
to all the child requests.

This means that the first response from the first completed request will
be sent to Redis and mistakenly interpreted by HTTP listeners as the
return value of the whole procedure.

`Procedure.build` should instead calculate its own ID for the procedure,
and apply different IDs to the child requests.
2024-09-10 19:53:14 +02:00
5a7068501a
[request] The action name can be specified either on action or name.
This is for UI compatibility purposes.
2024-09-10 19:52:23 +02:00
efe2bb6196
Merge branch '341/procedure-entities' into 341/procedure-entities-ui 2024-09-06 12:04:32 +02:00
05b1fcd43a
[procedures] Don't validate the presence of the actions field in procedures.save.
When saving procedures with if/else/for blocks, some blocks aren't
supposed to have the `actions` field.
2024-09-06 11:55:03 +02:00
f18d0d8b74
[procedures] Recursive serialization in procedures.to_yaml. 2024-09-06 11:53:35 +02:00
15cf611c95
[#341] [UI] Implemented support for procedure entities.
- Added UI panel.

- Added support for entity types.

- Enhanced ability to edit procedures.

- Added ability to create, rename, edit, duplicate and delete stored
  procedures.

- Added support for YAML dumps of non-Python procedures.

- Added support for visualizing Python procedures directly in their
  source files.
2024-09-05 02:02:44 +02:00
bbfc5b32e6
[UI] Added integration icons to the ActionEditor autocomplete. 2024-09-05 02:00:52 +02:00
1133c6019a
[UI] Added general-purpose entity icon editor component. 2024-09-05 01:58:28 +02:00
1316af9553
[UI] Added general-purpose drag-and-drop components.
This is to bridge the gap between pointer-based and touch-based devices
and provide a drag-and-drop implementation that exposes a consistent API
for both the interfaces.

These components work by wrapping an underlying draggable/droppable DOM
element and proxying the event handlers consistently when drag/touch
events are detected.

This allows to listen to high-level drag/drop events even on touch-based
interface based on touch start/move/end events.

Example usage:

```vue
<template>
  <div class="draggable" ref="draggable">
    I can be dragged.
  </div>

  <div class="droppable" ref="droppable">
    Drop elements here.
  </div>

  <Draggable :element="$refs.draggable"
             @drag="console.log('The element is being dragged')"
             @drop="console.log('The element is been dropped')" />

  <Droppable :element="$refs.droppable"
             @dragenter="console.log('Entering')"
             @dragleave="console.log('Leaving')"
             @dragover="console.log('Dragging over')"
             @drop="console.log('Dropped!')" />
</template>

<style lang="scss" scoped>
.draggable {
  &.dragged {
    opacity: 0.5;
  }
}

.droppable {
  &.active {
    border: 1px solid green;
  }

  &.selected {
    background: yellow;
  }
}
</style>
```
2024-09-05 01:41:04 +02:00
44e319e7ca
[UI] Listen for keyup, keydown and touch events on NameEditor. 2024-09-05 01:40:26 +02:00
4e5c740908
[File UI] Added support for custom line positioning in file editor.
- Adds the ability to select lines from the editor, which in turn will
  highlight them.

- Adds the ability to load a file and scroll at a specific line if the
  URL has with the `line` argument.

- Adds the ability to maximize the file editor modal.
2024-09-05 01:39:43 +02:00
cc621cdca6
[UI] Support both string and objects on the Response component. 2024-09-05 01:35:05 +02:00
b0d9a95331
[UI] Added title propery to EditButton component. 2024-09-05 01:32:51 +02:00
485a1db3d3
[UI] Several improvements to the FloatingButton component.
- Added support for multiple element classes.

- Added `glow` property.

- Added support for absolute initial positioning.

- Added dynamic button size.

- Added FloatingButtons component to support groups of floating buttons.
2024-09-05 01:31:32 +02:00
e17abc34c1
[UI] Several improvements on the Modal component.
- Don't propagate `close` events. This prevents underlying modals from
  being closed on cascade when the current modal is closed.

- Added logic to filter out <ESC> keystrokes that have already targeted
  the outermost open modal, so underlying modals won't be closed.

- Added `:before-close` property. This is a callback that can optionally
  be passed to the component and it will run some custom logic before
  the modal is closed. If it returns false then the modal will stay
  open.
2024-09-05 01:28:47 +02:00
b74b8aa154
[#341] Added procedure entity icon. 2024-09-05 01:22:19 +02:00
d623b3d1b8
[UI] Added default styles for monospace content and draggable elements. 2024-09-05 01:21:13 +02:00
68e3cc51e4
[UI] Added shink, expand and unfold animations. 2024-09-05 01:20:43 +02:00
dd2ea2092e
[UI] Added more color settings. 2024-09-05 01:20:13 +02:00
3249053eb0
[UI] Added support for custom HTML and data in Autocomplete component. 2024-09-05 01:19:26 +02:00
bbc70fe6e6
[UI] The main module should load the config dir and main file paths at startup. 2024-09-05 01:17:35 +02:00
9ec21fe10d
[#341] Added icon for procedures plugin. 2024-09-05 01:16:33 +02:00
c54269e3d2
[#341] Added utility procedures.to_yaml action. 2024-09-05 01:13:30 +02:00
e39e36e5f6
[CI/CD] A more resilient github-mirror script.
- Fail immediately if no branches are checked out.

- Rebase only if we're pushing on master (don't bother for feature
  branches).

- Do a push force to Github.
2024-09-02 02:31:11 +02:00
c5c872eb68
[chore] Removed unused file re-added upon rebase. 2024-09-02 02:27:33 +02:00
26f491025a
[#341] Improvements on procedures.save.
- Update the cached representation of the procedure asynchronously on
  the `publish_entities` callback. This prevents stale records from
  being loaded from the db before the entities engine has persisted the
  new ones.

- Don't re-publish all entities when calling `procedures.status` at the
  end of `procedures.save`. This is both for performance reasons and to
  avoid sending to the entities engine stale representation of the data.
2024-09-02 02:24:43 +02:00
90a953b738
[WIP] 2024-09-02 02:24:42 +02:00
981ae3479e
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:42 +02:00
bca340ebc1
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:42 +02:00
9aff704444
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:42 +02:00
5061c5290c
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:42 +02:00
c68cf4b585
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:41 +02:00
b53c4c5c18
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:41 +02:00
e01782c344
An empty commit to re-trigger the CI/CD pipelines 2024-09-02 02:24:41 +02:00
6df699359c
Merge branch 'master' into 341/procedure-entities 2024-09-01 20:00:41 +02:00
861e7e7c52
[#341] More improvements on the procedures plugin.
- `procedures.status` should always sync with the db to ensure that the
  action returns the most up-to-date version of the procedures.

- Store, return and propagate entity procedure metadata.
2024-09-01 18:13:06 +02:00
1369848114
[#341] More procedures features.
- `procedures.exec` now supports running procedures "on the fly" given a
  definition with a list of actions.

- Fixed procedure renaming/overwrite logic.

- Access to `_all_procedures` should always be guarded by a lock.
2024-09-01 01:47:39 +02:00
9d086a4a10
[system] Don't use is_defined macro for system plugin entities.
It seems to clash with something and cause plugin actions to return
random `ImportError`.
2024-09-01 01:34:58 +02:00
7cc7009d08
[db] Always run PRAGMA foreign_keys = ON on SQLite connections.
This is the default behaviour on basically any other supported RDBMS.
2024-09-01 01:04:12 +02:00
a3eedc6adc
[core] Fix support for custom SQLAlchemy engine options on db conf.
Earlier any extra parameters passed to the `db` configuration other than
`engine` where ignored.

This enables engine-level configurations such as:

```yaml
db:
  # Display all SQL queries
  echo: true
```
2024-08-31 21:55:19 +02:00
740e35bd5e
Moved full dump of requests+responses+events to log debug level.
Messages can be quite big and verbose, and they can anyway be subscribed
over Websockets.

Full dumps are anyway enabled when Platypush is started in verbose mode.

This commit replaces the dumps on INFO level with a quick summary
containing the message ID, request/event type and response time.
2024-08-31 21:47:44 +02:00
457333929f
[#341] Added procedures.save and procedures.delete actions. 2024-08-30 02:08:42 +02:00
e593264eab
[#341] Added ProcedureType enum. 2024-08-30 02:05:02 +02:00
cb9244964c
[Automatic] Updated components cache 2024-08-27 22:19:09 +00:00
62980cc9e2 [procedures] Store actions for YAML procedures. 2024-08-27 22:18:15 +00:00
42d672ab5e 🐛 Fixed import error 2024-08-27 22:18:15 +00:00
ef524fa388 [config] Added config.get_config_dir method. 2024-08-27 22:18:15 +00:00
dea72fbfdb [#341] Support for procedure reconciliation.
If some procedures are removed either from the configuration or from the
loaded scripts, then their associated entities should also be removed
from the database when the `procedures` plugin is loaded.
2024-08-27 22:18:15 +00:00
99909c73ab [#341] Backend implementation of the new procedure entities architecture. 2024-08-27 22:18:15 +00:00
1ee8055597 [WIP] 2024-08-27 22:18:15 +00:00
d9916873cb
[procedures] Store actions for YAML procedures. 2024-08-28 00:14:54 +02:00
234963b069
🐛 Fixed import error 2024-08-28 00:14:21 +02:00
ffc3fe218d
[config] Added config.get_config_dir method. 2024-08-28 00:13:46 +02:00
0f186c44ef
[#341] Support for procedure reconciliation.
If some procedures are removed either from the configuration or from the
loaded scripts, then their associated entities should also be removed
from the database when the `procedures` plugin is loaded.
2024-08-25 23:45:11 +02:00
06781cd72c
[#341] Backend implementation of the new procedure entities architecture. 2024-08-25 16:06:56 +02:00
24f7d4a789
Merge branch 'master' into 341/procedure-entities 2024-08-25 14:29:43 +02:00
c788f2d858
[Automatic] Updated components cache 2024-08-25 01:25:37 +00:00
f6b1f92a88
🐛 file.bookmarks must be optional. 2024-08-25 03:24:02 +02:00
9f8fe60cdf
An empty commit to re-trigger the CI/CD pipelines 2024-08-25 03:20:38 +02:00
83d21d3f04
[media] Media played from live streams should be at least 5MB before playback starts. 2024-08-25 03:16:59 +02:00
54a6b34a64
[file] Added support for UI bookmarks on the file plugin. 2024-08-25 03:16:59 +02:00
377b2c2425
[Automatic] Updated UI files 2024-08-25 01:15:10 +00:00
a152b0d734
[#333] Added file browser UI panel.
Closes: #333
2024-08-25 03:13:05 +02:00
496dfdb50b
[Media UI] Adapted media browser to the new file browser plugin. 2024-08-25 03:13:04 +02:00
9493445af6
[Media UI] Renamed play-cache event to play-with-opts.
As we're likely to add more play options in the future, this approach is
much more scalable.
2024-08-25 03:13:04 +02:00
0657c80a5c
[#333] Enhanced file browser component.
- Added support for file/directory add/copy/move/rename/remove
  operations.

- Added automatic detection of MIME types.

- Added support for file view/download.

- Added file uploader component.

- Added custom sorting and other visualization options.

- Added custom `Home` component to show configurable bookmarks above the
  filesystem root level.

- Added file editor with automatic syntax highlight.
2024-08-25 03:13:04 +02:00
e672a7fb5c
[Media UI] Always normalize the duration field to float. 2024-08-25 03:13:04 +02:00
e8acf8615f
[UI] Added disabled property to FloatingButton. 2024-08-25 03:13:04 +02:00
336cb18cb3
[UI] New features for the Modal element.
- Added `uppercase` property (default: true) for the modal title. This
  makes it possible to override the default case of the modal title.

- Added support for custom buttons in the modal titlebar.
2024-08-25 03:13:03 +02:00
342df0eeec
[UI] Added common disabled style to buttons. 2024-08-25 03:13:03 +02:00
e6a358fe27
[UI] Added quick String.hashCode function.
This is needed in several places in the code where we need to compare if
two strings differ, but either the strings are too long (e.g. content of
large files) or we don't want to pass the original values (e.g.
credentials, session tokens etc.).
2024-08-25 03:13:03 +02:00
818f60a468
[UI] Better parsing of the parameter types in getUrlArgs and setUrlArgs. 2024-08-25 03:13:03 +02:00
db34a607e4
[UI] Improvements to the Dropdown element.
- Added `style` property to pass static style rules to the dropdown
  body.

- Better positioning of the dropdown when the resulting body is too long
  and may overflow the top of the screen - in that case, the dropdown
  position needs to be maximized at zero.
2024-08-25 03:13:03 +02:00
8f2e68f0db
[UI] Added visible property to ConfirmDialog element. 2024-08-25 03:13:02 +02:00
8b3c2a8ee1
[UI] Updated highlight.js dependency 2024-08-25 03:13:02 +02:00
92bff4decb
[Automatic] Updated components cache 2024-08-24 22:13:00 +00:00
0bb264792e
[file] Added file.copy and file.move actions. 2024-08-25 00:11:38 +02:00
a5426ede58
[file] Added recursive option to file.rmdir. 2024-08-25 00:11:38 +02:00
2c481c54af
[file] Added POST/PUT /file endpoints. 2024-08-25 00:11:38 +02:00
0010342fb7
Get the original MIME type for symlinks.
If the target resource is a symbolic link, then `get_mime_type` should
retrieve the MIME type of the linked resource.
2024-08-25 00:11:38 +02:00
1e9418b072
[file] file.list, file.is_binary and file.get_user_home actions.
- Added `sort` and `reverse` arguments to `file.list`.

- Added `file.is_binary` and `file.get_user_home` actions.
2024-08-25 00:11:37 +02:00
213498318f
[media] Support extended format/metadata for media dirs. 2024-08-25 00:11:37 +02:00
077e12e9a8
[media] Allow media_dirs to be either a list or a dict.
This allows the user to have some user-friendly names for their
collections on the UI, such as `Movies` instead of
`/mnt/hd/media/movies`.
2024-08-25 00:11:37 +02:00
897e8a9ff7
[Automatic] Updated components cache 2024-08-19 01:08:49 +00:00
b439b8b0f4
[file] Better implementation of file.get_mime_types.
- The MIME magic functions apparently aren't thread safe, and they may
  crash the interpreter if called concurrently. Lock calls to
  `file.get_mime_types`.

- Added an LRU cache for the MIME type results.
2024-08-19 03:07:34 +02:00
f1c640fabb
[Automatic] Updated components cache 2024-08-19 00:14:32 +00:00
666bbe5372
Removed media.omxplayer reference. 2024-08-19 02:11:40 +02:00
9dfb22c23a
[file] Added file.info and file.get_mime_types actions. 2024-08-19 02:10:51 +02:00
2b48edfabc
[media.vlc] Prevent deadlock on media.vlc.quit.
`_on_stop_event` may be set by the callback, but then cleared again when
`_reset_state` is called.

This can result in the `_on_stop_event.wait` call in `quit` to time out.

Instead, `_on_stop_event` should be cleared only when the player goes
into `playing` or `paused` mode. It's only then that we know for sure
that the state isn't `stopped`, and only in that case it makes sense to
wait for a stop.
2024-08-19 02:02:21 +02:00
6e27c9b8e4
Bump version: 1.2.2 → 1.2.3 2024-08-18 15:22:02 +02:00
74a2958ff4
Updated CHANGELOG 2024-08-18 15:22:02 +02:00
8333cc09ee
[Automatic] Updated UI files 2024-08-18 11:05:26 +00:00
01571e2e65
[UI] Many improvements for the media UI.
- Support for _Play_ / _Play (With Cache)_ options for YouTube videos.

- Added `media.chromecast` and `media.gstreamer` UI panels.

- Removed `media.omxplayer` - the plugin has been removed.

- Enriched and improved the media info component.

- Propagate the media loading state to all children components.

- Persist query/search state on the URL.

Closes: #422
2024-08-18 13:03:04 +02:00
a21aaee888
[Automatic] Updated components cache 2024-08-18 10:58:02 +00:00
5080caa38e [#422] Enabled support for yt-dlp mux+transcoding in media plugins
Reviewed-on: platypush/platypush#423
2024-08-18 12:56:47 +02:00
ca5853cbab
[Automatic] Updated UI files 2024-08-15 17:58:53 +00:00
1f120b167b
Merge pull request #444 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/axios-1.7.4
Bump axios from 1.7.1 to 1.7.4 in /platypush/backend/http/webapp
2024-08-15 19:54:58 +02:00
dependabot[bot]
09412acba7
Bump axios from 1.7.1 to 1.7.4 in /platypush/backend/http/webapp
Bumps [axios](https://github.com/axios/axios) from 1.7.1 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.1...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-15 17:54:22 +00:00
snyk-bot
846324fa12
fix: platypush/backend/http/webapp/package.json & platypush/backend/http/webapp/package-lock.json to reduce vulnerabilities
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JS-AXIOS-7361793
2024-08-15 05:53:02 +00:00
af21ff13ff
[WIP] 2024-08-13 22:27:10 +02:00
e0ff180fb0
[Automatic] Updated components cache 2024-08-13 11:41:39 +00:00
1189e71539 Added merge_output_format option to media plugins 2024-08-13 13:40:15 +02:00
50beb1460b
[Automatic] Updated components cache 2024-08-12 07:45:30 +00:00
d155094729
[zigbee.mqtt] Minor LINT fixes. 2024-08-12 09:44:07 +02:00
866be36aad
[Automatic] Updated components cache 2024-08-09 13:37:56 +00:00
129c7587ad
[zigbee.mqtt] Removed synchronous logic on device_set.
Don't wait for a value update on `device_set` - an event will be
triggered anyway when the value changes.

This should prevent timeouts when setting/toggling values.
2024-08-09 15:36:39 +02:00
d70737ea2b
[zigbee.mqtt] Added more logging lines on device_set action. 2024-08-09 15:36:34 +02:00
b8b70f43c0
[Automatic] Updated components cache 2024-07-27 19:52:18 +00:00
8b6c1fb969
Fixed NPE on the /auth endpoint in case the user response is already a UserAuthStatus. 2024-07-27 21:47:55 +02:00
0071fc54b3
Bump version: 1.2.1 → 1.2.2 2024-07-27 16:34:02 +02:00
ee1d91db6b
Updated CHANGELOG 2024-07-27 16:33:55 +02:00
d0f65e84e0
Restored some sections of setup.py.
Otherwise the installation won't work properly on Debian oldstable (and
probably any Python installation that doesn't fully support
pyproject.toml yet).
2024-07-27 16:22:14 +02:00
4d18345cda
Bump version: 1.2.0 → 1.2.1 2024-07-27 15:40:20 +02:00
4c80e6fd34
Fixed github.com repo URL case. 2024-07-27 15:38:55 +02:00
598de6b91a
[CI/CD] Only push tags to Github on the second git push command. 2024-07-27 15:36:22 +02:00
20483524d1
Updated CHANGELOG. 2024-07-27 15:34:29 +02:00
9bbdfc1eb9
[CI/CD] Mirror also tags to Github. 2024-07-27 15:31:50 +02:00
4568b4659e
[CI/CD] grep the version out of version.py.
Instead of running `python setup.py --version`.

That's because earlier versions of Python that don't fully support
dynamic version specifications through `pyproject.toml` may just return
`0.0.0` here.
2024-07-27 15:29:08 +02:00
54c2264403
license is not required in pyproject.toml if it's already specified in classifiers. 2024-07-27 15:18:47 +02:00
52d92da907
Upgraded license date. 2024-07-27 15:12:45 +02:00
88cc18de92
Replaced remaining setup.py references in the code with pyproject.toml. 2024-07-27 15:12:19 +02:00
fb99eefe40
Bump version: 1.1.3 → 1.2.0 2024-07-27 14:43:16 +02:00
ba390ab2f3
[Automatic] Updated components cache 2024-07-27 12:41:32 +00:00
6053a80796
Added backend /login and /register routes.
These are required for clients that don't have JS enabled and will get a
404 otherwise.

The routes simply render `index.html`, which will be empty if JS is
disabled and will redirect to the appropriate login/registration Vue
route if the user isn't logged in.
2024-07-27 14:39:09 +02:00
ccc778e056
Setting optional-dependencies as dynamic in pyproject.toml. 2024-07-27 14:15:01 +02:00
e10f1d7e1b
[Automatic] Updated components cache 2024-07-27 12:09:07 +00:00
de96b4ea17
Migrated project setup to pyproject.toml. 2024-07-27 14:06:53 +02:00
92fe119cff
Removed psutil as a required pip dependency.
Moved to optional dependencies for `system` plugin.

It requires gcc, linux-headers and python-dev to be installed on the
system.

The `python-psutil` system package however will still be installed when
Platypush is installed through a package manager.
2024-07-27 14:02:53 +02:00
f809ce0cb0
Apparently the version variable needs to always be named __version__.
Otherwise bump-my-version gets confused.
2024-07-27 12:13:40 +02:00
0963cd3d55
Moving around the __version__ string to get bumpversion to work again. 2024-07-27 12:12:07 +02:00
a70f151e28
[Fix] Version should be explicitly defined in setup.cfg (for now).
Importing `platypush.__version__` also imports the `platypush` base
package, which in turns depends on at least `pyyaml` being already
installed on the system.
2024-07-27 11:56:23 +02:00
b71612cb2f
[core] Moved __version__ string to its own package.
It reduces the duplication of `__version__`, which before was defined
on:

- `setup.py`
- `setup.cfg`
- `platypush/__init__.py`
2024-07-27 11:46:18 +02:00
dfe0092857
Updated CHANGELOG 2024-07-27 11:35:09 +02:00
90736f8ffb
[Automatic] Updated UI files 2024-07-26 23:58:32 +00:00
6cd342e1f4
[UI] Quickfix: Remove margin from default input style. 2024-07-27 01:55:46 +02:00
e234210fb5
[Automatic] Updated UI files 2024-07-26 23:46:57 +00:00
179c8265cf
[#419] Added ability to view and remove API tokens. 2024-07-27 01:43:18 +02:00
c13623c3f7
[#419] API tokens - frontend implementation. 2024-07-26 21:59:14 +02:00
a8343cb45b
[UI] Persist current settings page on the URL. 2024-07-26 17:37:25 +02:00
91f6beb349
[#419] API tokens - backend implementation. 2024-07-26 02:29:40 +02:00
683ffa98c1
[Automatic] Updated UI files 2024-07-25 00:45:14 +00:00
6b5dbe7c1e
[#339] Frontend implementation of the new 2FA logic.
Closes: #339
2024-07-25 02:43:15 +02:00
7351a2685a
[Automatic] Updated components cache 2024-07-25 00:24:25 +00:00
79dc5e238d
[core] Skip 2FA code verification for JWT tokens. 2024-07-25 02:23:07 +02:00
a11f17aa8f
[core] Encrypt users 2FA backup codes with bcrypt.
Instead of RSA - decrypting is unnecessary.
2024-07-25 02:23:07 +02:00
67d8d0a515
[Automatic] Updated components cache 2024-07-24 22:48:49 +00:00
8ec1ca8543
[#339] Backend preparation for 2FA support. 2024-07-25 00:47:04 +02:00
2cbb005c67
[core] The generation of RSA keys should be behind shared process locks. 2024-07-25 00:38:30 +02:00
cf813e4197
[Automatic] Updated UI files 2024-07-24 19:36:47 +00:00
b1b51b4b7e
[tests] Fixed tests after HTTP auth refactor. 2024-07-24 21:34:30 +02:00
70db33b4e2
[core] Better Redis connection fail handling logic.
If the connection to Redis goes down, it shouldn't take down the main
thread.

Instead, catch `RedisConnectionError`, and execute `poll` in a loop
until the connection is restored.
2024-07-24 21:33:04 +02:00
357d92b479
[core] Added current_user() HTTP utility. 2024-07-24 00:49:21 +02:00
2033f9760a
[core] Refactoring user/authentication layer.
- Separated the user model/db classes from the `UserManager`.
- More consistent naming for the flag on the `authenticate_*` functions
  that enables returning a tuple with the authentication status - all
  those flags are now named `with_status`.
2024-07-23 22:44:40 +02:00
ee27b2c4c6
[core] Refactored Web login/registration layer.
Instead of having a single Flask-provided endpoint, the UI should
initialize its own Vue component and manage the authentication
asynchronously over API.

This is especially a requirement for the implementation of 2FA.

The following routes have also been merged/refactored:

- `POST /register` -> `POST /auth?type=register`
- `POST /login` -> `POST /auth?type=login`
- `POST /auth` -> `POST /auth?type=jwt`
2024-07-23 02:08:25 +02:00
8904e40f9f
[UI] Redirect URIs should always be relative to the current host. 2024-07-23 02:08:25 +02:00
fe2497577d
[Automatic] Updated UI files 2024-07-21 19:34:57 +00:00
01aedb5568
[UI] DropdownItem should emit @input together with @click.
The propagation of the `click` event shouldn't be stopped, as it is
required for the upstream Dropdown event to understand if it needs to
close.

Components should instead listen to `@input` events, so disabled items
will not be triggered.
2024-07-21 21:32:54 +02:00
2ccf00508d
[qrcode] Allow binary content for qrcode.generate. 2024-07-21 21:32:54 +02:00
8329de15ba
[UI] Added extra showError condition on /execute. 2024-07-21 21:32:53 +02:00
c1b1bd6c50
[UI] Added generic .text-danger class. 2024-07-21 21:32:49 +02:00
snyk-bot
9a388afd1d
fix: requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-ZIPP-7430899
2024-07-21 06:17:05 +00:00
snyk-bot
f63dd68043
fix: upgrade axios from 1.7.1 to 1.7.2
Snyk has created this PR to upgrade axios from 1.7.1 to 1.7.2.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-07-21 05:54:36 +00:00
snyk-bot
40c5a69a57
fix: upgrade vue from 3.4.29 to 3.4.31
Snyk has created this PR to upgrade vue from 3.4.29 to 3.4.31.

See this package in npm:
vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-07-21 05:54:32 +00:00
snyk-bot
5070b4dad5
fix: upgrade sass from 1.77.3 to 1.77.6
Snyk has created this PR to upgrade sass from 1.77.3 to 1.77.6.

See this package in npm:
sass

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-07-21 05:54:29 +00:00
snyk-bot
d2e51e7ee2
fix: upgrade vue-router from 4.3.3 to 4.4.0
Snyk has created this PR to upgrade vue-router from 4.3.3 to 4.4.0.

See this package in npm:
vue-router

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-07-21 05:54:25 +00:00
fdd42b8a22
[Automatic] Updated UI files 2024-07-20 22:00:27 +00:00
baa584c1ca
[Torrents UI] Fixed style for files list 2024-07-20 23:58:25 +02:00
929271ee0e
[Automatic] Updated components cache 2024-07-20 20:26:11 +00:00
f608475380 Fix outdated method name in sound plugin docs. 2024-07-20 20:25:21 +00:00
snyk-bot
5a1c6b064a
fix: requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-URLLIB3-7267250
2024-07-20 22:21:54 +02:00
f2b2189282
Merge pull request #437 from blacklight/snyk-fix-be2eea233b2d14c94dc846778f7390d9
[Snyk] Security upgrade zipp from 3.15.0 to 3.19.1
2024-07-20 12:43:52 +02:00
3e8ab8d0db
Merge branch 'master' into snyk-fix-be2eea233b2d14c94dc846778f7390d9 2024-07-20 12:43:44 +02:00
3f195b4e9c
Merge pull request #436 from blacklight/snyk-upgrade-68f4dd59f0141e45f5a75099ee1e596c
[Snyk] Upgrade vue from 3.4.27 to 3.4.29
2024-07-20 12:42:58 +02:00
e75a865c86
Merge branch 'master' into snyk-upgrade-68f4dd59f0141e45f5a75099ee1e596c 2024-07-20 12:42:51 +02:00
af187e95e5
Merge pull request #435 from blacklight/snyk-upgrade-207399cb68f0b666cd5a74f601c97857
[Snyk] Upgrade vue-router from 4.3.2 to 4.3.3
2024-07-20 12:41:55 +02:00
255422d5b8
Merge pull request #434 from blacklight/snyk-upgrade-e9c8d4be8f1465ea41d140fa3ac746c3
[Snyk] Upgrade sass from 1.77.2 to 1.77.3
2024-07-20 12:41:42 +02:00
18a7b05a80
Merge pull request #432 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/braces-3.0.3
Bump braces from 3.0.2 to 3.0.3 in /platypush/backend/http/webapp
2024-07-20 12:41:24 +02:00
0c8d86b8d4
Merge pull request #433 from blacklight/snyk-fix-08929d3c6ce8e8043728fa83145c4cd8
[Snyk] Security upgrade urllib3 from 2.0.7 to 2.2.2
2024-07-20 12:40:59 +02:00
6ab0c3d953
Merge pull request #431 from blacklight/snyk-upgrade-58571bd8b39cde152d941b6e296a9f10
[Snyk] Upgrade axios from 1.6.8 to 1.7.1
2024-07-20 12:40:41 +02:00
48996622d9
[README] Added Codefactor badge. 2024-07-20 12:13:10 +02:00
38edaaf311
[Automatic] Updated UI files 2024-07-20 10:09:48 +00:00
15b615efe8
[Camera UI] React on screen orientation changes to redraw the camera frame. 2024-07-20 12:07:51 +02:00
6e99e87aa6
[PWA] Allow any orientation. 2024-07-20 12:07:51 +02:00
a5c69d2ded
[Automatic] Updated components cache 2024-07-20 01:48:11 +00:00
7661d9c843
[#393] Added bind_socket parameter to backend.http.
The web server can now listen either on a TCP port, on a UNIX socket, or
both.

Closes: #393
2024-07-20 03:46:57 +02:00
9e36c5550f
[Automatic] Updated UI files 2024-07-20 00:59:17 +00:00
8291a97cd9
[Camera UI] Don't overflow the screen size. 2024-07-20 02:57:21 +02:00
579f9816e2
[Automatic] Updated UI files 2024-07-20 00:27:45 +00:00
b69e950076
[Camera UI] Dynamic fullscreen support. 2024-07-20 02:25:48 +02:00
3fddf67949
[Automatic] Updated UI files 2024-07-17 23:04:48 +00:00
4441461354
[Samsung TV UI] Improved UI style. 2024-07-18 01:02:51 +02:00
52f5ce2642
[Automatic] Updated components cache 2024-07-17 22:44:54 +00:00
392d64d03b
[tv.samsung] Reset the websocket connection in case of command error. 2024-07-18 00:43:12 +02:00
4d39791569
Dynamically generate install_requires through requirements.txt. 2024-07-18 00:04:04 +02:00
fabcba14d9
[CI/CD] Commented out the generation of the Arch platypush-git package.
See https://aur.archlinux.org/packages/platypush-git#comment-982845

If the PKGBUILD uses a dynamic `pkgver()` function, then `*-git`
packages shouldn't be updated unless there are some changes in the
PKGBUILD file itself.

See https://aur.archlinux.org/packages/platypush-git#comment-982845
2024-07-17 23:16:58 +02:00
38cf102397
[#401] Added --redis-bin/PLATYPUSH_REDIS_BIN option/variable.
Closes: #401
2024-07-17 23:11:48 +02:00
6d425b06f7
[Automatic] Updated UI files 2024-07-17 00:46:29 +00:00
234c3ce7d8
[UI] Added nav button to expand plugin views. 2024-07-17 02:44:33 +02:00
28ce11d636
[Automatic] Updated components cache 2024-07-17 00:32:01 +00:00
490ed4c361
[#413] /manifest.json should install PWAs for specific plugins.
If called on a `/plugin/<plugin>` route.
2024-07-17 02:30:47 +02:00
b039d98c66
[Packaging] Removed sudo and redis dependencies from Arch.
These are already available through the pacman/AUR helper and
`python-redis` respectively.

See https://aur.archlinux.org/packages/platypush#comment-982710
2024-07-17 02:30:47 +02:00
4c4e29b34e
[Automatic] Updated components cache 2024-07-16 23:27:52 +00:00
f55cacb2e3
[chore] Added missing parameter to docstring. 2024-07-17 01:26:17 +02:00
35c827ca4a
[Packaging] Exclude symlinks in the source package from installation.
It seems that `setup.py` started complaining about the installation of
non-regular files.

They seem to be created later anyway (as directories rather than
symlinks), so no further action should be required (hopefully).
2024-07-17 01:15:22 +02:00
b312f1717f
[Automatic] Updated components cache 2024-07-16 20:16:23 +00:00
a27955a583
[chore] Removed unused wheel dependency. 2024-07-16 22:14:56 +02:00
0e012c9800
[chore] Removed unused pytz dependency. 2024-07-16 22:09:35 +02:00
1d873aca05
[chore] Removed frozendict dependency.
It's no longer in use in the codebase.
2024-07-16 22:07:49 +02:00
8c0943e700
[CI/CD] Moved generation of Arch stable package to tag events.
Separating the generation of the Arch git package (on each commit to
master) from the generation of the Arch stable package (only on a new
tag) ensures that:

1. The checksum of the package isn't calculated on an older version of
   the archive.

2. The stable version of the package is always exactly aligned with the
   commit associated to the tag.
2024-07-16 22:03:32 +02:00
dc96b4995c
[core] Added ApplicationStartedEvent to Redis bus instead of application.
The Redis bus now uses a pub/sub architecture rather than a simple
queue.

Earlier on, the application could post an event to the queue and then
pick it up when it started listening.

When doing a publish on a pub/sub channel, however, any messages
sent before the client started listening will be lost.
2024-07-16 20:56:51 +02:00
837b0fad98
[Automatic] Updated UI files 2024-07-16 13:37:08 +00:00
03950e23f7
Bump version: 1.1.2 → 1.1.3 2024-07-16 15:34:50 +02:00
1f0aa0965d
Updated CHANGELOG 2024-07-16 15:31:32 +02:00
01af85d024
Updated UI files 2024-07-16 15:31:00 +02:00
c128887c3e
Increased maxkb limit 2024-07-16 15:30:50 +02:00
673a52fde5 Bumped setup.py version - bumpversion did not work again 2024-07-16 13:00:45 +02:00
cf5993ede0 CHANGELOG update 2024-07-16 12:56:27 +02:00
6f8a81f020
Bump version: 1.1.1 → 1.1.2 2024-07-16 12:01:45 +02:00
71ff453587
Updated CHANGELOG. 2024-07-16 12:00:18 +02:00
484959a153 Merge pull request 'New YouTube UI features' (#411) from 391/improve-youtube-support into master
Reviewed-on: platypush/platypush#411

Closes: #391
2024-07-16 03:58:37 +02:00
329296b606
Merge branch 'master' into 391/improve-youtube-support 2024-07-16 03:56:12 +02:00
b8d8b48d73
[Automatic] Updated components cache 2024-07-16 01:52:02 +00:00
398925d76e
[media] Added only_audio option to media.download. 2024-07-16 03:50:14 +02:00
b44bd0be32
[Media UI] Download Audio support. 2024-07-16 03:48:45 +02:00
5ebdb381f1
[File UI] Persist the path on the URI. 2024-07-16 03:12:18 +02:00
aa92db9850
[UI] A more robust way to encode/decode URI arguments. 2024-07-16 03:10:54 +02:00
e710a3a974
[Media UI] Support for open-channel events from any media item. 2024-07-16 02:30:47 +02:00
c95381cead
[Media UI] Added more permalinks.
- `channel`
- `playlist`
2024-07-16 01:19:29 +02:00
c5ac02d133
[Media UI] Misc style improvements. 2024-07-16 01:18:33 +02:00
910304b817
[Media UI] More URI-persisted navigation items.
- `player`
- `provider`
2024-07-15 23:12:04 +02:00
e8723eae98
Merge branch 'master' into 391/improve-youtube-support 2024-07-15 22:35:24 +02:00
061e5a67a2
Merge branch 'master' into 391/improve-youtube-support 2024-07-15 22:35:01 +02:00
a746273f73
[Media UI] Added media view to URL fragment. 2024-07-15 22:34:32 +02:00
e180c9c76f
[Media UI] Extend YouTube video events to all media views.
These events should be available for all YouTube videos, regardless of
where they are rendered:

- `add-to-playlist`
- `remove-from-playlist`
- `download`
2024-07-15 22:32:13 +02:00
c416d0ea1f
[Media UI] MediaImage should emit both play and select. 2024-07-15 22:28:06 +02:00
75aed6af92
[UI] Added asynchronous timeout utility. 2024-07-15 22:26:42 +02:00
ef4d0bd38c
[media] Support for generic media downloads. 2024-07-15 04:09:54 +02:00
bd01827b52
[Automatic] Updated components cache 2024-07-15 04:09:54 +02:00
f64d47565d
[Media UI] Support for generic media download. 2024-07-15 04:09:54 +02:00
79ba8deb71
[media] Added support for yt-dlp-compatible URLs to media.download.
Also, added `MediaDownloadEvent`s to keep track of the state of the
download.
2024-07-15 04:09:53 +02:00
84e06e30fe
[core] New architecture for the Redis bus.
- Use pubsub pattern rather than `rpush`/`blpop` - it saves memory, it's
  faster, and it decreases the risk of deadlocks.

- Use a connection pool.

- Propagate `PLATYPUSH_REDIS_QUEUE` environment variable so any
  subprocesses can access it.
2024-07-15 04:09:53 +02:00
f78027a6eb
[Automatic] Updated components cache 2024-07-15 02:09:39 +00:00
b43c4612fd
[media] Support for generic media downloads. 2024-07-15 04:08:26 +02:00
dce6096020
[Automatic] Updated components cache 2024-07-14 01:07:51 +00:00
96aa22c03e
[media] Added support for yt-dlp-compatible URLs to media.download.
Also, added `MediaDownloadEvent`s to keep track of the state of the
download.
2024-07-14 03:06:36 +02:00
16527417da
[core] New architecture for the Redis bus.
- Use pubsub pattern rather than `rpush`/`blpop` - it saves memory, it's
  faster, and it decreases the risk of deadlocks.

- Use a connection pool.

- Propagate `PLATYPUSH_REDIS_QUEUE` environment variable so any
  subprocesses can access it.
2024-07-14 03:06:35 +02:00
1ad68cac11 Merge branch 'master' into 391/improve-youtube-support 2024-07-13 00:55:34 +02:00
972f9dffb9
[YouTube UI] Fixed infinite scroll for channels. 2024-07-13 00:54:29 +02:00
6a0f19a62f
[Automatic] Updated components cache 2024-07-12 22:42:49 +00:00
81fb1a47c3
Merge branch 'master' into 391/improve-youtube-support 2024-07-13 00:41:46 +02:00
24b5b3ba14
[youtube] Added youtube.is_subscribed action. 2024-07-13 00:41:38 +02:00
21ac87394a
[YouTube UI] Added channel subscribe/unsubscribe buttons. 2024-07-13 00:40:29 +02:00
55c4f5797b
[YouTube UI] Added support for browsing channels from search results. 2024-07-13 00:04:26 +02:00
c7f12e0bd8
[Automatic] Updated components cache 2024-07-12 01:13:48 +00:00
5ff839919c
Merge branch 'master' into 391/improve-youtube-support 2024-07-12 03:12:39 +02:00
7266fe8a43
[youtube] Always add id and url to playlist results. 2024-07-12 03:12:31 +02:00
c8fa53e62f
[media.mpv] Fix for media duration if playback_time is not available. 2024-07-12 03:12:31 +02:00
e65bf99baf
[YouTube UI] Added support for browsing playlists from search results. 2024-07-12 03:10:43 +02:00
9b42815d77
[Automatic] Updated components cache 2024-07-11 21:45:09 +00:00
7c610413df
Merge branch 'master' into 391/improve-youtube-support 2024-07-11 23:44:07 +02:00
aaf6c39255
Fixed RST doc typo. 2024-07-11 23:43:53 +02:00
a4979f1513
[Media UI] Support for playlists in search results. 2024-07-11 23:42:18 +02:00
ded64e8dc2
[UI Performance] Lazy initialization for router components. 2024-07-11 23:40:19 +02:00
1a9ac56923
Merge branch 'master' into 391/improve-youtube-support 2024-07-11 23:37:57 +02:00
12d53b846e
[Automatic] Updated components cache 2024-07-11 21:37:44 +00:00
5e905db0f5
[youtube] Support for playlists and channels in search results. 2024-07-11 23:36:25 +02:00
snyk-bot
17dbd91198
fix: requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-ZIPP-7430899
2024-07-10 04:55:05 +00:00
snyk-bot
e70f12b948
fix: upgrade vue from 3.4.27 to 3.4.29
Snyk has created this PR to upgrade vue from 3.4.27 to 3.4.29.

See this package in npm:
vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-07-06 04:46:46 +00:00
snyk-bot
e59606e588
fix: upgrade vue-router from 4.3.2 to 4.3.3
Snyk has created this PR to upgrade vue-router from 4.3.2 to 4.3.3.

See this package in npm:
vue-router

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-07-02 06:32:38 +00:00
98a6adb7ef
[Youtube UI] More playlist actions.
- `create_playlist`
- `rename_playlist`
- `delete_playlist`
2024-06-27 01:26:00 +02:00
d4354e81f8
[Youtube UI] Added playlist operations.
- `add_to_playlist`
- `remove_from_playlist`
2024-06-27 00:24:31 +02:00
c9daa037a7
[Automatic] Updated components cache 2024-06-26 22:23:01 +00:00
701623c99d
Merge branch 'master' into 391/improve-youtube-support 2024-06-27 00:22:22 +02:00
8880b966fc
[youtube] Fixed playlist operations URLs. 2024-06-27 00:21:27 +02:00
5d4bfb3f90
Fixed un-bumped version in setup.py 2024-06-27 00:21:03 +02:00
5f23aa8e78 Merge branch 'master' into 391/improve-youtube-support 2024-06-26 00:05:22 +02:00
8cea668e37
[Automatic] Updated components cache 2024-06-25 22:04:33 +00:00
26d9e6d9b9 Merge branch 'master' into 391/improve-youtube-support 2024-06-26 00:03:47 +02:00
b890326e71 [youtube] Added Piped edit actions.
- `subscribe`
- `unsubscribe`
- `add_to_playlist`
- `remove_from_playlist`
- `create_playlist`
- `remove_playlist`
2024-06-25 22:03:40 +00:00
b2b59d651c Merge pull request '[youtube] Added Piped edit actions.' (#412) from 391/improve-youtube-support-backend into master
Reviewed-on: platypush/platypush#412
2024-06-26 00:03:21 +02:00
b83dee50e5 Merge branch 'master' into 391/improve-youtube-support-backend 2024-06-26 00:02:26 +02:00
773986f211
[youtube] Added Piped edit actions.
- `subscribe`
- `unsubscribe`
- `add_to_playlist`
- `remove_from_playlist`
- `create_playlist`
- `remove_playlist`
2024-06-25 23:59:20 +02:00
705ba82fa1
[Media UI] Added button to get the raw stream URL from youtube-dl compatible media. 2024-06-25 23:03:44 +02:00
affe95be96
[Automatic] Updated components cache 2024-06-25 21:02:16 +00:00
6faa845afd
Migrated /file route.
Streaming content from a Flask route wrapped into a Tornado route is a
buffering nightmare.

`/file` has now been migrated to a pure Tornado asynchronous route
instead.
2024-06-25 23:00:51 +02:00
c7ee97bb0b
Bump version: 1.1.0 → 1.1.1 2024-06-25 01:51:27 +02:00
5799a2b352
Updated CHANGELOG 2024-06-25 01:51:27 +02:00
1a21671dde
[Automatic] Updated components cache 2024-06-24 18:30:48 +00:00
1f544c9e53
Fixed schema attribute description type mismatch. 2024-06-24 20:29:35 +02:00
a754b06f88
[Automatic] Updated UI files 2024-06-24 17:23:31 +00:00
3dc1ff3c6e
[#408] Rewritten+expanded torrent UI.
Closes: #408
2024-06-24 19:20:04 +02:00
1774e464cc
[torrent] Added is_media attribute to torrent results. 2024-06-24 01:12:00 +02:00
1dd905dc66
[torrent] Normalized limit/page parameters in torrent.search. 2024-06-23 23:42:04 +02:00
ec050b2853
[HTTP] Added authenticated /file?path=<path> route. 2024-06-23 23:40:39 +02:00
9993e9b6b7
[Chore] Updated pre-commit dependencies. 2024-06-23 23:40:01 +02:00
5672b23fbe
[Automatic] Updated UI files 2024-06-22 23:43:49 +00:00
744da20b7c Updated CHANGELOG 2024-06-22 23:42:18 +00:00
91e2530dd5 [#407] Implemented torrent.csv backend 2024-06-22 23:42:18 +00:00
8fc3201b8c [torrent] Refactored torrent search.
Allow for more torrent search providers other than PopcornTime (in
preparation for torrent-csv).
2024-06-22 23:42:18 +00:00
snyk-bot
9027eaf4d1
fix: upgrade sass from 1.77.2 to 1.77.3
Snyk has created this PR to upgrade sass from 1.77.2 to 1.77.3.

See this package in npm:
sass

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-20 20:17:29 +00:00
snyk-bot
dedfc0bc3a
fix: requirements.txt to reduce vulnerabilities
The following vulnerabilities are fixed by pinning transitive dependencies:
- https://snyk.io/vuln/SNYK-PYTHON-URLLIB3-7267250
2024-06-18 18:27:14 +00:00
dependabot[bot]
8b65a5f151
Bump braces from 3.0.2 to 3.0.3 in /platypush/backend/http/webapp
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-06-18 02:36:09 +00:00
snyk-bot
d8598f60a2
fix: upgrade axios from 1.6.8 to 1.7.1
Snyk has created this PR to upgrade axios from 1.6.8 to 1.7.1.

See this package in npm:
axios

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-10 17:58:39 +00:00
36f49952c4
Merge pull request #430 from blacklight/snyk-upgrade-710b29e45259ad93d91928143706d645
[Snyk] Upgrade sass from 1.76.0 to 1.77.2
2024-06-10 16:33:58 +02:00
dff6aeec6d
Merge pull request #429 from blacklight/snyk-upgrade-1ae729d9a265d3887d0b23573e3b6020
[Snyk] Upgrade vue from 3.4.24 to 3.4.27
2024-06-10 16:33:41 +02:00
snyk-bot
d6ab2ee02b
fix: upgrade sass from 1.76.0 to 1.77.2
Snyk has created this PR to upgrade sass from 1.76.0 to 1.77.2.

See this package in npm:
sass

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-07 17:21:22 +00:00
snyk-bot
2e08e2f820
fix: upgrade vue from 3.4.24 to 3.4.27
Snyk has created this PR to upgrade vue from 3.4.24 to 3.4.27.

See this package in npm:
vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-07 17:21:17 +00:00
ed697c0ad2
🐛 [CI/CD] Fixed build-ui.sh script.
After doing "cd $SRCDIR", it should reference the `dist` directory in
the webapp by full relative path.
2024-06-06 22:46:37 +02:00
22cfe777fa
Merge pull request #428 from blacklight/snyk-upgrade-2f9bcb05344a53203d1db8700a74298c
[Snyk] Upgrade core-js from 3.37.0 to 3.37.1
2024-06-06 22:32:31 +02:00
3f2832a077
Merge branch 'master' into snyk-upgrade-2f9bcb05344a53203d1db8700a74298c 2024-06-06 22:32:04 +02:00
6f8eb397d2
Merge pull request #427 from blacklight/snyk-upgrade-97c24303ee224553f29b460d83c6c780
[Snyk] Upgrade cronstrue from 2.49.0 to 2.50.0
2024-06-06 22:30:59 +02:00
3163721bf3
Merge pull request #426 from blacklight/snyk-upgrade-26bc4dca62d58f39bfb77f2e69121708
[Snyk] Upgrade sass from 1.75.0 to 1.76.0
2024-06-06 22:30:43 +02:00
d79b8a1de5
Merge pull request #425 from blacklight/snyk-upgrade-a75151e7066361ecbf2a647d6e707a32
[Snyk] Upgrade vue from 3.4.23 to 3.4.24
2024-06-06 22:30:20 +02:00
3afc6b2271
[Automatic] Updated components cache 2024-06-06 20:29:13 +00:00
17b6b02986 Replaced warnings.warn with logging.warnings.
I couldn't find an easy and reliable way of routing `warnings.warn` to
`logging`.

Closes: #281
2024-06-06 20:28:23 +00:00
87a902bfa3
[Automatic] Updated components cache 2024-06-06 01:28:13 +00:00
421feffd3e
Bump version: 1.0.7 → 1.1.0 2024-06-06 03:27:04 +02:00
518dc146d6
Bumped version in CHANGELOG. 2024-06-06 03:27:04 +02:00
6b11db7afb
[docs] Added inherited-members.
Closes: #403
2024-06-06 03:27:03 +02:00
8814859abc
[zwave.mqtt] Fixed typo in documentation. 2024-06-06 03:27:03 +02:00
0ccd029ff1
[Automatic] Updated components cache 2024-06-06 00:25:21 +00:00
e52f5e06f4 [calendar.ical] Fixed timezone/datetime parsing issues.
Closes: #405
2024-06-06 00:24:31 +00:00
snyk-bot
066d71faa3
fix: upgrade core-js from 3.37.0 to 3.37.1
Snyk has created this PR to upgrade core-js from 3.37.0 to 3.37.1.

See this package in npm:
core-js

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-06-04 18:18:54 +00:00
4f19b45975 Bump version: 1.0.6 → 1.0.7 2024-06-02 15:49:59 +00:00
7d6ffc76fb [CI/CD] The build-ui script should go back to the source root after running the UI build commands from the UI folder 2024-06-02 15:46:18 +00:00
7a8f30e5e0 [#384] Added assistant.openai and tts.openai plugins.
Closes: #384
2024-06-02 15:31:11 +00:00
3528b3646f [openai] Update documentation to include assistant and tts. 2024-06-02 15:31:11 +00:00
9cca928d4b [#348] Added openai.transcribe action.
This API is the foundation for the `assistant.openai` plugin.
2024-06-02 15:31:11 +00:00
f356fcd844 Added tts.stop method. 2024-06-02 15:31:11 +00:00
fcae7aa3ad Several improvements for assistant plugins.
- `stop_conversation_on_speech_match` should default to True.

- `render_response` should also handle conversation follow-ups, set the
  follow-up to True if the response ends with a question mark and the
  value of `with_follow_on_turn` is not set,

- Don't render responses if a `tts_plugin` is not set.
2024-06-02 15:31:11 +00:00
c7d640a1d2 IntentRecognizedEvent should stop the current assistant conversation when matched by a hook. 2024-06-02 15:31:11 +00:00
1cc2aaf5a4 [assistant.picovoice] _on_response_* methods should have varargs. 2024-06-02 15:31:11 +00:00
2acf6ef3e9 Bump version: 1.0.5 → 1.0.6 2024-06-01 09:01:07 +00:00
1107e526f7 Updated CHANGELOG 2024-06-01 09:01:07 +00:00
5fc9c1199b Fixed the root cause of the failure on the time module.
The previous commit prompted a new error:

```
2024-06-01 10:54:08,310|ERROR|platypush:plugin:bluetooth|module 'platypush.entities.time' has no attribute 'time'
Traceback (most recent call last):
  File "/usr/lib/python3.9/dist-packages/platypush/plugins/__init__.py", line 247, in _runner
    self.main()
  File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 590, in main
    self._refresh_cache()
  File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 146, in _refresh_cache
    get_entities_engine().wait_start()
  File "/usr/lib/python3.9/dist-packages/platypush/entities/__init__.py", line 48, in get_entities_engine
    time_start = time.time()
AttributeError: module 'platypush.entities.time' has no attribute 'time'
```

Which explains even the previous error: `import time` in that module
won't use the `time` module from the Python library, but the `.time`
module within the same directory.

This error only happens when the current directory is part of PYTHONPATH
(and usually it shouldn't), but for sake of keeping things safe I've
replaced `time()` with `utcnow().timestamp()`, with `utcnow` imported
from `platypush.utils`.
2024-06-01 09:01:07 +00:00
b067430cd5 Weird fix for a weird error that suddenly started on one of my machines.
```
Traceback (most recent call last):
  File "/usr/lib/python3.9/dist-packages/platypush/plugins/__init__.py", line 247, in _runner
    self.main()
  File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 590, in main
    self._refresh_cache()
  File "/usr/lib/python3.9/dist-packages/platypush/plugins/bluetooth/__init__.py", line 146, in _refresh_cache
    get_entities_engine().wait_start()
  File "/usr/lib/python3.9/dist-packages/platypush/entities/__init__.py", line 48, in get_entities_engine
    time_start = time()
TypeError: 'module' object is not callable
```

There isn't a single reason in this world for this error to happen.

If I do `from time import time`, then `t = time()` is 100% valid Python.

I have no clue of what may be causing it, but I hope that this will fix
it.
2024-06-01 08:50:30 +00:00
ff60896625
[Automatic] Updated components cache 2024-05-31 23:55:27 +00:00
67b6e3a608
Bump version: 1.0.4 → 1.0.5 2024-06-01 01:43:12 +02:00
c61a1b89d6
Updated CHANGELOG 2024-06-01 01:42:21 +02:00
c9a5c29a4a
🐛 A proper cross-version solution for the utcnow() issue.
No need to maintain two different pieces of logic - a `utcnow()` for
Python < 3.11 and `now(datetime.UTC)` for Python >= 3.11.

`datetime.timezone.utc` existed long before datetime.UTC and that's what
the `utcnow` facade should use.

This means that all the `utcnow()` will always have `tzinfo=UTC`
regardless of the Python version.

There's still a problem with the `utcnow()`-generated timestamps that
have been generated by previous versions of Python and stored on the db.

Therefore, when the code performs comparisons with timestamps fetched
from the db, it should always explicitly do a `.replace(tzinfo=utc)` to
ensure that we always compare offset-aware datetime representations.

See blog post for technical details:
https://manganiello.blog/wheres-my-time-again
2024-06-01 01:34:47 +02:00
1067ab04d9
[tts.picovoice] Adapted to the new orca.synthesize API.
The new API no longer returns a list of numeric values alone. Instead,
it returns a tuple where the first element is the raw audio, and the
second element contains extra info on the rendered phonemes.
2024-05-31 21:10:48 +02:00
709b90fa4b
Merge branch 'master' into 384/assistant-openai 2024-05-31 21:07:06 +02:00
06f0ac4545
[Automatic] Updated components cache 2024-05-31 17:59:06 +00:00
944fd45f9f
Bump version: 1.0.3 → 1.0.4 2024-05-31 19:57:51 +02:00
6acdde6164
Updated CHANGELOG 2024-05-31 19:57:43 +02:00
3583dafbc3
🐛 Partial revert of c18768e61f
`datetime.utcnow` may be deprecated on Python >= 3.12, but
`datetime.UTC` isn't present on older Python versions.

Added a `platypush.utils.utcnow()` method as a workaround compatible
with both.
2024-05-31 19:55:19 +02:00
4513bb9569
Set a plugin argument on AssistantEvents besides assistant.
`assistant` contains the assistant plugin object that triggered the
event, but you can't create event hook conditions on attributes that are
plugins.

The event should also store a `plugin` attribute which contains the
unique plugin name, so hooks like these can be built:

```
from platypush import hook
from platypush.events.assistant import ConversationStartEvent

@when(ConversationStartEvent, plugin="assistant.google")
def on_google_conversation_start():
  ...
```

It wouldn't be possible to construct a hook condition like the one above
on the plugin object reported on the `assistant` attribute.
2024-05-31 19:55:19 +02:00
4e82dd17bb
🐛 Partial revert of c18768e61f
`datetime.utcnow` may be deprecated on Python >= 3.12, but
`datetime.UTC` isn't present on older Python versions.

Added a `platypush.utils.utcnow()` method as a workaround compatible
with both.
2024-05-31 19:52:32 +02:00
e982c02524
Set a plugin argument on AssistantEvents besides assistant.
`assistant` contains the assistant plugin object that triggered the
event, but you can't create event hook conditions on attributes that are
plugins.

The event should also store a `plugin` attribute which contains the
unique plugin name, so hooks like these can be built:

```
from platypush import hook
from platypush.events.assistant import ConversationStartEvent

@when(ConversationStartEvent, plugin="assistant.google")
def on_google_conversation_start():
  ...
```

It wouldn't be possible to construct a hook condition like the one above
on the plugin object reported on the `assistant` attribute.
2024-05-31 19:29:50 +02:00
d9a5ea1e53
Merge branch 'master' into 384/assistant-openai 2024-05-31 02:58:08 +02:00
23e02de1d7
Bump version: 1.0.2 → 1.0.3 2024-05-31 02:57:08 +02:00
4d0b63a155
Updated CHANGELOG 2024-05-31 02:57:08 +02:00
ce1525e786
[Automatic] Updated components cache 2024-05-31 00:53:37 +00:00
67478e7ca1 🐛 Fixed proper support for event package alias platypush.events.
Even though `platypush.events` is just a symlink to
`platypush.message.event`, imports from those two modules will be
treated as different imports, thus hook conditions build on
`platypush.events` imports will never match.
2024-05-31 00:52:48 +00:00
c18768e61f Replaced deprecated usages of datetime.utcnow() with datetime.now(UTC). 2024-05-31 00:52:48 +00:00
30362b89e3 [assistant] tts_plugin_args should include join=True by default.
The assistant by default should be configured to wait for response audio
to be fully rendered before proceeding.
2024-05-31 00:52:48 +00:00
826a3fa55c CHANGELOG update 2024-05-31 00:52:48 +00:00
3986549326
🐛 Fixed proper support for event package alias platypush.events.
Even though `platypush.events` is just a symlink to
`platypush.message.event`, imports from those two modules will be
treated as different imports, thus hook conditions build on
`platypush.events` imports will never match.
2024-05-31 02:50:00 +02:00
fa318882a5
Replaced deprecated usages of datetime.utcnow() with datetime.now(UTC). 2024-05-31 02:30:48 +02:00
d6185ddb1e
[assistant] tts_plugin_args should include join=True by default.
The assistant by default should be configured to wait for response audio
to be fully rendered before proceeding.
2024-05-30 01:33:56 +02:00
snyk-bot
fee12951d6
fix: upgrade cronstrue from 2.49.0 to 2.50.0
Snyk has created this PR to upgrade cronstrue from 2.49.0 to 2.50.0.

See this package in npm:
cronstrue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-27 17:30:57 +00:00
d2caa989ac
CHANGELOG update 2024-05-27 00:18:27 +02:00
fa3c804b71 [#368] Added Ubuntu release logic to update-apt-repo step too. 2024-05-26 20:57:10 +00:00
4cd0761e78
[#368] Added Ubuntu release logic to update-apt-repo step too. 2024-05-26 22:56:23 +02:00
16f7b7e12a Merge pull request '[#368] Added Ubuntu packages' (#402) from 368/ubuntu-packages into master
Reviewed-on: platypush/platypush#402

Closes: #368
2024-05-26 22:41:44 +02:00
22222fab65
[#368] Added Ubuntu packages 2024-05-26 22:38:22 +02:00
5b3c0ad1cf
Bump version: 1.0.1 → 1.0.2 2024-05-26 11:09:51 +02:00
3758a8d759
Updated CHANGELOG 2024-05-26 11:09:42 +02:00
de2bbc53c6
Support both @procedure and @procedure(name) notations. 2024-05-26 11:02:19 +02:00
a4a776986b
Bump version: 1.0.0 → 1.0.1 2024-05-26 04:27:27 +02:00
9fef73a746
Bumped version in setup.py (for some reason bumpversion missed it) 2024-05-26 04:27:03 +02:00
0f6f119089
Bump version: 0.99.11 → 1.0.0 2024-05-26 04:15:52 +02:00
c64ff40dd3
Bump version: 0.99.10 → 0.99.11 2024-05-26 04:06:15 +02:00
5c0f85c311
Don't provide git+https:// dependencies in setup.py extras.
Otherwise Twine will complain with errors like this:

```
HTTPError: 400 Bad Request from https://upload.pypi.org/legacy/
Can't have direct dependency: pybluez@
git+https://github.com/pybluez/pybluez ; extra == "bluetooth". See
https://packaging.python.org/specifications/core-metadata for more
information.
```
2024-05-26 04:04:58 +02:00
e6702398dc
Bump version: 0.99.9 → 0.99.10 2024-05-26 03:46:51 +02:00
983bcc240a
[Docs] A more robust interceptor for the grid rendering. 2024-05-26 03:46:25 +02:00
d6d9d7a8e7
[CI/CD] Added extra dependencies to update-pip-package step. 2024-05-26 03:40:10 +02:00
8d26721040
Bump version: 0.99.8 → 0.99.9 2024-05-26 03:32:49 +02:00
96f265a4a2
[CI/CD] Use a base Alpine image instead of python:3.11-alpine.
Weird errors seem to happen on Twine on that image:

```
Traceback (most recent call last):
  File "/usr/bin/twine", line 5, in <module>
    from twine.__main__ import main
  File "/usr/lib/python3.11/site-packages/twine/__init__.py", line 32, in <module>
    import importlib.metadata
  File "/usr/lib/python3.11/importlib/metadata/__init__.py", line 17, in <module>
    from . import _adapters, _meta
  File "/usr/lib/python3.11/importlib/metadata/_adapters.py", line 3, in <module>
    import email.message
  File "/usr/lib/python3.11/email/message.py", line 15, in <module>
    from email import utils
  File "/usr/lib/python3.11/email/utils.py", line 28, in <module>
    import random
  File "/usr/lib/python3.11/random.py", line 49, in <module>
    from math import log as _log, exp as _exp, pi as _pi, e as _e, ceil as _ceil
ImportError: Error relocating /usr/lib/python3.11/lib-dynload/math.cpython-311-x86_64-linux-musl.so: _PyModule_Add: symbol not found
```
2024-05-26 03:31:25 +02:00
0ffff854d3
Bump version: 0.99.7 → 0.99.8 2024-05-26 03:18:16 +02:00
020804fd1c
Don't link wiki/Home.md to wiki/index.md 2024-05-26 03:16:32 +02:00
42174b31bc
Bump version: 0.99.6 → 0.99.7 2024-05-26 03:12:26 +02:00
3642d1ffa4
Added dns to mocked modules. 2024-05-26 03:02:57 +02:00
104457a302
Bump version: 0.99.5 → 0.99.6 2024-05-26 01:47:22 +02:00
0445087699
[CI/CD] Removed hanging dependency. 2024-05-26 01:47:05 +02:00
751d719b04
Bump version: 0.99.4 → 0.99.5 2024-05-26 01:46:29 +02:00
bef027fc07
[CI/CD] Just remove the sync-stable-branch step. 2024-05-26 01:46:04 +02:00
cc670f9d4a
Bump version: 0.99.3 → 0.99.4 2024-05-26 01:26:46 +02:00
86674ddc28
[CI/CD] Do a git fetch beofre checkout/rebase in sync-stable-branch. 2024-05-26 01:26:19 +02:00
ee3933dc77
Bump version: 0.99.2 → 0.99.3 2024-05-26 00:56:29 +02:00
e23664b5e7
[CI/CD] Be explicit about the origin in sync-stable-branch. 2024-05-26 00:56:01 +02:00
0537815721
Bump version: 0.99.1 → 0.99.2 2024-05-26 00:50:37 +02:00
a2ec20bb3a
[CI/CD] Create stable branch if it doesn't exist. 2024-05-26 00:50:02 +02:00
d3562f4d20
Bump version: 0.99.0 → 0.99.1 2024-05-26 00:44:30 +02:00
bf5aece08b
Bumped version in setup.py (for some reason bumpversion missed it) 2024-05-26 00:44:20 +02:00
2f20580498
[CI/CD] Remove git remote rm/add logic from sync-stable-branch. 2024-05-26 00:39:51 +02:00
1911c05afe
Bump version: 0.50.3 → 0.99.0 2024-05-26 00:25:34 +02:00
cac256af08
Updated CHANGELOG 2024-05-26 00:21:40 +02:00
a784a6fe23
The index symlink is not needed. 2024-05-25 23:23:15 +02:00
0baae01ab7
[README and wiki changes]
- Renamed _Post-installation_ README section as _Configuration_.

- Docs style tweaks for the latest version of the Sphinx theme.

- Adapted the docs index to the new structure of the wiki.
2024-05-25 23:17:12 +02:00
f70d352cd7
[README] Added database, workdir and device ID sections. 2024-05-25 22:14:15 +02:00
7c7e488867
Added example with return to the webhook handler. 2024-05-25 22:05:09 +02:00
baee33b88f
README.
- Added/restyled badges.

- Added sections on the scripts directory and YAML `include` directive.
2024-05-25 21:43:44 +02:00
6de0b2e041
[Automatic] Updated components cache 2024-05-25 18:12:49 +00:00
35751da068
Added web hooks section to README. 2024-05-25 20:11:07 +02:00
0479c37d64
Full rewrite of the README. 2024-05-25 18:13:58 +02:00
3a4d7afcf0
Clean up all package manager caches after Docker build. 2024-05-25 14:15:13 +02:00
e071e99dab
Remove all cached/compiled Python files after Docker build. 2024-05-25 13:36:52 +02:00
2e0246413c
Replaced an old for ... in ... yield loop with yield from ... 2024-05-25 10:35:23 +02:00
b1dd484704
A more corner-case-proof logic for wants_break_system_packages. 2024-05-25 10:33:05 +02:00
303b11613b
s/Dockerfile.default/Dockerfile.alpine/ 2024-05-25 10:30:53 +02:00
05c6449d8b
Always add --break-system-packages to pip when the Docker context is active.
This fixes the case where Platydock is called within the context of a
virtual environment, but it needs to generate a Docker image - and
therefore, unless the host virtual environment, it needs
--break-system-packages to write to /usr.
2024-05-25 10:30:52 +02:00
5e52741986
[Automatic] Updated components cache 2024-05-25 08:19:19 +00:00
7d8a00696c
Adapted Platydock to the new Dockerfile format. 2024-05-25 10:17:45 +02:00
254604e404
py3-vlc has not been merged upstream in Alpine Linux yet. 2024-05-25 10:17:19 +02:00
8f0002ae40
Moved /Dockerfile to /Dockerfile.default.
It can mess up the Alpine Dockerfile if platydock is run from the source
directory.
2024-05-25 10:16:29 +02:00
8e05a7f4c9
Make Dockerfiles work both within and outside a Platypush source dir.
If the Platypush setup.py is found in the current directory, then use
that directory as the base for the new image.

Otherwise, clone the repo on the fly and build the image from there.
2024-05-25 00:42:30 +02:00
0fd2992894
Added platypush.events alias for platypush.message.event package. 2024-05-24 23:16:15 +02:00
efd97f7186
Added a docker-compose.yml. 2024-05-24 23:15:37 +02:00
3ccdd4d179
Updated pip ... --break-system-packages configuration.
All the latest versions of Alpine, Debian, Ubuntu and Fedora now require
`--break-system-packages` when installing packages via `pip` outside of
a virtual environment, even if it's within a container.
2024-05-24 22:59:42 +02:00
d20cd4b058
Added a nice ASCII-art logo at application startup. 2024-05-24 22:57:50 +02:00
c49b4ca273
platypush.run should also support procedures in the format procedure.<name>. 2024-05-24 20:20:25 +02:00
5c2204f99d
Allow for custom procedure names on the @procedure decorator.
```
@procedure("foo")
def bar():
    ...
```

Will now be published as `procedure.foo` instead of
`procedure.<module>.bar`.
2024-05-24 20:07:24 +02:00
cbc58c7330
Fix: /var/lib/platypush is still empty at the time of package creation. 2024-05-23 03:37:46 +02:00
7ca2159acb
Fix: missing newline escape 2024-05-23 03:34:00 +02:00
2dd5b6c122
systemd-rpm-macros is a build requirement for the Fedora build. 2024-05-23 03:28:56 +02:00
a51d8978e7
[#319] Added /etc/platypush and /var/lib/platypush to rpm packages. 2024-05-23 03:24:24 +02:00
a5adac9314
[#319] Added /etc and /var/lib dirs to Debian package. 2024-05-23 02:46:38 +02:00
a211e2e2e4
Changed default permissions for /var/lib/platypush from 0755 to 0750. 2024-05-23 01:15:26 +02:00
7fa0dbda7b
Split Drone CI steps into separate shell scripts.
A fully self-contained 1.5k LoC Drone file isn't very maintainable, and
it makes it hard to reuse parts that are shared across multiple steps
(like SSH and git configuration).
2024-05-23 00:17:55 +02:00
snyk-bot
46da373637
fix: upgrade sass from 1.75.0 to 1.76.0
Snyk has created this PR to upgrade sass from 1.75.0 to 1.76.0.

See this package in npm:
sass

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-22 12:34:49 +00:00
4038ef3bc1
[#319] Added platypush/config/systemd directory.
It contains both `platypush.service` and the `sysusers.d` and
`tmpfiles.d` configurations used by the package managers.
2024-05-20 22:19:30 +02:00
1cad0394ab
systemd requirements tweak.
`Requires=redis.service` should be commented unless the service is
started as a privileged user.

Also added some comments on how the systemd service usually works.
2024-05-20 21:51:08 +02:00
02a3385638
Tweaked Platypush systemd service.
- A more informative Description
- `s/WantedBy=default.target/WantedBy=multi-user.target/`
- `redis.service` is a strong requirement
2024-05-20 13:02:36 +02:00
c899627eca
[Automatic] Updated components cache 2024-05-20 00:03:25 +00:00
d0f781919d
[#345] Rewritten sun plugin.
Closes: #345
2024-05-20 02:01:40 +02:00
86b4b14112
Added the current running application as a static object.
```python
from platypush import app
```
2024-05-20 01:54:25 +02:00
32b8296244
[#400] Dynamic logic to infer procedures/hooks arguments.
This allows procedures and event hooks to have more flexible signatures.
Along the lines of:

```python
@when(SomeEvent)
def hook(event):
  ...

@when(SomeOtherEvent)
def hook2():
  ...
```

Instead of supporting only the full context spec:

```python
@when(SomeEvent)
def hook(event, **ctx):
  ...
```

Closes: #400
2024-05-19 02:17:42 +02:00
2ab1743bec
[Docs UI] Fix filter bar to the top while scrolling. 2024-05-19 02:17:42 +02:00
70ffe0ec04
[Automatic] Updated components cache 2024-05-17 00:23:06 +00:00
3d5c60e4f4
[UI docs] Added filter bar for integrations and events. 2024-05-17 02:21:57 +02:00
f06233801b
[#394] Dynamically generate setup extras.
Also, convert all code that relied on `manifest.yaml` to use
`manifest.json` instead.

Closes: #394
2024-05-17 02:21:57 +02:00
59c693d6a0
[#394] All manifest.yaml converted to manifest.json.
YAML isn't part of the Python standard library, while JSON is.

If we want `setup.py` to dynamically parse the available integration
manifest files in order to populate the extra dependencies, then it's
better to rely on a JSON format for manifest files - the parser is part
of the standard library and it doesn't require the user to install
`pyyaml` before `platypush`.
2024-05-17 02:21:57 +02:00
79a71d00b4
[Automatic] Updated components cache 2024-05-16 00:44:03 +00:00
c77cf2c98b
[#372] Removed Google Fit plugin.
The Fit API has (unfortunately) been deprecated by Google with no
alternatives - the new Health Connect API is only available on Android
devices.

Other Google APIs don't seem to be affected by the refresh token issue
either, so this should hopefully close that issue too.

Closes: #372
2024-05-16 02:42:54 +02:00
f514f7ce1e
[Automatic] Updated components cache 2024-05-15 07:57:10 +00:00
98a98ea1dc
[#398] Removed custom Response objects from Tensorflow and response docs generation logic.
Closes: #398
2024-05-15 09:55:58 +02:00
77c91aa5e3
[Automatic] Updated components cache 2024-05-15 06:35:17 +00:00
0e11bbeb05
Fixed schema reference. 2024-05-15 08:33:49 +02:00
20f3eaf375
[#398] Refactored esp plugin.
- Converted `Response` objects into `Schema`s.

- Removed the last references to the deprecated `Mapping` object.

- Fixed all errors and warnings in the plugin.
2024-05-15 01:29:45 +02:00
55e230c361
Removed wiimote events associated to removed wiimote plugin. 2024-05-15 01:29:45 +02:00
snyk-bot
092f5b607c
fix: upgrade vue from 3.4.23 to 3.4.24
Snyk has created this PR to upgrade vue from 3.4.23 to 3.4.24.

See this package in npm:
https://www.npmjs.com/package/vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-14 17:33:05 +00:00
ad4d929c28
[Automatic] Updated components cache 2024-05-13 00:23:15 +00:00
7ae99b4325
[#398] cups plugin refactor.
1. Renamed plugin: `printer.cups` ➡️  `cups`.
2. Replaced `Response` objects with schemas.
2024-05-13 02:22:04 +02:00
2efffb8ebe
[Automatic] Updated components cache 2024-05-12 23:26:45 +00:00
15b1c1f3c0
[#398] Replaced GoogleDriveFile response with GoogleDriveFileSchema. 2024-05-13 01:25:33 +02:00
6003b205c8
[#398] Removed TranslateResponse. 2024-05-13 01:25:33 +02:00
825f20ab77
[Automatic] Updated UI files 2024-05-09 23:48:43 +00:00
7d87c5e92a
Merge pull request #424 from blacklight/snyk-upgrade-5e98f9077cfce52b1250c6e6a38f95a0
[Snyk] Upgrade core-js from 3.36.1 to 3.37.0
2024-05-10 01:46:32 +02:00
3b3f157086
Merge branch 'master' into snyk-upgrade-5e98f9077cfce52b1250c6e6a38f95a0 2024-05-10 01:46:24 +02:00
ad20345505
Merge pull request #423 from blacklight/snyk-upgrade-d36bc5586dd237b84d10196576744320
[Snyk] Upgrade cronstrue from 2.48.0 to 2.49.0
2024-05-10 01:45:09 +02:00
fc4d006064
Merge pull request #422 from blacklight/snyk-upgrade-782bd84f2e24c5ffc88e43f1d25d5de4
[Snyk] Upgrade axios from 1.6.7 to 1.6.8
2024-05-10 01:44:51 +02:00
e10fb4e549
Merge pull request #421 from blacklight/snyk-upgrade-7087de73af11ecfaae1f5f0a8dc827f2
[Snyk] Upgrade vue-router from 4.3.0 to 4.3.2
2024-05-10 01:44:34 +02:00
404737dc24
Merge branch 'master' into snyk-upgrade-7087de73af11ecfaae1f5f0a8dc827f2 2024-05-10 01:44:19 +02:00
2896bd64b6
Merge pull request #420 from blacklight/snyk-upgrade-9cd3716e8d2fdaf3c323b2566785c607
[Snyk] Upgrade vue-skycons from 4.2.0 to 4.3.4
2024-05-10 01:43:16 +02:00
7e8f8a35fc
[Automatic] Updated components cache 2024-05-09 23:38:23 +00:00
b662e98447
[#398] Removed ssh response objects. 2024-05-10 01:37:17 +02:00
f978d708cb
[Automatic] Updated components cache 2024-05-09 23:02:32 +00:00
6f8c2085f2
[#398] Replaced qrcode response objects with schemas. 2024-05-10 01:01:22 +02:00
snyk-bot
6f0451b733
fix: upgrade core-js from 3.36.1 to 3.37.0
Snyk has created this PR to upgrade core-js from 3.36.1 to 3.37.0.

See this package in npm:
https://www.npmjs.com/package/core-js

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-09 17:00:23 +00:00
snyk-bot
c80af03e8d
fix: upgrade cronstrue from 2.48.0 to 2.49.0
Snyk has created this PR to upgrade cronstrue from 2.48.0 to 2.49.0.

See this package in npm:
https://www.npmjs.com/package/cronstrue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-09 17:00:18 +00:00
snyk-bot
186e85cc86
fix: upgrade axios from 1.6.7 to 1.6.8
Snyk has created this PR to upgrade axios from 1.6.7 to 1.6.8.

See this package in npm:
https://www.npmjs.com/package/axios

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-09 17:00:14 +00:00
snyk-bot
83ca9fd8e5
fix: upgrade vue-router from 4.3.0 to 4.3.2
Snyk has created this PR to upgrade vue-router from 4.3.0 to 4.3.2.

See this package in npm:
https://www.npmjs.com/package/vue-router

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-09 17:00:10 +00:00
snyk-bot
8437f05d6a
fix: upgrade vue-skycons from 4.2.0 to 4.3.4
Snyk has created this PR to upgrade vue-skycons from 4.2.0 to 4.3.4.

See this package in npm:
https://www.npmjs.com/package/vue-skycons

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-09 17:00:06 +00:00
8d04eadd77
[Automatic] Updated UI files 2024-05-09 12:17:46 +00:00
e74137d4d1
Merge pull request #419 from blacklight/snyk-upgrade-83595b90e84b75cb5b628ea340bc6c3c
[Snyk] Upgrade core-js from 3.36.0 to 3.36.1
2024-05-09 14:15:26 +02:00
6b12d5ca0e
Merge pull request #418 from blacklight/snyk-upgrade-f5b9569657478ec13c79bc3064807f15
[Snyk] Upgrade @fortawesome/fontawesome-free from 6.5.1 to 6.5.2
2024-05-09 14:15:10 +02:00
e683912228
Merge pull request #417 from blacklight/snyk-upgrade-503e414934e3e9df4999abbd15eed244
[Snyk] Upgrade vue-router from 4.2.5 to 4.3.0
2024-05-09 14:14:52 +02:00
912dddd3da
Merge branch 'master' into snyk-upgrade-503e414934e3e9df4999abbd15eed244 2024-05-09 14:14:07 +02:00
afbb61565b
Merge pull request #416 from blacklight/snyk-upgrade-eb696c7a079843109d9453f80144a956
[Snyk] Upgrade sass from 1.71.0 to 1.75.0
2024-05-09 14:12:35 +02:00
df8299ab61
Merge pull request #415 from blacklight/snyk-upgrade-9b35134e9eafc25285fde220c6d0dc11
[Snyk] Upgrade vue from 3.4.19 to 3.4.23
2024-05-09 14:12:17 +02:00
741f1aef84
[Automatic] Updated components cache 2024-05-09 12:08:36 +00:00
3df76a4a9c
[#398] Replaced pihole response objects with schemas. 2024-05-09 14:06:54 +02:00
929ac09cae
[#398] Removed unused stt response module. 2024-05-09 11:01:42 +02:00
3277c56b43
[Automatic] Updated UI files 2024-05-08 23:48:25 +00:00
c906aab64d
[camera.android.ipcam UI] Adapted to the new plugin API. 2024-05-09 01:46:36 +02:00
2021e25752
[Automatic] Updated components cache 2024-05-08 23:42:25 +00:00
13bde4adba
[#398] Replaced camera response objects with schemas. 2024-05-09 01:41:15 +02:00
579faf63bc
[Automatic] Updated components cache 2024-05-08 20:00:37 +00:00
7a849379f9
[#399] Added @when decorator as an alias for @hook.
Closes: #399
2024-05-08 21:58:58 +02:00
snyk-bot
4a100b0dc0
fix: upgrade core-js from 3.36.0 to 3.36.1
Snyk has created this PR to upgrade core-js from 3.36.0 to 3.36.1.

See this package in npm:
https://www.npmjs.com/package/core-js

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 18:19:30 +00:00
snyk-bot
5a1bccac4d
fix: upgrade @fortawesome/fontawesome-free from 6.5.1 to 6.5.2
Snyk has created this PR to upgrade @fortawesome/fontawesome-free from 6.5.1 to 6.5.2.

See this package in npm:
https://www.npmjs.com/package/@fortawesome/fontawesome-free

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 18:19:26 +00:00
snyk-bot
499b3cd9c9
fix: upgrade vue-router from 4.2.5 to 4.3.0
Snyk has created this PR to upgrade vue-router from 4.2.5 to 4.3.0.

See this package in npm:
https://www.npmjs.com/package/vue-router

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 18:19:22 +00:00
snyk-bot
8be6d80b45
fix: upgrade sass from 1.71.0 to 1.75.0
Snyk has created this PR to upgrade sass from 1.71.0 to 1.75.0.

See this package in npm:
https://www.npmjs.com/package/sass

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 18:19:19 +00:00
snyk-bot
fc621a27f3
fix: upgrade vue from 3.4.19 to 3.4.23
Snyk has created this PR to upgrade vue from 3.4.19 to 3.4.23.

See this package in npm:
https://www.npmjs.com/package/vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-05-07 18:19:15 +00:00
85e44542e2
[Automatic] Updated UI files 2024-05-07 10:49:01 +00:00
c094fac7dc
Merge pull request #407 from blacklight/snyk-upgrade-1d870abd0ad2c511fe65000600f02a14
[Snyk] Upgrade cronstrue from 2.47.0 to 2.48.0
2024-05-07 12:42:38 +02:00
9dc0fbe6e7
Merge branch 'master' into snyk-upgrade-1d870abd0ad2c511fe65000600f02a14 2024-05-07 12:42:30 +02:00
d6b1337c5a
Merge pull request #413 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/express-4.19.2
Bump express from 4.18.2 to 4.19.2 in /platypush/backend/http/webapp
2024-05-07 12:40:57 +02:00
0adde5a107
Merge pull request #414 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/ejs-3.1.10
Bump ejs from 3.1.9 to 3.1.10 in /platypush/backend/http/webapp
2024-05-07 12:40:41 +02:00
89ced918f1
Merge pull request #412 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/webpack-dev-middleware-5.3.4
Bump webpack-dev-middleware from 5.3.3 to 5.3.4 in /platypush/backend/http/webapp
2024-05-07 12:40:28 +02:00
bf52f44f92
Merge pull request #411 from blacklight/dependabot/npm_and_yarn/platypush/backend/http/webapp/follow-redirects-1.15.6
Bump follow-redirects from 1.15.4 to 1.15.6 in /platypush/backend/http/webapp
2024-05-07 12:40:08 +02:00
e285009da3
Merge pull request #409 from blacklight/snyk-upgrade-f2aa58c6cb66ee05c32f925b535a8b1f
[Snyk] Upgrade core-js from 3.35.1 to 3.36.0
2024-05-07 12:39:56 +02:00
3edc8d1077
Merge pull request #408 from blacklight/snyk-upgrade-29d2bb6c813d352072249a6db7e3a805
[Snyk] Upgrade vue from 3.4.18 to 3.4.19
2024-05-07 12:39:36 +02:00
cbaca5f303
Merge pull request #406 from blacklight/snyk-upgrade-bf6b4c595d14f755033e14807ed62fa7
[Snyk] Upgrade @fortawesome/fontawesome-free from 6.4.2 to 6.5.1
2024-05-07 12:39:20 +02:00
df79bb3ea6
Merge pull request #410 from blacklight/snyk-upgrade-73632136d273593f5bc9c7ffc6fd9c71
[Snyk] Upgrade sass from 1.70.0 to 1.71.0
2024-05-07 12:39:04 +02:00
228031c4ad
[#331] Automatically initialize __init__.py in script dirs.
Closes: #331
2024-05-07 02:59:13 +02:00
fdeba9e53c
[Automatic] Updated components cache 2024-05-06 00:28:09 +00:00
3c88593e9a
[#293] Merged midi plugin and backend.
Closes: #293
2024-05-06 02:26:27 +02:00
6a8c83f99b
🐛 Don't add the new password salt/iterations columns if already present.
And, similarly, don't remove them if they aren't on the user table.
2024-05-05 21:58:51 +02:00
901338e228
[#397] Replaced bcrypt dependency with native hashlib logic.
Closes: #397
2024-05-05 21:38:27 +02:00
dependabot[bot]
9651354fbf
Bump ejs from 3.1.9 to 3.1.10 in /platypush/backend/http/webapp
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-05-02 10:39:57 +00:00
a5826892dd
[CI/CD] Added python-setuptools to build dependencies.
It's apparently been extracted out of the core Python library on the
version 3.12 of the interpreter.
2024-05-02 03:31:41 +02:00
373d98fc6a
[Automatic] Updated UI files 2024-05-02 01:11:36 +00:00
61e466d8e2
[UI] Added tts.picovoice component and assistant.picovoice icon. 2024-05-02 03:07:47 +02:00
4ac73837f6
[Automatic] Updated components cache 2024-05-02 00:51:58 +00:00
72bc697122 [assistant.picovoice] Extended documentation. 2024-05-02 00:51:09 +00:00
b2c07a31f2 Fixed tests 2024-05-02 00:51:09 +00:00
d813356d9c [music.mopidy] Handle the case where add returns a list. 2024-05-02 00:51:09 +00:00
1192782729 [tts.picovoice] Convert digits before replacing other substrings. 2024-05-02 00:51:09 +00:00
4734909912 🐛 The EventMatchResult object shouldn't be initialized with args from the event.
If there's a good use-case for overriding `Event._matches_condition`
with a logic that also parses the event arguments, then those arguments
should be accessed directly from the event object, not from the match
result.

Initializing `EventMatchResult` with the arguments from the event means
that, if `EventMatchResult.parsed_args` are populated with custom
extracted arguments, then the upstream event arguments will also be
modified.

If the event is matched against multiple conditions, this will result in
the extracted tokens getting modified by each `matches_condition`
iteration.
2024-05-02 00:51:09 +00:00
237fc58928 [tts.picovoice] A more robust logic for replacing unsupported characteres on the input. 2024-05-02 00:51:09 +00:00
d8e24207c5 Added openai plugin. 2024-05-02 00:51:09 +00:00
bd4b1d3e0f [assistant.picovoice] Sync between the speech and intent engines. 2024-05-02 00:51:09 +00:00
a373091c64 Prevent duplicate hook runs.
Instead of being a list, the hooks in the hook processor should be
backed by by-name and by-value maps.

Don't insert a hook if its exact backing method has already been
inserted. This is actually very common when hooks are defined as Python
snippets imported in other scripts too.
2024-05-02 00:51:09 +00:00
632d98703b New architecture for the assistant speech detection logic.
The assistant object now runs in its own thread and leverages an
external `SpeechProcessor` that uses two threads to scan for both
intents and speech in parallel on audio frames.
2024-05-02 00:51:09 +00:00
6f8816d23d Prevent a potential recursion error in wait_for_either.
We shouldn't overwrite `event._set` and `event._clear` if those values
have already been set.

Those attributes hold the original references to `Event.set` and
`Event.clear` respectively, and the `OrEvent` logic overwrites them with
a callback-based logic.

This shouldn't happen if those attributes are already present.
2024-05-02 00:51:09 +00:00
af1392b5b9 [assistant] Added ResponseEndEvent and IntentMatchedEvent 2024-05-02 00:51:09 +00:00
bb9b6cd319 [assistant.picovoice] Various improvements.
- Added `intent_model_path` parameter.

- Always apply `expanduser` to configuration paths.

- Better logic to infer the fallback model path.

- The Picovoice Leonardo object should always be removed after
  `assistant.picovoice.transcribe` is called.
2024-05-02 00:51:09 +00:00
f0a780b759 Added assistant.picovoice.transcribe and assistant.picovoice.say. 2024-05-02 00:51:09 +00:00
f04f7ce9d7 [tts.picovoice] Better text pre-processing logic. 2024-05-02 00:51:09 +00:00
37d70d67ac [assistant.picovoice] Implemented assistant.picovoice.send_text_query. 2024-05-02 00:51:09 +00:00
fa1d5eb672 [tts.picovoice] Added text pre-processing workaround.
This workaround is required until
https://github.com/Picovoice/orca/issues/10 is fixed.
2024-05-02 00:51:09 +00:00
a345b12244 [assistant.picovoice] Leverage upstream _on_mute_changed.
The plugin should leverage `AssistantPlugin._on_mute_changed` to handle
the boilerplate state managent on mute/unmute actions instead of
re-implementing the same logic.
2024-05-02 00:51:09 +00:00
2c197c275e [assistant.picovoice] Implemented mic mute/unmute handling. 2024-05-02 00:51:09 +00:00
9de49c71a1 [assistant.picovoice] Conversation flow improvements.
- The `Responding` state should be modelled as an extra event/binary
  flag, not as an assistant state. The assistant may be listening for
  hotwords even while the `tts` plugin is responding, and we don't want
  the two states to interfere with each either - neither to build a more
  complex state machine that also needs to take concurrent states into
  account.

- Stop any responses being rendered upon the `tts` plugin when a new
  hotword audio is detected. If e.g. I say "Ok Google", I should always
  be able to trigger the assistant and stop any concurrent audio
  process.

- `SpeechRecognizedEvent` should be emitted even if `cheetah`'s latest
  audio frame results weren't marked as final, and the speech detection
  window timed out. Cheetah's `is_final` detection seems to be quite
  buggy sometimes, and it may not properly detect the end of utterances,
  especially with non-native accents. The workaround is to flush out
  whatever text is available (if at least some speech was detected) into
  a `SpeechRecognizedEvent` upon timeout.
2024-05-02 00:51:09 +00:00
a6f7b6e790 Added more default imports under the platypush module root.
These objects can now also be imported in scripts through
`from platypush import <name>`:

- `Variable`
- `cron`
- `hook`
- `procedure`
2024-05-02 00:51:09 +00:00
aa333db05c [assistant.picovoice] More features.
- Added wiring between `assistant.picovoice` and `tts.picovoice`.

- Added `RESPONDING` status to the assistant.

- Added ability to override the default speech model upon
  `start_conversation`.

- Better handling of conversation timeouts.

- Cache Cheetah objects in a `model -> object` map - at least the
  default model should be pre-loaded, since model loading at runtime
  seems to take a while, and that could impact the ability to detect the
  speech in the first seconds after a hotword is detected.
2024-05-02 00:51:09 +00:00
af875c996e Added tts.picovoice plugin. 2024-05-02 00:51:09 +00:00
a4c911a5d7 Added ffmpeg requirement for assistant.picovoice. 2024-05-02 00:51:09 +00:00
8193c5702c s/partial_transcript/transcript/g 2024-05-02 00:51:09 +00:00
8378bee7c6 Refactored AssistantEvent.
`AssistantEvent.assistant` is now modelled as an opaque object that
behaves the following way:

- The underlying plugin name is saved under `event.args['_assistant']`.

- `event.assistant` is a property that returns the assistant instance
  via `get_plugin`.

- `event.assistant` is reported as a string (plugin qualified name) upon
  event dump.

This allows event hooks to easily use `event.assistant` to interact with
the underlying assistant and easily modify the conversation flow, while
event hook conditions can still be easily modelled as equality
operations between strings.
2024-05-02 00:51:09 +00:00
a670f01647 [assistant.picovoice] Better partial transcript + flush handling logic. 2024-05-02 00:51:09 +00:00
921025be0c picovoice -> assistant.picovoice 2024-05-02 00:51:09 +00:00
bb38f2439c Better integration with the native base API of the assistant plugin. 2024-05-02 00:51:09 +00:00
f7517eb321 [WIP] Added speech detection logic over Cheetah. 2024-05-02 00:51:09 +00:00
a9498ea191 [WIP] Added initial hotword integration. 2024-05-02 00:51:09 +00:00
44f9c03bf3 [#304] Removed old Picovoice integrations 2024-05-02 00:51:09 +00:00
98c99c7888
[Automatic] Updated components cache 2024-04-17 02:09:18 +00:00
e123463804
[media.chromecast] Refactored implementation.
Explicitly use a `CastBrowser` object initialized at plugin boot instead
of relying on blocking calls to `pychromecast.get_chromecasts`.

1. It enables better event handling via callbacks instead of
   synchronously waiting for scan batches.

2. It optimizes resources - only one Zeroconf and one CastBrowser object
   will be created in the plugin, and destroyed upon stop.

3. No need for separate `get_chromecast`/`_refresh_chromecasts` methods:
   all the scanning is run continuously, so we can just return the
   results from the maps.
2024-04-17 03:56:45 +02:00
f99f6bdab9
[media.chromecast] Resource clean up + new API adaptations.
- `pychromecast.get_chromecasts` returns both a list of devices and a
  browser object. Since the Chromecast plugin is the most likely culprit
  of the excessive number of open MDNS sockets, it seems that we may
  need to explicitly stop discovery on the browser and close the
  ZeroConf object after the discovery is done.

- I was still using an ancient version of pychromecast on my RPi4, and I
  didn't notice that more recent versions implemented several breaking
  changes. Adapted the code to cope with those changes.
2024-04-17 02:49:31 +02:00
4972c8bdcf
Unregister a Zeroconf instance if it already exists before publishing a backend service.
`mdns` connection are another culprit for the increasing number of open
files in the process.
2024-04-16 00:12:55 +02:00
33d4c8342d
[#389] Possible fix for "Too many open files" media issue.
It seems that the process keeps a lot of open connections to Chromecast
devices during playback.

The most likely culprit is the `_refresh_chromecasts` logic.

We should start a `cast` object and register a status listener only if a
Chromecast with the same identifier isn't already registered in the
plugin.
2024-04-15 23:01:10 +02:00
027bcea612
[Automatic] Updated components cache 2024-04-08 21:05:46 +00:00
dependabot[bot]
749eda16b4
Bump express from 4.18.2 to 4.19.2 in /platypush/backend/http/webapp
Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2.
- [Release notes](https://github.com/expressjs/express/releases)
- [Changelog](https://github.com/expressjs/express/blob/master/History.md)
- [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2)

---
updated-dependencies:
- dependency-name: express
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-29 06:46:45 +00:00
dependabot[bot]
2d72ce9645
Bump webpack-dev-middleware in /platypush/backend/http/webapp
Bumps [webpack-dev-middleware](https://github.com/webpack/webpack-dev-middleware) from 5.3.3 to 5.3.4.
- [Release notes](https://github.com/webpack/webpack-dev-middleware/releases)
- [Changelog](https://github.com/webpack/webpack-dev-middleware/blob/v5.3.4/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-middleware/compare/v5.3.3...v5.3.4)

---
updated-dependencies:
- dependency-name: webpack-dev-middleware
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-23 19:14:44 +00:00
dependabot[bot]
8ed358fcc8
Bump follow-redirects in /platypush/backend/http/webapp
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 23:30:25 +00:00
snyk-bot
a30042e1eb
fix: upgrade sass from 1.70.0 to 1.71.0
Snyk has created this PR to upgrade sass from 1.70.0 to 1.71.0.

See this package in npm:
https://www.npmjs.com/package/sass

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-08 18:50:38 +00:00
snyk-bot
b9d637187c
fix: upgrade core-js from 3.35.1 to 3.36.0
Snyk has created this PR to upgrade core-js from 3.35.1 to 3.36.0.

See this package in npm:
https://www.npmjs.com/package/core-js

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-06 15:26:22 +00:00
snyk-bot
6a55527694
fix: upgrade vue from 3.4.18 to 3.4.19
Snyk has created this PR to upgrade vue from 3.4.18 to 3.4.19.

See this package in npm:
https://www.npmjs.com/package/vue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-06 02:09:43 +00:00
snyk-bot
5488c97f4b
fix: upgrade cronstrue from 2.47.0 to 2.48.0
Snyk has created this PR to upgrade cronstrue from 2.47.0 to 2.48.0.

See this package in npm:
https://www.npmjs.com/package/cronstrue

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-06 02:09:40 +00:00
snyk-bot
e12054c484
fix: upgrade @fortawesome/fontawesome-free from 6.4.2 to 6.5.1
Snyk has created this PR to upgrade @fortawesome/fontawesome-free from 6.4.2 to 6.5.1.

See this package in npm:
https://www.npmjs.com/package/@fortawesome/fontawesome-free

See this project in Snyk:
https://app.snyk.io/org/blacklight/project/96bfd125-5816-4d9e-83c6-94d1569ab0f1?utm_source=github&utm_medium=referral&page=upgrade-pr
2024-03-06 02:09:36 +00:00
1361 changed files with 44059 additions and 14045 deletions

File diff suppressed because it is too large Load diff

46
.drone/build-ui.sh Executable file
View file

@ -0,0 +1,46 @@
#!/bin/sh
export SRCDIR="$PWD"
export WEBAPP_DIR="$SRCDIR/platypush/backend/http/webapp"
export SKIPCI="$PWD/.skipci"
rm -rf "$SKIPCI"
. .drone/macros/configure-git.sh
cd "$WEBAPP_DIR"
if [ $(git log --pretty=oneline $DRONE_COMMIT_AFTER...$DRONE_COMMIT_BEFORE . | wc -l) -eq 0 ]; then
echo "No UI changes detected, skipping build"
exit 0
fi
if [ "$(git log --pretty=format:%s HEAD...HEAD~1 | head -1)" == "[Automatic] Updated UI files" ]; then
echo "UI changes have already been committed, skipping build"
exit 0
fi
rm -rf dist node_modules
npm install
npm run build
if [ $(git status --porcelain dist | wc -l) -eq 0 ]; then
echo "No build files have been changed"
exit 0
fi
# Create a .skipci file to mark the fact that the next steps should be skipped
# (we're going to do another push anyway, so another pipeline will be triggered)
touch "$SKIPCI"
cd "$SRCDIR"
. .drone/macros/configure-ssh.sh
. .drone/macros/configure-gpg.sh
git add "${WEBAPP_DIR}/dist"
git commit "${WEBAPP_DIR}/dist" -S -m "[Automatic] Updated UI files" --no-verify
git remote rm origin
git remote add origin git@git.platypush.tech:platypush/platypush.git
git push -f origin master
# Restore the original git configuration
mv "$TMP_GIT_CONF" "$GIT_CONF"

23
.drone/github-mirror.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/sh
. .drone/macros/configure-git.sh
. .drone/macros/configure-ssh.sh
ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null
# Clone the repository
branch=$(git rev-parse --abbrev-ref HEAD)
if [ -z "${branch}" ]; then
echo "No branch checked out"
exit 1
fi
git remote add github git@github.com:/blacklight/platypush.git
if [[ "$branch" == "master" ]]; then
git pull --rebase github "${branch}" || echo "No such branch on Github"
fi
# Push the changes to the GitHub mirror
git push -f --all -v github
git push --tags -v github

29
.drone/macros/configure-git.sh Executable file
View file

@ -0,0 +1,29 @@
#!/bin/sh
# Install git
if [ -z "$(which git)" ]; then
if [ -n "$(which apt-get)" ]; then
apt-get update
apt-get install -y git
elif [ -n "$(which apk)" ]; then
apk add --update --no-cache git
elif [ -n "$(which yum)" ]; then
yum install -y git
elif [ -n "$(which dnf)" ]; then
dnf install -y git
elif [ -n "$(which pacman)" ]; then
pacman -Sy --noconfirm git
else
echo "Could not find a package manager to install git"
exit 1
fi
fi
# Backup the original git configuration before changing attributes
export GIT_CONF="$PWD/.git/config"
export TMP_GIT_CONF=/tmp/git.config.orig
cp "$GIT_CONF" "$TMP_GIT_CONF"
git config --global --add safe.directory "$PWD"
git config user.name "Platypush CI/CD Automation"
git config user.email "admin@platypush.tech"

30
.drone/macros/configure-gpg.sh Executable file
View file

@ -0,0 +1,30 @@
#!/bin/sh
[ -z "$PGP_KEY" ] && echo "PGP_KEY is not set" && exit 1
[ -z "$PGP_KEY_ID" ] && echo "PGP_KEY_ID is not set" && exit 1
# Install gpg
if [ -z "$(which gpg)" ]; then
if [ -n "$(which apt-get)" ]; then
apt-get update
apt-get install -y gnupg
elif [ -n "$(which apk)" ]; then
apk add --update --no-cache bash gnupg
elif [ -n "$(which yum)" ]; then
yum install -y gnupg
elif [ -n "$(which dnf)" ]; then
dnf install -y gnupg
elif [ -n "$(which pacman)" ]; then
pacman -Sy --noconfirm gnupg
else
echo "Could not find a package manager to install gnupg"
exit 1
fi
fi
cat <<EOF | gpg --import --armor
$PGP_KEY
EOF
git config commit.gpgsign true
git config user.signingkey "$PGP_KEY_ID"

35
.drone/macros/configure-ssh.sh Executable file
View file

@ -0,0 +1,35 @@
#!/bin/sh
if [ -z "$SSH_PUBKEY" ] || [ -z "$SSH_PRIVKEY" ]; then
echo "SSH_PUBKEY and SSH_PRIVKEY environment variables must be set"
exit 1
fi
# Install ssh
if [ -z "$(which ssh)" ]; then
if [ -n "$(which apt-get)" ]; then
apt-get update
apt-get install -y openssh
elif [ -n "$(which apk)" ]; then
apk add --update --no-cache openssh
elif [ -n "$(which yum)" ]; then
yum install -y openssh
elif [ -n "$(which dnf)" ]; then
dnf install -y openssh
elif [ -n "$(which pacman)" ]; then
pacman -Sy --noconfirm openssh
else
echo "Could not find a package manager to install openssh"
exit 1
fi
fi
mkdir -p ~/.ssh
echo $SSH_PUBKEY > ~/.ssh/id_rsa.pub
cat <<EOF > ~/.ssh/id_rsa
$SSH_PRIVKEY
EOF
chmod 0600 ~/.ssh/id_rsa
ssh-keyscan git.platypush.tech >> ~/.ssh/known_hosts 2>/dev/null

27
.drone/rebuild-docs.sh Executable file
View file

@ -0,0 +1,27 @@
#!/bin/sh
echo "Installing required build dependencies"
apk add --update --no-cache git make py3-sphinx py3-myst-parser py3-pip $(cat platypush/install/requirements/alpine.txt)
pip install -U sphinx-rtd-theme sphinx-book-theme --break-system-packages
pip install . --break-system-packages
mkdir -p /docs/current
export APPDIR="$PWD"
rm -rf "$APPDIR/docs/build"
echo "Building the updated documentation"
cd "$APPDIR/docs/source"
git clone 'https://git.platypush.tech/platypush/platypush.wiki.git' wiki
echo "Linking the wiki to the Sphinx index"
cd wiki
cd "$APPDIR/docs"
make html
rm -f config*.yaml
cd "$APPDIR"
echo "Copying the new documentation files to the target folder"
mv -v "$APPDIR/docs/build" /docs/new
cd /docs
mv current old
mv new current
rm -rf old

6
.drone/run-tests.sh Executable file
View file

@ -0,0 +1,6 @@
#!/bin/sh
apk add --update --no-cache $(cat platypush/install/requirements/alpine.txt)
pip install . --break-system-packages
pip install -r requirements-tests.txt --break-system-packages
pytest tests

169
.drone/update-apt-repo.sh Executable file
View file

@ -0,0 +1,169 @@
#!/bin/sh
[ -f .skipci ] && exit 0
echo "-- Installing dependencies"
apt update
apt install -y dpkg-dev gpg
echo "-- Creating a new apt root folder"
export TMP_APT_ROOT="/tmp/apt"
cp -r "$APT_ROOT" "$TMP_APT_ROOT"
echo "-- Cleaning up older apt releases"
find "$TMP_APT_ROOT/pool" -mindepth 2 -maxdepth 2 -type d | while read reldir; do
pkg_to_remove=$(( $(ls "$reldir"/*.deb | wc -l) - 1 ))
[ $pkg_to_remove -le 0 ] && continue
ls "$reldir"/*.deb | sort -V | head -n$pkg_to_remove | xargs rm -f
done
echo "-- Updating Packages files"
echo "stable\noldstable\nubuntu" | while read distro; do
echo "main\ndev" | while read branch; do
branch_dir="$TMP_APT_ROOT/pool/$distro/$branch"
echo "Checking pool folder: $branch_dir"
[ -d "$branch_dir" ] || mkdir -p "$branch_dir"
dist_dir="$TMP_APT_ROOT/dists/$distro/$branch/all"
mkdir -p "$dist_dir"
pkg_file="$dist_dir/Packages"
dpkg-scanpackages --arch all "$branch_dir" > "$pkg_file"
sed -i "$pkg_file" -re "s|^Filename: $TMP_APT_ROOT/|Filename: |"
cat "$pkg_file" | gzip -9 > "$pkg_file.gz"
echo "Generated Packages file: $pkg_file"
cat "$pkg_file"
done
done
echo "-- Updating Release files"
add_hashes() {
dist_dir=$1
hash_cmd=$2
hash_label=$3
echo "$hash_label:"
find "$dist_dir" -name 'Packages*' | while read file; do
basename="$(echo "$file" | sed -r -e "s|^$dist_dir/||")"
hash="$($hash_cmd "$file" | cut -d" " -f1)"
size="$(wc -c < $file)"
echo " $hash $size $basename"
echo " $hash $size $(echo $basename | sed -re 's|/all/|/binary-i386/|')"
echo " $hash $size $(echo $basename | sed -re 's|/all/|/binary-amd64/|')"
echo " $hash $size $(echo $basename | sed -re 's|/all/|/binary-armel/|')"
echo " $hash $size $(echo $basename | sed -re 's|/all/|/binary-armhf/|')"
echo " $hash $size $(echo $basename | sed -re 's|/all/|/binary-arm64/|')"
done
}
echo "stable\noldstable\nubuntu" | while read distro; do
dist_dir="$TMP_APT_ROOT/dists/$distro"
components=$(find "$dist_dir" -name Packages | awk -F '/' '{print $(NF-2)}' | uniq | tr '\n' ' ')
release_file="$dist_dir/Release"
cat <<EOF > "$release_file"
Origin: Platypush repository
Label: Platypush
Suite: $distro
Codename: $distro
Architectures: i386 amd64 armel armhf arm64
Components: $components
Description: The official APT repository for Platypush
Date: $(date -Ru)
EOF
add_hashes "$dist_dir" "md5sum" "MD5Sum" >> "$release_file"
add_hashes "$dist_dir" "sha1sum" "SHA1" >> "$release_file"
add_hashes "$dist_dir" "sha256sum" "SHA256" >> "$release_file"
done
echo "-- Generating list files"
mkdir -p "$TMP_APT_ROOT/lists"
for distro in stable oldstable ubuntu; do
for branch in main dev; do
echo "deb https://apt.platypush.tech/ $distro $branch" > "$TMP_APT_ROOT/lists/platypush-$distro-$branch.list"
done
done
echo "-- Updating index file"
cat <<EOF > "$TMP_APT_ROOT/index.txt"
Welcome to the Platypush APT repository!
Project homepage: https://platypush.tech
Source code: https://git.platypush.tech/platypush/platypush
Documentation / API reference: https://docs.platypush.tech
You can use this APT repository to install Platypush on Debian, Ubuntu or any
Debian-based distro.
Steps:
1. Add this repository's PGP key to your apt keyring
====================================================
# wget -q -O \\\
/etc/apt/trusted.gpg.d/platypush.asc \\\
https://apt.platypush.tech/pubkey.txt
2. Add the repository to your sources
=====================================
# wget -q -O \\\
/etc/apt/sources.list.d/platypush.list \\\
https://apt.platypush.tech/lists/platypush-<deb_version>-<branch>.list
Where:
- deb_version can be:
- *stable* - current Debian stable version
- *oldstable* - previous Debian stable version
- *ubuntu* - latest Ubuntu version
- branch can be either:
- *main* - latest stable release
- *dev* a package always in sync with the git version
For example, to install the latest stable tags on Debian stable:
# wget -q -O \\\
/etc/apt/sources.list.d/platypush.list \\\
https://apt.platypush.tech/lists/platypush-stable-main.list
3. Update your repos
====================
# apt update
4. Install Platypush
====================
# apt install platypush
EOF
echo "-- Importing and refreshing PGP key"
echo "$PGP_PUBKEY" > "$TMP_APT_ROOT/pubkey.txt"
export PGP_KEYID=$(echo "$PGP_PUBKEY" | gpg --with-colons --import-options show-only --import --fingerprint | grep -e '^fpr:' | head -1 | awk -F ':' '{print $(NF - 1)}')
cat <<EOF | gpg --import --armor
$PGP_PRIVKEY
EOF
echo "-- Signing Release files"
find "$TMP_APT_ROOT/dists" -type f -name Release | while read file; do
dirname="$(dirname "$file")"
cat "$file" | gpg -q --default-key "$PGP_KEYID" -abs > "$file.gpg"
cat "$file" | gpg -q --default-key "$PGP_KEYID" -abs --clearsign > "$dirname/InRelease"
done
echo "-- Updating the apt repo root"
export OLD_APT_ROOT="$REPOS_ROOT/oldapt"
rm -rf "$OLD_APT_ROOT"
mv "$APT_ROOT" "$OLD_APT_ROOT"
mv "$TMP_APT_ROOT" "$APT_ROOT"
chmod -R a+r "$APT_ROOT"
chmod a+x "$APT_ROOT"

View file

@ -0,0 +1,38 @@
#!/bin/sh
[ -f .skipci ] && exit 0
apk add --update --no-cache curl pacman sudo
. .drone/macros/configure-ssh.sh
. .drone/macros/configure-git.sh
git pull --rebase origin master --tags
export VERSION=$(python setup.py --version)
export HEAD=$(git log --pretty=format:%h HEAD...HEAD~1 | head -1)
export GIT_VERSION="$VERSION.r$(git log --pretty=oneline HEAD...v$VERSION | wc -l).g${HEAD}"
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
adduser -u 1000 -D build
mkdir -p "$WORKDIR"
echo "--- Updating Arch git version"
export PKGDIR=$WORKDIR/git
git clone ssh://aur@aur.archlinux.org/platypush-git.git "$PKGDIR"
git config --global --add safe.directory "$PKGDIR"
chown -R build "$PKGDIR"
cd "$PKGDIR"
sed -i 'PKGBUILD' -r \
-e "s/^pkgver=.*/pkgver=$GIT_VERSION/" \
-e "s/^pkgrel=.*/pkgrel=1/" \
sudo -u build makepkg --printsrcinfo > .SRCINFO
export FILES_CHANGED=$(git status --porcelain --untracked-files=no | wc -l)
if [ $FILES_CHANGED -gt 0 ]; then
echo "--- Pushing git package version $GIT_VERSION"
git commit -a -m '[Automatic] Package update'
git push origin master
fi

View file

@ -0,0 +1,46 @@
#!/bin/sh
[ -f .skipci ] && exit 0
apk add --update --no-cache curl pacman sudo
. .drone/macros/configure-ssh.sh
. .drone/macros/configure-git.sh
git pull --rebase origin master --tags
export VERSION=$(python setup.py --version)
export TAG_URL="https://git.platypush.tech/platypush/platypush/archive/v$VERSION.tar.gz"
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts 2>/dev/null
adduser -u 1000 -D build
mkdir -p "$WORKDIR"
echo "--- Updating Arch stable version"
export PKGDIR="$WORKDIR/stable"
git clone ssh://aur@aur.archlinux.org/platypush.git "$PKGDIR"
git config --global --add safe.directory "$PKGDIR"
chown -R build "$PKGDIR"
cd "$PKGDIR"
export RELEASED_VERSION=$(grep -e '^pkgver=' PKGBUILD | sed -r -e 's/^pkgver=(.*)\s*/\1/')
if [ "$RELEASED_VERSION" == "$VERSION" ]; then
echo "--- No changes in the stable package version"
exit 0
fi
export TAG_CHECKSUM=$(curl --silent "$TAG_URL" | sha512sum | awk '{print $1}')
sed -i 'PKGBUILD' -r \
-e "s/^pkgver=.*/pkgver=$VERSION/" \
-e "s/^pkgrel=.*/pkgrel=1/" \
-e "s/^sha512sums=.*/sha512sums=('$TAG_CHECKSUM')/"
sudo -u build makepkg --printsrcinfo > .SRCINFO
export FILES_CHANGED=$(git status --porcelain --untracked-files=no | wc -l)
if [ $FILES_CHANGED -gt 0 ]; then
echo "--- Pushing stable package version $VERSION"
git commit -a -m '[Automatic] Package update'
git push origin master
fi

View file

@ -0,0 +1,46 @@
#!/bin/sh
export SKIPCI="$PWD/.skipci"
export CACHEFILE="$PWD/platypush/components.json.gz"
[ -f "$SKIPCI" ] && exit 0
# Backup the original git configuration before changing attributes
export GIT_CONF="$PWD/.git/config"
export TMP_GIT_CONF="/tmp/git.config.orig"
cp "$GIT_CONF" "$TMP_GIT_CONF"
. .drone/macros/configure-git.sh
# Only regenerate the components cache if either the plugins, backends,
# events or schemas folders have some changes (excluding the webapp files).
if [ -z "$(git log --pretty=oneline $DRONE_COMMIT_AFTER...$DRONE_COMMIT_BEFORE -- platypush/backend platypush/plugins platypush/schemas platypush/message/event ':(exclude)platypush/backend/http/webapp')" ]; then
echo 'No changes to the components file'
exit 0
fi
. .drone/macros/configure-ssh.sh
. .drone/macros/configure-gpg.sh
echo 'Updating components cache'
apk add --update --no-cache $(cat platypush/install/requirements/alpine.txt)
pip install . --break-system-packages
python - <<EOF
from platypush import get_plugin
get_plugin('inspect').refresh_cache(force=True)
EOF
# Create a .skipci file to mark the fact that the next steps should be skipped
# (we're going to do another push anyway, so another pipeline will be triggered)
touch "$SKIPCI"
git add "$CACHEFILE"
git commit "$CACHEFILE" -S -m "[Automatic] Updated components cache" --no-verify
git remote rm origin
git remote add origin git@git.platypush.tech:platypush/platypush.git
git push -f origin master
# Restore the original git configuration
mv "$TMP_GIT_CONF" "$GIT_CONF"

103
.drone/update-deb-packages.sh Executable file
View file

@ -0,0 +1,103 @@
#!/bin/sh
[ -f .skipci ] && exit 0
echo "-- Copying source directory"
mkdir -p "$WORKDIR/src"
export SRCDIR="$WORKDIR/src/$DEB_VERSION"
cp -r "$PWD" "$SRCDIR"
cd "$SRCDIR"
echo "-- Installing dependencies"
export DEBIAN_FRONTEND=noninteractive
apt update
apt install -y curl dpkg-dev gpg git python3 python3-pip python3-setuptools
echo "--- Parsing metadata"
git config --global --add safe.directory "$PWD"
git pull --rebase origin master --tags
export VERSION=$(grep -e '^__version__' "${SRCDIR}/version.py" | sed -r -e 's/^__version__\s*=\s*"([^"]+)"$/\1/')
export GIT_VERSION="$VERSION-$(git log --pretty=oneline HEAD...v$VERSION | wc -l)"
export GIT_BUILD_DIR="$WORKDIR/${PKG_NAME}_${GIT_VERSION}_all"
export GIT_DEB="$WORKDIR/${PKG_NAME}_${GIT_VERSION}_all.deb"
export POOL_PATH="$APT_ROOT/pool/$DEB_VERSION/dev"
echo "--- Building git package"
pip install --prefix="$GIT_BUILD_DIR/usr" --no-cache --no-deps .
find "$GIT_BUILD_DIR" -name "site-packages" | while read dir; do
base="$(dirname "$dir")"
mv "$dir" "$base/dist-packages"
done
install -m755 -d "${GIT_BUILD_DIR}/usr/lib/systemd/system"
install -m755 -d "${GIT_BUILD_DIR}/usr/lib/systemd/user"
install -m750 -d "${GIT_BUILD_DIR}/var/lib/platypush"
install -m750 -d "${GIT_BUILD_DIR}/etc/platypush/scripts"
install -m644 "${SRCDIR}/platypush/config/config.yaml" "${GIT_BUILD_DIR}/etc/platypush/config.yaml"
install -m644 "${SRCDIR}/platypush/config/systemd/platypush.service" "${GIT_BUILD_DIR}/usr/lib/systemd/user/platypush.service"
install -m644 "${SRCDIR}/platypush/config/systemd/platypush.service" "${GIT_BUILD_DIR}/usr/lib/systemd/system/platypush.service"
sed -i "${GIT_BUILD_DIR}/usr/lib/systemd/system/platypush.service" -r \
-e 's/^#\s*Requires=(.*)/Requires=\1/' \
-e 's/^\[Service\]$/\[Service\]\
User=platypush\
Group=platypush\
WorkingDirectory=\/var\/lib\/platypush\
Environment="PLATYPUSH_CONFIG=\/etc\/platypush\/config.yaml"\
Environment="PLATYPUSH_WORKDIR=\/var\/lib\/platypush"/'
mkdir -p "$GIT_BUILD_DIR/DEBIAN"
cat <<EOF > "$GIT_BUILD_DIR/DEBIAN/control"
Package: $PKG_NAME
Version: $GIT_VERSION
Maintainer: Fabio Manganiello <fabio@platypush.tech>
Depends: $(cat platypush/install/requirements/debian.txt | tr '\n' ',' | sed -re 's/,$//' -e 's/,/, /g')
Architecture: all
Homepage: https://platypush.tech
Description: Universal command executor and automation hub.
EOF
cat <<EOF > "$GIT_BUILD_DIR/DEBIAN/postinst" && chmod +x "$GIT_BUILD_DIR/DEBIAN/postinst"
#!/bin/sh
set -e
if [ "\$1" = "configure" ]; then
grep -e '^platypush:' /etc/passwd 2>/dev/null || useradd -U -r -s /bin/false -d /var/lib/platypush platypush
mkdir -p /var/lib/platypush
chown -R platypush:platypush /var/lib/platypush
chown -R platypush:platypush /etc/platypush
if which systemctl; then systemctl daemon-reload; fi
fi
EOF
mkdir -p "$POOL_PATH"
rm -f "$POOL_PATH/"*.deb
dpkg --build "$GIT_BUILD_DIR"
echo "--- Copying $GIT_DEB to $POOL_PATH"
cp "$GIT_DEB" "$POOL_PATH"
# If main/all/Packages doesn't exist, then we should create the first main release
[ $(ls "$APT_ROOT/pool/$DEB_VERSION/main/${PKG_NAME}_${VERSION}-"*"_all.deb" 2>/dev/null | wc -l) -eq 0 ] && export UPDATE_STABLE_PKG=1
export PKGURL="https://apt.platypush.tech/dists/$DEB_VERSION/main/all/Packages"
[ -z "$UPDATE_STABLE_PKG" ] &&
curl -ILs -o /dev/null -w "%{http_code}" "$PKGURL" |
grep -e '^4' >/dev/null && export UPDATE_STABLE_PKG=1
# If the published release version differs from the current one, then we should publish a new main release
if [ -z "$UPDATE_STABLE_PKG" ]; then
RELEASED_VERSION=$(curl -s "$PKGURL" | grep -e '^Version: ' | head -1 | awk '{print $2}' | cut -d- -f 1)
[ "$RELEASED_VERSION" != "$VERSION" ] && export UPDATE_STABLE_PKG=1
fi
# Proceed and update the main release if the version number has changed
if [ -n "$UPDATE_STABLE_PKG" ]; then
echo "--- Updating main package"
mkdir -p "$APT_ROOT/pool/$DEB_VERSION/main"
cp "$GIT_DEB" "$APT_ROOT/pool/$DEB_VERSION/main/${PKG_NAME}_${VERSION}-1_all.deb"
fi

26
.drone/update-image-registry.sh Executable file
View file

@ -0,0 +1,26 @@
#!/bin/sh
[ -z "$DOCKER_USER" ] && echo "Please set the DOCKER_USER environment variable" && exit 1
[ -z "$DOCKER_PASS" ] && echo "Please set the DOCKER_PASS environment variable" && exit 1
export VERSION=$(grep current_version pyproject.toml | sed -r -e "s/.*=\s*['\"]?([^'\"]+)['\"]?\s*$/\1/")
export REGISTRY_ENDPOINT="${REGISTRY_ENDPOINT:-quay.io}"
export IMAGE_NAME="$REGISTRY_ENDPOINT/$DOCKER_USER/platypush"
# Log in to the registry
docker login "$REGISTRY_ENDPOINT" -u "$DOCKER_USER" -p "$DOCKER_PASS"
# Required for multi-platform builds
docker buildx create --name=multiarch --driver=docker-container
# Build and publish the images
docker buildx build \
-f Dockerfile.alpine \
-t "$IMAGE_NAME:$VERSION" \
-t "$IMAGE_NAME:latest" \
--platform linux/amd64,linux/arm64,linux/arm/v7 \
--builder multiarch \
--push .
# Clean up
docker buildx rm multiarch

5
.drone/update-pip-package.sh Executable file
View file

@ -0,0 +1,5 @@
#!/bin/sh
apk add --update --no-cache py3-twine py3-setuptools py3-wheel py3-pip
python setup.py sdist bdist_wheel
twine upload dist/platypush-$(python setup.py --version).tar.gz

261
.drone/update-rpm-repo.sh Executable file
View file

@ -0,0 +1,261 @@
#!/bin/sh
[ -f .skipci ] && exit 0
echo "-- Installing dependencies"
yum install -y \
createrepo \
git \
gpg \
python \
python-pip \
python-setuptools \
rpm-build \
rpm-sign \
systemd-rpm-macros \
wget \
yum-utils \
echo "-- Copying source directory"
mkdir -p "$WORKDIR"
export SRCDIR="$WORKDIR/src"
cp -r "$PWD" "$SRCDIR"
cd "$SRCDIR"
mkdir -p "$RPM_ROOT"
echo "--- Parsing metadata"
git config --global --add safe.directory $PWD
git pull --rebase origin master --tags
export VERSION=$(grep -e '^__version__' "${SRCDIR}/version.py" | sed -r -e 's/^__version__\s*=\s*"([^"]+)"$/\1/')
export RELNUM="$(git log --pretty=oneline HEAD...v$VERSION | wc -l)"
export SPECFILE="$WORKDIR/$PKG_NAME.spec"
export BUILD_DIR="$WORKDIR/build"
export TMP_RPM_ROOT="$WORKDIR/repo"
export SRC_URL="https://git.platypush.tech/platypush/platypush/archive/master.tar.gz"
echo "--- Creating git package spec"
cat <<EOF > $SPECFILE
Summary: Universal command executor and automation hub.
Name: $PKG_NAME-git
Version: $VERSION
Release: $RELNUM
URL: https://platypush.tech
Group: System
License: MIT
Packager: Fabio Manganiello <fabio@platypush.tech>
Source: $SRC_URL
Requires: $(cat platypush/install/requirements/fedora.txt | tr '\n' ' ')
Conflicts: $PKG_NAME
Prefix: %{_prefix}
BuildRoot: %{_tmppath}/%{name}-root
BuildRequires: systemd-rpm-macros
%{?sysusers_requires_compat}
%description
Universal command executor and automation hub.
%install
mkdir -p %{buildroot}/
cp -r "$BUILD_DIR"/* %{buildroot}/
install -p -Dm0644 "${BUILD_DIR}/usr/lib/sysusers.d/platypush.conf" %{buildroot}%{_sysusersdir}/platypush.conf
%pre
%sysusers_create_compat "${BUILD_DIR}/usr/lib/sysusers.d/platypush.conf"
%clean
%files
%defattr(750,platypush,platypush,750)
%dir /etc/platypush
/etc/platypush/*
/usr/bin/*
/usr/lib/python$(python3 --version | awk '{print $2}' | cut -d. -f 1,2)/site-packages/platypush
/usr/lib/python$(python3 --version | awk '{print $2}' | cut -d. -f 1,2)/site-packages/platypush-$VERSION.dist-info
/usr/lib/systemd/system/*
/usr/lib/systemd/user/*
%defattr(750,platypush,platypush,750)
%dir /var/lib/platypush
%{_sysusersdir}/platypush.conf
%changelog
* $(date +'%a %b %d %Y') admin <admin@platypush.tech>
- [Automatic] Release $VERSION-$RELNUM
EOF
echo "--- Building git package"
mkdir -p "$BUILD_DIR"
pip install --prefix="$BUILD_DIR/usr" --no-cache --no-deps .
install -m755 -d "${BUILD_DIR}/usr/lib/systemd/system"
install -m755 -d "${BUILD_DIR}/usr/lib/systemd/user"
install -m755 -d "${BUILD_DIR}/usr/lib/sysusers.d"
install -m750 -d "${BUILD_DIR}/var/lib/platypush"
install -m750 -d "${BUILD_DIR}/etc/platypush/scripts"
install -m644 "${SRCDIR}/platypush/config/config.yaml" "${BUILD_DIR}/etc/platypush/config.yaml"
install -Dm644 "${SRCDIR}/platypush/config/systemd/platypush-sysusers.conf" "${BUILD_DIR}/usr/lib/sysusers.d/platypush.conf"
install -m644 "${SRCDIR}/platypush/config/systemd/platypush.service" "${BUILD_DIR}/usr/lib/systemd/user/platypush.service"
install -m644 "${SRCDIR}/platypush/config/systemd/platypush.service" "${BUILD_DIR}/usr/lib/systemd/system/platypush.service"
sed -i "${BUILD_DIR}/usr/lib/systemd/system/platypush.service" -r \
-e 's/^#\s*Requires=(.*)/Requires=\1/' \
-e 's/^\[Service\]$/\[Service\]\
User=platypush\
Group=platypush\
WorkingDirectory=\/var\/lib\/platypush\
Environment="PLATYPUSH_CONFIG=\/etc\/platypush\/config.yaml"\
Environment="PLATYPUSH_WORKDIR=\/var\/lib\/platypush"/'
rpmbuild --target "noarch" -bb "$SPECFILE"
echo "--- Copying the new RPM package"
mkdir -p "$TMP_RPM_ROOT"
cp "$HOME/rpmbuild/RPMS/noarch/$PKG_NAME-git-$VERSION-$RELNUM.noarch.rpm" "$TMP_RPM_ROOT"
echo "--- Checking the latest released stable version"
export LATEST_STABLE_PKG=$(ls -rt "$RPM_ROOT/$PKG_NAME"*.rpm 2>/dev/null | grep -v "$PKG_NAME-git" | tail -1)
if [ -z "$LATEST_STABLE_PKG" ]; then
# If not stable release is available, then create one
export UPDATE_STABLE_PKG=1
else
# Otherwise, create a new release if the reported version on the repo is different
# from the latest released version.
export LATEST_STABLE_VERSION=$(basename $LATEST_STABLE_PKG | cut -d- -f 2)
if [ "$VERSION" != "$LATEST_STABLE_VERSION" ]; then
export UPDATE_STABLE_PKG=1
else
# If the version has remained the same, then simply copy the existing RPM to the
# new repository directory.
echo "Copying the existing release $LATEST_STABLE_VERSION to the new repository"
cp "$LATEST_STABLE_PKG" "$TMP_RPM_ROOT"
fi
fi
# If a new stable release is required, build another RPM
if [ -n "$UPDATE_STABLE_PKG" ]; then
export RELNUM=1
export SRC_URL="https://git.platypush.tech/platypush/platypush/archive/v$VERSION.tar.gz"
cat <<EOF > $SPECFILE
Summary: Universal command executor and automation hub.
Name: $PKG_NAME
Version: $VERSION
Release: $RELNUM
URL: https://platypush.tech
Group: System
License: MIT
Packager: Fabio Manganiello <fabio@platypush.tech>
Source: $SRC_URL
Requires: $(cat platypush/install/requirements/fedora.txt | tr '\n' ' ')
Conflicts: $PKG_NAME-git
Prefix: %{_prefix}
BuildRoot: %{_tmppath}/%{name}-root
BuildRequires: systemd-rpm-macros
%{?sysusers_requires_compat}
%description
Universal command executor and automation hub.
%install
mkdir -p %{buildroot}/
cp -r "$BUILD_DIR"/* %{buildroot}/
install -p -Dm0644 "${BUILD_DIR}/usr/lib/sysusers.d/platypush.conf" %{buildroot}%{_sysusersdir}/platypush.conf
%pre
%sysusers_create_compat "${BUILD_DIR}/usr/lib/sysusers.d/platypush.conf"
%clean
%files
%defattr(750,platypush,platypush,750)
%dir /etc/platypush
/etc/platypush/*
/usr/bin/*
/usr/lib/python$(python3 --version | awk '{print $2}' | cut -d. -f 1,2)/site-packages/platypush
/usr/lib/python$(python3 --version | awk '{print $2}' | cut -d. -f 1,2)/site-packages/platypush-$VERSION.dist-info
/usr/lib/systemd/system/*
/usr/lib/systemd/user/*
%defattr(750,platypush,platypush,750)
%dir /var/lib/platypush
%{_sysusersdir}/platypush.conf
%changelog
* $(date +'%a %b %d %Y') admin <admin@platypush.tech>
- [Automatic] Release $VERSION-$RELNUM
EOF
echo "--- Building package for stable release $VERSION"
rpmbuild --target "noarch" -bb "$SPECFILE"
cp "$HOME/rpmbuild/RPMS/noarch/$PKG_NAME-$VERSION-$RELNUM.noarch.rpm" "$TMP_RPM_ROOT"
fi
echo "--- Importing the repository keys"
cat <<EOF | gpg --import --armor
$PGP_PRIVKEY
EOF
export PGP_KEYID=$(echo "$PGP_PUBKEY" | gpg --with-colons --import-options show-only --import --fingerprint | grep -e '^fpr:' | head -1 | awk -F ':' '{print $(NF - 1)}')
cat <<EOF > $HOME/.rpmmacros
%signature gpg
%_gpg_name $PGP_KEYID
EOF
echo "--- Signing the new RPM packages"
rpm --addsign "$TMP_RPM_ROOT"/*.rpm
echo "--- Creating a new copy of the RPM repository"
createrepo "$TMP_RPM_ROOT"
gpg --detach-sign --armor "$TMP_RPM_ROOT/repodata/repomd.xml"
cat <<EOF > "$TMP_RPM_ROOT/platypush.repo"
[platypush]
name=Platypush repository
baseurl=https://rpm.platypush.tech
enabled=1
type=rpm
gpgcheck=1
gpgkey=https://rpm.platypush.tech/pubkey.txt
EOF
cat <<EOF > "$TMP_RPM_ROOT/index.txt"
Welcome to the Platypush RPM repository!
Project homepage: https://platypush.tech
Source code: https://git.platypush.tech/platypush/platypush
Documentation / API reference: https://docs.platypush.tech
You can use this RPM repository to install Platypush on Fedora or other
RPM-based distros - as long as they are compatible with the latest Fedora
release.
Steps:
1. Add the repository to your sources
=====================================
# yum config-manager --add-repo https://rpm.platypush.tech/platypush.repo
2. Install Platypush
====================
# yum install platypush
Or, if you want to install a version always up-to-date with the git repo:
# yum install platypush-git
EOF
cat <<EOF > "$TMP_RPM_ROOT/pubkey.txt"
$PGP_PUBKEY
EOF
echo "--- Updating the repository"
export NEW_RPM_ROOT="$REPOS_ROOT/rpm_new"
export OLD_RPM_ROOT="$REPOS_ROOT/rpm_old"
cp -r "$TMP_RPM_ROOT" "$NEW_RPM_ROOT"
rm -rf "$TMP_RPM_ROOT"
mv "$RPM_ROOT" "$OLD_RPM_ROOT"
mv "$NEW_RPM_ROOT" "$RPM_ROOT"
rm -rf "$OLD_RPM_ROOT"

22
.env.example Normal file
View file

@ -0,0 +1,22 @@
# The device ID is the unique identifier for the device that runs Platypush.
# You should make sure that it's unique at least within your local network,
# as it is used to identify the device in the MQTT topics, on the HTTP API
# and on the published ZeroConf services.
PLATYPUSH_DEVICE_ID=platypush
# Use an external Redis server for the message queue. By default, the Platypush
# container will run a Redis server on the same container. Also remove the
# `--start-redis` option from the `docker run` command if you want to use an
# external Redis server.
# PLATYPUSH_REDIS_HOST=localhost
# PLATYPUSH_REDIS_PORT=6379
# Custom location for the Platypush configuration file.
# PLATYPUSH_CONFIG=/etc/platypush/config.yaml
# Custom location for the Platypush working directory.
# PLATYPUSH_WORKDIR=/var/lib/platypush
# SQLAlchemy database URL. By default, the Platypush container will run on a
# SQLite database installed under <WORKDIR>/main.db. If you want
# PLATYPUSH_DB=sqlite:////var/lib/platypush/main.db

1
.gitignore vendored
View file

@ -28,3 +28,4 @@ Session.vim
/docs/source/wiki /docs/source/wiki
/.skipci /.skipci
dump.rdb dump.rdb
.env

1
.ignore Normal file
View file

@ -0,0 +1 @@
dist/

View file

@ -2,7 +2,7 @@
# See https://pre-commit.com/hooks.html for more hooks # See https://pre-commit.com/hooks.html for more hooks
repos: repos:
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.1.0 rev: v4.6.0
hooks: hooks:
# - id: trailing-whitespace # - id: trailing-whitespace
# - id: end-of-file-fixer # - id: end-of-file-fixer
@ -11,15 +11,15 @@ repos:
- id: check-xml - id: check-xml
- id: check-symlinks - id: check-symlinks
- id: check-added-large-files - id: check-added-large-files
args: ['--maxkb=1500'] args: ['--maxkb=3000']
- repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs - repo: https://github.com/Lucas-C/pre-commit-hooks-nodejs
rev: v1.1.2 rev: v1.1.2
hooks: hooks:
- id: markdown-toc - id: markdown-toc
- repo: https://github.com/pycqa/flake8 - repo: https://github.com/PyCQA/flake8
rev: 4.0.1 rev: 7.1.0
hooks: hooks:
- id: flake8 - id: flake8
additional_dependencies: additional_dependencies:

View file

@ -1,8 +1,358 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. ## [1.3.4]
Given the high speed of development in the first phase, changes are being
reported only starting from v0.20.2. - [Bug]: Fixed installation bug in `pip install platypush` introduced by the
`pyproject.toml` migration.
## [1.3.3]
- [`3e02304a`](https://git.platypush.tech/platypush/platypush/commit/3e02304ac203625650ab4b03f9d4146a40839f2f)
[Auth]: Fixed generation of API tokens when 2FA is enabled.
## [1.3.2]
- [[#414](https://git.platypush.tech/platypush/platypush/issues/414)]: added
Jellyfin UI for all media plugins. This makes it possible to browse and play
media items from the Jellyfin library on multiple devices.
- [[#434](https://git.platypush.tech/platypush/platypush/issues/434)]: added
official Docker base image - [see
documentation](https://docs.platypush.tech/wiki/Installation.html#docker).
- [_Other Docker improvements_]
* Reduced size for the base images.
* Better self-documented [`docker-compose.yml`
file](https://git.platypush.tech/platypush/platypush/src/branch/master/docker-compose.yml).
- [[`db`](https://git.platypush.tech/platypush/platypush/commit/dd02be12)]:
Fixed compatibility of transaction scopes with SQLAlchemy 2.0.
- [`media.vlc`]: Improved state management.
- [`media.mpv`]: Enhanced compatibility. mpv 1.0 introduced many breaking
changes in the event management API, but it may not yet be available on all
distros. Platypush has now introduced a compatibility layer to seamlessly
interact with any version of mpv.
- [_Media UI_] Added support for embedded players for Jellyfin, YouTube and
local media.
- [[`http.webpage`](https://git.platypush.tech/platypush/platypush/commit/09413bc0)]:
Added support for custom headers to the scraping functions.
## [1.3.1]
- [[#344](https://git.platypush.tech/platypush/platypush/issues/344)]: removed
`marshmallow_dataclass` dependency. That package isn't included in the
package managers of any supported distros and requires to be installed via
pip. Making the Platypush' system packages depend on a pip-only package is
not a good idea. Plus, the library seems to be still in heavy development and
it has already broken compatibility with at least the `system` package.
## [1.3.0]
- [[#333](https://git.platypush.tech/platypush/platypush/issues/333)]: new file
browser UI/component. It includes custom MIME type support, a file editor
with syntax highlight, file download and file upload.
- [[#341](https://git.platypush.tech/platypush/platypush/issues/341)]:
procedures are now native entities that can be managed from the entities panel.
A new versatile procedure editor has also been added, with support for nested
blocks, conditions, loops, variables, context autocomplete, and more.
- [`procedure`]: Added the following features to YAML/structured procedures:
- `set`: to set variables whose scope is limited to the procedure / code
block where they are created. `variable.set` is useful to permanently
store variables on the db, `variable.mset` is useful to set temporary
global variables in memory through Redis, but sometimes you may just want
to assign a value to a variable that only needs to live within a procedure,
event hook or cron.
```yaml
- set:
foo: bar
temperature: ${output.get('temperature')}
```
- `return` can now return values too when invoked within a procedure:
```yaml
- return: something
# Or
- return: "Result: ${output.get('response')}"
```
- The default logging format is now much more compact. The full body of events
and requests is no longer included by default in `info` mode - instead, a
summary with the message type, ID and response time is logged. The full
payloads can still be logged by enabling `debug` logs through e.g. `-v`.
## [1.2.3]
- [[#422](https://git.platypush.tech/platypush/platypush/issues/422)]: adapted
media plugins to support streaming from the yt-dlp process. This allows
videos to have merged audio+video even if they had separate tracks upstream.
- [`media.*`] Many improvements on the media UI.
- [`zigbee.mqtt`] Removed synchronous logic from `zigbee.mqtt.device_set`. It
was prone to timeouts as well as pointless - the updated device state will
anyway be received as an event.
## [1.2.2]
- Fixed regression on older version of Python that don't fully support
`pyproject.toml` and can't install data files the new way.
## [1.2.1]
- Added static `/login` and `/register` Flask fallback routes to prevent 404 if
the client doesn't have JavaScript enabled.
- Fixed `apt` packages for Debian oldstable after the `setup.py` to
`pyproject.toml` migration.
- Fixed license string in the `pyproject.toml`.
## [1.2.0]
- [#419](https://git.platypush.tech/platypush/platypush/issues/419): added
support for randomly generated API tokens alongside JWT.
- [#339](https://git.platypush.tech/platypush/platypush/issues/339): added
support for 2FA with OTP codes.
- [#393](https://git.platypush.tech/platypush/platypush/issues/393): added
`bind_socket` parameter to `backend.http`, so Platypush can listen on (or
exclusively if `listen_port` is null) on a local UNIX socket as well.
- [#401](https://git.platypush.tech/platypush/platypush/issues/401): added
`--redis-bin` option / `PLATYPUSH_REDIS_BIN` environment variable to support
custom Redis (or drop-in replacements) executables when `--start-redis` is
specified.
- [#413](https://git.platypush.tech/platypush/platypush/issues/401): added
support for page-specific PWAs. If you navigate to `/plugin/<plugin-name>`,
and you install it as a PWA, you'll install a PWA only for that plugin - not
for the whole Platypush UI.
- Migrated project setup from `setup.py` to `pyproject.toml`.
- [`70db33b4e`](https://git.platypush.tech/platypush/platypush/commit/70db33b4e):
more application resilience in case Redis goes down.
- [`ee27b2c4`](https://git.platypush.tech/platypush/platypush/commit/ee27b2c4):
Refactor of all the authentication endpoints into a single `/auth` endpoint:
- `POST /register``POST /auth?type=register`
- `POST /login``POST /auth?type=login`
- `POST /auth``POST /auth?type=token`
- `POST /auth``POST /auth?type=jwt`
- [`2ccf0050`](https://git.platypush.tech/platypush/platypush/commit/2ccf0050):
Added support for binary content to `qrcode.generate`.
- [`b69e9500`](https://git.platypush.tech/platypush/platypush/commit/b69e9500):
Support for fullscreen mode on the `camera` plugins UI.
## [1.1.3] - 2024-07-16
- [`core`]: New architecture for the Redis bus - now leveraging pub/sub with a
connection pool instead of a single-connection queue. It makes the
application much faster and less prone to Redis deadlocks.
- [`youtube`]:
[#391](https://git.platypush.tech/platypush/platypush/issues/391): added
support for:
- Add/remove playlists (UI)
- Add to/remove from playlist (UI)
- Subscribe/unsubscribe from channels (UI)
- Browse channels and playlists directly in the UI
- Download media and audio
## [1.1.1] - 2024-06-24
- [`torrent`]: [[#263](https://git.platypush.tech/platypush/platypush/issues/263)], [[#375](https://git.platypush.tech/platypush/platypush/issues/375)],
[[#407](https://git.platypush.tech/platypush/platypush/issues/407)] and
[[#408](https://git.platypush.tech/platypush/platypush/issues/408)]: added
torrents.csv search provider and rewritten torrents UI.
- [[#281](https://git.platypush.tech/platypush/platypush/issues/281)]
replaced `warnings.warn` with `logging.warning`, as there is no easy and
reliable way of routing `warnings.warn` to `logging`.
## [1.1.0] - 2024-06-06
- [[#405](https://git.platypush.tech/platypush/platypush/issues/405)] Fixed
timezone/timestamp rendering issues for `calendar.ical` events.
- [[#403](https://git.platypush.tech/platypush/platypush/issues/403)]
Included inherited actions in plugins docs.
## [1.0.7] - 2024-06-02
- [[#384](https://git.platypush.tech/platypush/platypush/issues/384)] Added
`assistant.openai` and `tts.openai` plugins.
## [1.0.6] - 2024-06-01
- 🐛 Bug fix on one of the entities modules that prevented the application from
loading when `.` is part of `PYTHONPATH`.
## [1.0.5] - 2024-06-01
- A proper solution for the `utcnow()` issue.
It was a bit trickier than expected to solve, but now Platypush uses a
`utcnow()` facade that always returns a UTC datetime in a timezone-aware
representation.
The code should however also handle the case of timestamps stored on the db in
the old format.
## [1.0.4] - 2024-05-31
- Fixed regression introduced by
[c18768e61fef62924f4c1fac3089ecfb83666dab](https://git.platypush.tech/platypush/platypush/commit/c18768e61fef62924f4c1fac3089ecfb83666dab).
Python seems to have introduced a breaking change from the version 3.12 -
`datetime.utcnow()` is not deprecated, but `datetime.UTC`, the suggested
alternative, isn't available on older versions of Python. Added a workaround
that makes Platypush compatible with both the implementations.
## [1.0.3] - 2024-05-31
- [[#368](https://git.platypush.tech/platypush/platypush/issues/368)] Added
Ubuntu packages.
- Fixed bug that didn't get hooks to match events imported through the new
`platypush.events` symlinked module.
## [1.0.2] - 2024-05-26
- Fixed regression introduced by the support of custom names through the
`@procedure` decorator.
## [1.0.0] - 2024-05-26
Many, many changes for the first major release of Platypush after so many
years.
- [!3](https://git.platypush.tech/platypush/platypush/milestone/3) All
backends, except for `http`, `nodered`, `redis` and `tcp`, are gone. Many
were already deprecated a while ago, but the change now applies to all of
them. Backends should only be components that actively listen for application
messages to process, not generic daemon threads for integrations. This had
been a source of confusion for a long time. Backends and plugins are now
merged, meaning that you won't need to configure two different sections
instead of one for many integrations (one for the stateless plugin, and one
for the background state listener). Please check the
[documentation](https://docs.platypush.tech) to verify the configuration
changes required by your integrations. This has been a long process that has
involved the rewrite of most of the integrations, and many bugs have been
fixed.
- Improved Docker support - now with a default `docker-compose.yml`, multiple
Dockerfiles for
[Alpine](https://git.platypush.tech/platypush/platypush/src/branch/master/platypush/install/docker/alpine.Dockerfile),
[Debian](https://git.platypush.tech/platypush/platypush/src/branch/master/platypush/install/docker/debian.Dockerfile),
[Ubuntu](https://git.platypush.tech/platypush/platypush/src/branch/master/platypush/install/docker/ubuntu.Dockerfile)
and
[Fedora](https://git.platypush.tech/platypush/platypush/src/branch/master/platypush/install/docker/fedora.Dockerfile)
base images. Many improvements on the `platydock` and `platyvenv` scripts
too, with better automated installation processes for optional dependencies.
- Added [official
packages](https://git.platypush.tech/platypush/platypush#system-package-manager-installation)
for
[Debian](https://git.platypush.tech/platypush/platypush#debian-ubuntu)
and [Fedora](https://git.platypush.tech/platypush/platypush#fedora).
- Added `--device-id`, `--workdir`, `--logsdir`, `--cachedir`, `--main-db`,
`--redis-host`, `--redis-port` and `--redis-queue` CLI arguments, along the
`PLATYPUSH_DEVICE_ID`, `PLATYPUSH_WORKDIR`, `PLATYPUSH_LOGSDIR`,
`PLATYPUSH_CACHEDIR`, `PLATYPUSH_DB`, `PLATYPUSH_REDIS_HOST`,
`PLATYPUSH_REDIS_PORT` and `PLATYPUSH_REDIS_QUEUE` environment variables.
- Added an _Extensions_ panel to the UI to dynamically:
- Install new dependencies directly from the Web view.
- Explore the documentation as well as the supported actions and events for
each plugin.
- Get ready-to-paste configuration snippets/templates.
- New, completely rewritten [documentation](https://docs.platypush.tech), which
now integrates the wiki, dynamically includes plugins configuration snippets
and dependencies, and adds a global filter bar for the integrations.
- [[#394](https://git.platypush.tech/platypush/platypush/issues/394)] A more
intuitive way of installing extra dependencies via `pip`. Instead of a static
list that the user should check inside of `setup.py`, the syntax `pip install
'platypush[plugin1,plugin2,...]'` is now supported.
- No more need to manually create `__init__.py` in each of the `scripts`
folders that you want to use to store your custom scripts. Automatic
discovery of scripts and creation of module files has been implemented. You
can now just drop a `.py` script with your procedures, hooks or crons in the
scripts folder and it should be picked up by the application.
- The _Execute_ Web panel now supports procedures too, as well as curl snippets.
- Removed all `Response` objects outside of the root type. They are now all
replaced by Marshmallow schemas with the structure automatically generated in
the documentation.
- [`alarm`] [[#340](https://git.platypush.tech/platypush/platypush/issues/340)]
Rewritten integration. It now includes a powerful UI panel to set up alarms
with custom procedures too.
- [`assistant.picovoice`]
[[#304](https://git.platypush.tech/platypush/platypush/issues/304)] New
all-in-one Picovoice integration that replaces the previous `stt.picovoice.*`
integrations.
- [`youtube`]
[[#337](https://git.platypush.tech/platypush/platypush/issues/337)] Full
rewrite of the plugin. It now supports Piped instances instead of the
official YouTube API. A new UI has also been designed to explore
subscriptions, playlists and channels.
- [`weather.*`]
[[#308](https://git.platypush.tech/platypush/platypush/issues/308)] Removed
the `weather.darksky` integration (it's now owned by Apple and the API is
basically dead) and enhanced the `weather.openweathermap` plugin instead.
- [`camera.pi*`] The old `camera.pi` integration based on the deprecated
`picamera` module has been moved to `camera.pi.legacy`. `camera.pi` is now a
new plugin which uses the new `picamera2` API (and it's so far only
compatible with recent versions on the Raspberry Pi OS).
- Dynamically auto-generate plugins documentation in the UI from the RST
docstrings.
- New design for the configuration panel.
- Better synchronization between processes on threads on application stop -
greatly reduced the risk of hanging processes on shutdown.
- Migrated all CI/CD pipelines to [Drone
CI](https://ci-cd.platypush.tech/platypush/platypush).
- Removed `google.fit` integration, as Google has deprecated the API.
- Removed `wiimote` integration: the `cwiid` library hasn't been updated in
years, it doesn't even work well with Python 3, and I'm not in the mood of
bringing it back from the dead.
- Removed `stt.deepspeech` integration. That project has been basically
abandoned by Mozilla, the libraries are very buggy and I don't think it's
going to see new developments any time soon.
- [[#297](https://git.platypush.tech/platypush/platypush/issues/297)] Removed
`spotify` backend integration based on Librespot. The project has gone
through a lot of changes, and I no longer have a Spotify premium account to
work on a new implementation. Open to contributions if somebody still wants
it.
## [0.50.3] - 2023-07-22 ## [0.50.3] - 2023-07-22

View file

@ -27,13 +27,9 @@ Guidelines:
you are changing some of the core entities (e.g. requests, events, procedures, hooks, crons you are changing some of the core entities (e.g. requests, events, procedures, hooks, crons
or the bus) then make sure to add tests and not to break the existing tests. or the bus) then make sure to add tests and not to break the existing tests.
- If the feature requires an optional dependency then make sure to document it: - If the feature requires an optional dependency then make sure to document it
in the `manifest.json` - refer to the Wiki (how to write
- In the class docstring (see other plugins and backends for examples).
- In [`setup.py`](https://git.platypush.tech/platypush/platypush/-/blob/master/setup.py#L72) as
an `extras_require` entry.
- In the plugin/backend class pydoc string.
- In the `manifest.yaml` - refer to the Wiki (how to write
[plugins](https://git.platypush.tech/platypush/platypush/wiki/Writing-your-own-plugins) [plugins](https://git.platypush.tech/platypush/platypush/wiki/Writing-your-own-plugins)
and [backends](https://git.platypush.tech/platypush/platypush/wiki/Writing-your-own-backends)) and
[backends](https://git.platypush.tech/platypush/platypush/wiki/Writing-your-own-backends))
for examples on how to write an extension manifest file. for examples on how to write an extension manifest file.

1
Dockerfile.alpine Symbolic link
View file

@ -0,0 +1 @@
platypush/install/docker/alpine.Dockerfile

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017, 2020 Fabio Manganiello Copyright (c) 2017, 2024 Fabio Manganiello
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -2,5 +2,16 @@ recursive-include platypush/backend/http/webapp/dist *
recursive-include platypush/install * recursive-include platypush/install *
include platypush/plugins/http/webpage/mercury-parser.js include platypush/plugins/http/webpage/mercury-parser.js
include platypush/config/*.yaml include platypush/config/*.yaml
global-include manifest.yaml include platypush/config/systemd/*
global-include manifest.json
global-include components.json.gz global-include components.json.gz
global-exclude __pycache__/*
global-exclude *.pyc
# Exclude symlinks to avoid issues with setuptools
exclude platypush/backend/http/webapp/public/icons/openweathermap/black
exclude platypush/backend/http/webapp/public/icons/openweathermap/white
exclude platypush/backend/http/webapp/src/**
exclude platypush/events
exclude platypush/install/scripts/ubuntu

1809
README.md

File diff suppressed because it is too large Load diff

61
docker-compose.yml Normal file
View file

@ -0,0 +1,61 @@
services:
platypush:
# Replace the build section with the next line if instead of building the
# image from a local checkout you want to pull the latest base
# (Alpine-based) image from the remote registry
# image: "quay.io/platypush/platypush:latest"
build:
context: .
# Alpine base image
dockerfile: ./platypush/install/docker/alpine.Dockerfile
# Debian base image
# dockerfile: ./platypush/install/docker/debian.Dockerfile
# Ubuntu base image
# dockerfile: ./platypush/install/docker/ubuntu.Dockerfile
# Fedora base image
# dockerfile: ./platypush/install/docker/fedora.Dockerfile
restart: "always"
command:
- platypush
- --redis-host
- redis
# Or, if you want to run Redis from the same container as Platypush,
# replace --redis-host redis with the line below
# - --start-redis
# Custom list of host devices that should be accessible to the container -
# e.g. an Arduino, an ESP-compatible microcontroller, a joystick etc.
# devices:
# - /dev/ttyUSB0
# Uncomment if you need plugins that require access to low-level hardware
# (e.g. Bluetooth BLE or GPIO/SPI/I2C) if access to individual devices is
# not enough or isn't practical
# privileged: true
# Copy .env.example to .env and modify as needed
# env_file:
# - .env
ports:
# Comment if you don't have the HTTP backend enable or you don't want to
# expose it
- "8008:8008"
# volumes:
# Replace with a path that contains/will contain your config.yaml file
# - /path/to/your/config:/etc/platypush
# Replace with a path that contains/will contain your working directory
# - /path/to/a/workdir:/var/lib/platypush
# Optionally, use an external volume for the cache
# - /path/to/a/cachedir:/var/cache/platypush
# This may be required, together with privileged mode, if you want the
# container to access the USB bus on the host (required for example if
# you have USB audio devices that you want to access from your plugins,
# or Bluetooth dongles, or other USB adapters).
# - /dev/bus/usb:/dev/bus/usb
redis:
image: redis

View file

@ -159,7 +159,7 @@ class IntegrationEnricher:
base_path, base_path,
*doc.split(os.sep)[:-1], *doc.split(os.sep)[:-1],
*doc.split(os.sep)[-1].split('.'), *doc.split(os.sep)[-1].split('.'),
'manifest.yaml', 'manifest.json',
) )
if not os.path.isfile(manifest_file): if not os.path.isfile(manifest_file):

View file

@ -152,7 +152,7 @@ const generateComponentsGrid = () => {
return return
} }
if (window.location.pathname.endsWith('/index.html')) { if (window.location.pathname === '/' || window.location.pathname.endsWith('/index.html')) {
if (tocWrappers.length < 2) { if (tocWrappers.length < 2) {
return return
} }
@ -188,9 +188,62 @@ const renderActionsList = () => {
}) })
} }
const createFilterBar = () => {
const input = document.createElement('input')
const referenceSection = document.getElementById('reference')
input.type = 'text'
input.placeholder = 'Filter'
input.classList.add('filter-bar')
input.addEventListener('input', (event) => {
const filter = event.target.value.toLowerCase()
referenceSection.querySelectorAll('ul.grid li').forEach((li) => {
if (li.innerText.toLowerCase().includes(filter)) {
li.style.display = 'flex'
} else {
li.style.display = 'none'
}
})
})
return input
}
const addFilterBar = () => {
const container = document.querySelector('.bd-main')
if (!container)
return
const referenceSection = document.getElementById('reference')
if (!referenceSection)
return
const header = referenceSection.querySelector('h2')
if (!header)
return
const origInnerHTML = header.innerHTML
header.innerHTML = '<span class="header-content">' + origInnerHTML + '</span>'
const input = createFilterBar()
header.appendChild(input)
const headerOffsetTop = header.offsetTop
// Apply the fixed class if the header is above the viewport
document.addEventListener('scroll', () => {
if (headerOffsetTop < window.scrollY) {
header.classList.add('fixed')
} else {
header.classList.remove('fixed')
}
})
}
document.addEventListener("DOMContentLoaded", function() { document.addEventListener("DOMContentLoaded", function() {
generateComponentsGrid() generateComponentsGrid()
convertDepsToTabs() convertDepsToTabs()
addClipboardToCodeBlocks() addClipboardToCodeBlocks()
renderActionsList() renderActionsList()
addFilterBar()
}) })

View file

@ -29,15 +29,18 @@ a.grid-title {
ul.grid li { ul.grid li {
display: flex; display: flex;
background: linear-gradient(0deg, #fff, #f9f9f9);
align-items: center; align-items: center;
justify-content: space-between;
margin: 0 10px 10px 0; margin: 0 10px 10px 0;
padding: 10px; padding: 20px;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 15px; border-radius: 15px;
flex-direction: column;
} }
ul.grid img { ul.grid img {
width: 32px; width: 48px;
margin-right: 5px; margin-right: 5px;
} }
@ -52,13 +55,20 @@ ul.grid li code .pre {
} }
ul.grid li:hover { ul.grid li:hover {
background: linear-gradient(0deg, #e0ffe8, #e3ffff); background: linear-gradient(0deg, #157765, #cbffd8) !important;
} }
ul.grid li a { ul.grid li a {
width: calc(100% - 35px); width: 100%;
display: flex; display: flex;
justify-content: center; justify-content: center;
text-align: center;
margin-top: 0.5em;
}
ul.grid li:hover a,
ul.grid li:hover a > code {
color: white !important;
} }
ul.grid li a code { ul.grid li a code {
@ -128,3 +138,51 @@ ul.grid .icon {
border-radius: 0 0 0.75em 0.75em; border-radius: 0 0 0.75em 0.75em;
} }
.bd-article-container {
position: relative;
}
.filter-bar {
width: 100%;
display: block;
font-size: 0.6em;
border: 1px solid #ccc;
border-radius: 0.75em;
margin: 0.5em 0;
padding: 0.25em;
}
#reference h2.fixed {
position: fixed;
top: 0;
background: white;
z-index: 1;
}
#reference h2.fixed .header-content {
display: none;
}
@media screen and (max-width: 768px) {
#reference h2.fixed {
width: 100%;
margin-left: -0.5em;
padding: 0.5em 0.5em 0 0.5em;
}
}
@media screen and (max-width: 959px) {
#reference h2.fixed {
width: 100%;
margin-left: -1em;
padding: 0.5em 0.5em 0 0.5em;
}
}
@media screen and (min-width: 960px) {
#reference h2.fixed {
width: 75%;
max-width: 800px;
padding-top: 0.5em;
}
}

View file

@ -7,9 +7,6 @@ Backends
:caption: Backends: :caption: Backends:
platypush/backend/http.rst platypush/backend/http.rst
platypush/backend/midi.rst
platypush/backend/nodered.rst platypush/backend/nodered.rst
platypush/backend/redis.rst platypush/backend/redis.rst
platypush/backend/stt.picovoice.hotword.rst
platypush/backend/stt.picovoice.speech.rst
platypush/backend/tcp.rst platypush/backend/tcp.rst

View file

@ -21,7 +21,7 @@ sys.path.insert(0, os.path.abspath("./_ext"))
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = 'Platypush' project = 'Platypush'
copyright = '2017-2023, Fabio Manganiello' copyright = '2017-2024, Fabio Manganiello'
author = 'Fabio Manganiello <fabio@manganiello.tech>' author = 'Fabio Manganiello <fabio@manganiello.tech>'
# The short X.Y version # The short X.Y version
@ -199,6 +199,7 @@ intersphinx_mapping = {'python': ('https://docs.python.org/3', None)}
autodoc_default_options = { autodoc_default_options = {
'members': True, 'members': True,
'show-inheritance': True, 'show-inheritance': True,
'inherited-members': True,
} }
sys.path.insert(0, os.path.abspath('../..')) sys.path.insert(0, os.path.abspath('../..'))

View file

@ -24,7 +24,6 @@ Events
platypush/events/geo.rst platypush/events/geo.rst
platypush/events/github.rst platypush/events/github.rst
platypush/events/google.rst platypush/events/google.rst
platypush/events/google.fit.rst
platypush/events/google.pubsub.rst platypush/events/google.pubsub.rst
platypush/events/gotify.rst platypush/events/gotify.rst
platypush/events/gpio.rst platypush/events/gpio.rst
@ -74,7 +73,6 @@ Events
platypush/events/web.rst platypush/events/web.rst
platypush/events/web.widget.rst platypush/events/web.widget.rst
platypush/events/websocket.rst platypush/events/websocket.rst
platypush/events/wiimote.rst
platypush/events/xmpp.rst platypush/events/xmpp.rst
platypush/events/zeroborg.rst platypush/events/zeroborg.rst
platypush/events/zeroconf.rst platypush/events/zeroconf.rst

View file

@ -24,9 +24,8 @@ Useful links
============ ============
* The `main page <https://platypush.tech>`_ of the project. * The `main page <https://platypush.tech>`_ of the project.
* The `Gitea page <https://git.platypush.tech/platypush/platypush>`_. * The `source code <https://git.platypush.tech/platypush/platypush>`_.
* The `blog <https://blog.platypush.tech>`_, for articles showing how to use * The `blog <https://blog.platypush.tech>`_.
Platypush in real-world scenarios.
Wiki Wiki
==== ====
@ -34,11 +33,15 @@ Wiki
.. toctree:: .. toctree::
:maxdepth: 3 :maxdepth: 3
wiki/index wiki/Home
wiki/Quickstart
wiki/Installation wiki/Installation
wiki/Plugins-installation
wiki/APIs
wiki/Variables
wiki/Entities
wiki/Configuration wiki/Configuration
wiki/Installing-extensions wiki/A-full-configuration-example
wiki/A-configuration-example
wiki/The-Web-interface wiki/The-Web-interface
Reference Reference
@ -50,7 +53,6 @@ Reference
backends backends
plugins plugins
events events
responses
Indices and tables Indices and tables
================== ==================

View file

@ -1,6 +0,0 @@
``midi``
==========================
.. automodule:: platypush.backend.midi
:members:

View file

@ -1,5 +0,0 @@
``stt.picovoice.hotword``
===========================================
.. automodule:: platypush.backend.stt.picovoice.hotword
:members:

View file

@ -1,5 +0,0 @@
``stt.picovoice.speech``
==========================================
.. automodule:: platypush.backend.stt.picovoice.speech
:members:

View file

@ -1,6 +0,0 @@
``google.fit``
======================================
.. automodule:: platypush.message.event.google.fit
:members:

View file

@ -1,6 +0,0 @@
``wiimote``
===================================
.. automodule:: platypush.message.event.wiimote
:members:

View file

@ -0,0 +1,5 @@
``assistant.openai``
====================
.. automodule:: platypush.plugins.assistant.openai
:members:

View file

@ -0,0 +1,5 @@
``assistant.picovoice``
=======================
.. automodule:: platypush.plugins.assistant.picovoice
:members:

View file

@ -0,0 +1,5 @@
``cups``
========
.. automodule:: platypush.plugins.cups
:members:

View file

@ -1,6 +0,0 @@
``google.fit``
================================
.. automodule:: platypush.plugins.google.fit
:members:

View file

@ -1,6 +0,0 @@
``media.omxplayer``
=====================================
.. automodule:: platypush.plugins.media.omxplayer
:members:

View file

@ -0,0 +1,5 @@
``openai``
==========
.. automodule:: platypush.plugins.openai
:members:

View file

@ -1,5 +0,0 @@
``printer.cups``
==================================
.. automodule:: platypush.plugins.printer.cups
:members:

View file

@ -0,0 +1,5 @@
``procedures``
==============
.. automodule:: platypush.plugins.procedures
:members:

View file

@ -1,5 +0,0 @@
``stt.picovoice.hotword``
===========================================
.. automodule:: platypush.plugins.stt.picovoice.hotword
:members:

View file

@ -1,5 +0,0 @@
``stt.picovoice.speech``
==========================================
.. automodule:: platypush.plugins.stt.picovoice.speech
:members:

View file

@ -0,0 +1,5 @@
``tts.openai``
==============
.. automodule:: platypush.plugins.tts.openai
:members:

View file

@ -0,0 +1,5 @@
``tts.picovoice``
=================
.. automodule:: platypush.plugins.tts.picovoice
:members:

View file

@ -1,5 +0,0 @@
``camera.android``
=============================================
.. automodule:: platypush.message.response.camera.android
:members:

View file

@ -1,5 +0,0 @@
``camera``
=====================================
.. automodule:: platypush.message.response.camera
:members:

View file

@ -1,5 +0,0 @@
``google.drive``
===========================================
.. automodule:: platypush.message.response.google.drive
:members:

View file

@ -1,5 +0,0 @@
``pihole``
=====================================
.. automodule:: platypush.message.response.pihole
:members:

View file

@ -1,5 +0,0 @@
``printer.cups``
===========================================
.. automodule:: platypush.message.response.printer.cups
:members:

View file

@ -1,5 +0,0 @@
``qrcode``
=====================================
.. automodule:: platypush.message.response.qrcode
:members:

View file

@ -1,5 +0,0 @@
``ssh``
==================================
.. automodule:: platypush.message.response.ssh
:members:

View file

@ -1,5 +0,0 @@
``stt``
==================================
.. automodule:: platypush.message.response.stt
:members:

View file

@ -1,5 +0,0 @@
``tensorflow``
=========================================
.. automodule:: platypush.message.response.tensorflow
:members:

View file

@ -1,5 +0,0 @@
``translate``
========================================
.. automodule:: platypush.message.response.translate
:members:

View file

@ -11,6 +11,8 @@ Plugins
platypush/plugins/application.rst platypush/plugins/application.rst
platypush/plugins/arduino.rst platypush/plugins/arduino.rst
platypush/plugins/assistant.google.rst platypush/plugins/assistant.google.rst
platypush/plugins/assistant.openai.rst
platypush/plugins/assistant.picovoice.rst
platypush/plugins/autoremote.rst platypush/plugins/autoremote.rst
platypush/plugins/bluetooth.rst platypush/plugins/bluetooth.rst
platypush/plugins/calendar.rst platypush/plugins/calendar.rst
@ -25,6 +27,7 @@ Plugins
platypush/plugins/clipboard.rst platypush/plugins/clipboard.rst
platypush/plugins/config.rst platypush/plugins/config.rst
platypush/plugins/csv.rst platypush/plugins/csv.rst
platypush/plugins/cups.rst
platypush/plugins/db.rst platypush/plugins/db.rst
platypush/plugins/dbus.rst platypush/plugins/dbus.rst
platypush/plugins/dropbox.rst platypush/plugins/dropbox.rst
@ -38,7 +41,6 @@ Plugins
platypush/plugins/github.rst platypush/plugins/github.rst
platypush/plugins/google.calendar.rst platypush/plugins/google.calendar.rst
platypush/plugins/google.drive.rst platypush/plugins/google.drive.rst
platypush/plugins/google.fit.rst
platypush/plugins/google.mail.rst platypush/plugins/google.mail.rst
platypush/plugins/google.maps.rst platypush/plugins/google.maps.rst
platypush/plugins/google.pubsub.rst platypush/plugins/google.pubsub.rst
@ -75,7 +77,6 @@ Plugins
platypush/plugins/media.kodi.rst platypush/plugins/media.kodi.rst
platypush/plugins/media.mplayer.rst platypush/plugins/media.mplayer.rst
platypush/plugins/media.mpv.rst platypush/plugins/media.mpv.rst
platypush/plugins/media.omxplayer.rst
platypush/plugins/media.plex.rst platypush/plugins/media.plex.rst
platypush/plugins/media.subtitles.rst platypush/plugins/media.subtitles.rst
platypush/plugins/media.vlc.rst platypush/plugins/media.vlc.rst
@ -94,10 +95,11 @@ Plugins
platypush/plugins/ngrok.rst platypush/plugins/ngrok.rst
platypush/plugins/nmap.rst platypush/plugins/nmap.rst
platypush/plugins/ntfy.rst platypush/plugins/ntfy.rst
platypush/plugins/openai.rst
platypush/plugins/otp.rst platypush/plugins/otp.rst
platypush/plugins/pihole.rst platypush/plugins/pihole.rst
platypush/plugins/ping.rst platypush/plugins/ping.rst
platypush/plugins/printer.cups.rst platypush/plugins/procedures.rst
platypush/plugins/pushbullet.rst platypush/plugins/pushbullet.rst
platypush/plugins/pwm.pca9685.rst platypush/plugins/pwm.pca9685.rst
platypush/plugins/qrcode.rst platypush/plugins/qrcode.rst
@ -119,8 +121,6 @@ Plugins
platypush/plugins/smartthings.rst platypush/plugins/smartthings.rst
platypush/plugins/sound.rst platypush/plugins/sound.rst
platypush/plugins/ssh.rst platypush/plugins/ssh.rst
platypush/plugins/stt.picovoice.hotword.rst
platypush/plugins/stt.picovoice.speech.rst
platypush/plugins/sun.rst platypush/plugins/sun.rst
platypush/plugins/switch.tplink.rst platypush/plugins/switch.tplink.rst
platypush/plugins/switch.wemo.rst platypush/plugins/switch.wemo.rst
@ -135,6 +135,8 @@ Plugins
platypush/plugins/tts.rst platypush/plugins/tts.rst
platypush/plugins/tts.google.rst platypush/plugins/tts.google.rst
platypush/plugins/tts.mimic3.rst platypush/plugins/tts.mimic3.rst
platypush/plugins/tts.openai.rst
platypush/plugins/tts.picovoice.rst
platypush/plugins/tv.samsung.ws.rst platypush/plugins/tv.samsung.ws.rst
platypush/plugins/twilio.rst platypush/plugins/twilio.rst
platypush/plugins/udp.rst platypush/plugins/udp.rst

View file

@ -1,18 +0,0 @@
Responses
=========
.. toctree::
:maxdepth: 1
:caption: Responses:
platypush/responses/camera.rst
platypush/responses/camera.android.rst
platypush/responses/google.drive.rst
platypush/responses/pihole.rst
platypush/responses/printer.cups.rst
platypush/responses/qrcode.rst
platypush/responses/ssh.rst
platypush/responses/stt.rst
platypush/responses/tensorflow.rst
platypush/responses/translate.rst

View file

@ -1,15 +1,16 @@
# A more versatile way to define event hooks than the YAML format of `config.yaml` is through native Python scripts. # A more versatile way to define event hooks than the YAML format of
# You can define hooks as simple Python functions that use the `platypush.event.hook.hook` decorator to specify on # `config.yaml` is through native Python scripts. You can define hooks as simple
# which event type they should be called, and optionally on which event attribute values. # Python functions that use the `platypush.event.hook.hook` decorator to specify
# on 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
# @hook decorator will automatically be discovered and imported as event hooks into the platform at runtime. # `~/.config/platypush/scripts`. All the functions that use the @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,13 +19,15 @@ 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):
""" """
This function will be executed when a SpeechRecognizedEvent with `phrase="play the music"` is triggered. This function will be executed when a SpeechRecognizedEvent with
`event` contains the event object and `context` any key-value info from the running context. `phrase="play the music"` is triggered. `event` contains the event object
Note that in this specific case we can leverage the token-extraction feature of SpeechRecognizedEvent through and `context` any key-value info from the running context. Note that in this
${} that operates on regex-like principles to extract any text that matches the pattern into context variables. specific case we can leverage the token-extraction feature of
SpeechRecognizedEvent through ${} that operates on regex-like principles to
extract any text that matches the pattern into context variables.
""" """
results = run( results = run(
'music.mpd.search', 'music.mpd.search',
@ -34,16 +37,17 @@ def on_music_play_command(event, title=None, artist=None, **context):
}, },
) )
if results: if results and results[0]:
run('music.mpd.play', results[0]['file']) run('music.mpd.play', results[0]['file'])
else: else:
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():
""" """
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
to make sure that your speech is properly detected. assistant and simply pauses the music to make sure that your speech is
properly detected.
""" """
run('music.mpd.pause_if_playing') run('music.mpd.pause_if_playing')

View file

@ -1,22 +0,0 @@
# platypush systemd service example.
# Edit and copy this file to your systemd folder. It's usually
# /usr/lib/systemd/user for global installation or
# ~/.config/systemd/user for user installation. You can
# then control and monitor the service through
# systemd [--user] [start|stop|restart|status] platypush.service
[Unit]
Description=Platypush daemon
After=network.target redis.service
[Service]
# platypush installation path
ExecStart=/usr/bin/platypush
Restart=always
# How long should be waited before restarting the service
# in case of failure.
RestartSec=10
[Install]
WantedBy=default.target

View file

@ -0,0 +1 @@
../../platypush/config/systemd/platypush.service

View file

@ -8,7 +8,6 @@ import pkgutil
from platypush.backend import Backend from platypush.backend import Backend
from platypush.message.event import Event from platypush.message.event import Event
from platypush.message.response import Response
from platypush.plugins import Plugin from platypush.plugins import Plugin
from platypush.utils.manifest import Manifests from platypush.utils.manifest import Manifests
from platypush.utils.mock import auto_mocks from platypush.utils.mock import auto_mocks
@ -26,10 +25,6 @@ def get_all_events():
return _get_modules(Event) return _get_modules(Event)
def get_all_responses():
return _get_modules(Response)
def _get_modules(base_type: type): def _get_modules(base_type: type):
ret = set() ret = set()
base_dir = os.path.dirname(inspect.getfile(base_type)) base_dir = os.path.dirname(inspect.getfile(base_type))
@ -151,20 +146,11 @@ def generate_events_doc():
) )
def generate_responses_doc():
_generate_components_doc(
index_name='responses',
package_name='message.response',
components=sorted(response for response in get_all_responses() if response),
)
def main(): def main():
with auto_mocks(): with auto_mocks():
generate_plugins_doc() generate_plugins_doc()
generate_backends_doc() generate_backends_doc()
generate_events_doc() generate_events_doc()
generate_responses_doc()
if __name__ == '__main__': if __name__ == '__main__':

View file

@ -5,29 +5,42 @@ Platypush
.. license: MIT .. license: MIT
""" """
from .app import Application from .app import Application, app
from .config import Config from .config import Config
from .context import get_backend, get_bus, get_plugin from .context import Variable, get_backend, get_bus, get_plugin
from .cron import cron
from .event.hook import hook
from .message.event import Event from .message.event import Event
from .message.request import Request from .message.request import Request
from .message.response import Response from .message.response import Response
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
__version__ = '1.3.4'
__author__ = 'Fabio Manganiello <fabio@manganiello.tech>' __author__ = 'Fabio Manganiello <fabio@manganiello.tech>'
__version__ = '0.50.3'
__all__ = [ __all__ = [
'Application', 'Application',
'Variable',
'Config', 'Config',
'Event', 'Event',
'Request', 'Request',
'Response', 'Response',
'app',
'cron',
'get_backend', 'get_backend',
'get_bus', 'get_bus',
'get_plugin', 'get_plugin',
'hook',
'main', 'main',
'procedure',
'run', 'run',
'when',
'__version__',
] ]

View file

@ -1,4 +1,4 @@
from ._app import Application, main from ._app import Application, app, main
__all__ = ["Application", "main"] __all__ = ["Application", "app", "main"]

View file

@ -5,6 +5,7 @@ import os
import signal import signal
import subprocess import subprocess
import sys import sys
from textwrap import dedent
from typing import Optional, Sequence from typing import Optional, Sequence
from platypush.bus import Bus from platypush.bus import Bus
@ -18,7 +19,6 @@ from platypush.entities import init_entities_engine, EntitiesEngine
from platypush.event.processor import EventProcessor from platypush.event.processor import EventProcessor
from platypush.logger import Logger from platypush.logger import Logger
from platypush.message.event import Event from platypush.message.event import Event
from platypush.message.event.application import ApplicationStartedEvent
from platypush.message.request import Request from platypush.message.request import Request
from platypush.message.response import Response from platypush.message.response import Response
from platypush.utils import get_enabled_plugins, get_redis_conf from platypush.utils import get_enabled_plugins, get_redis_conf
@ -32,6 +32,9 @@ class Application:
# Default Redis port # Default Redis port
_default_redis_port = 6379 _default_redis_port = 6379
# Default Redis binary, if --start-redis is set
_default_redis_bin = 'redis-server'
# backend_name => backend_obj map # backend_name => backend_obj map
backends = None backends = None
@ -55,6 +58,7 @@ class Application:
start_redis: bool = False, start_redis: bool = False,
redis_host: Optional[str] = None, redis_host: Optional[str] = None,
redis_port: Optional[int] = None, redis_port: Optional[int] = None,
redis_bin: Optional[str] = None,
ctrl_sock: Optional[str] = None, ctrl_sock: Optional[str] = None,
): ):
""" """
@ -142,10 +146,11 @@ class Application:
:param verbose: Enable debug/verbose logging, overriding the stored :param verbose: Enable debug/verbose logging, overriding the stored
configuration (default: False). configuration (default: False).
:param start_redis: If set, it starts a managed Redis instance upon :param start_redis: If set, it starts a managed Redis instance upon
boot (it requires the ``redis-server`` executable installed on the boot (it requires Redis installed on the server, see
server). This is particularly useful when running the application ``redis_bin``). This is particularly useful when running the
inside of Docker containers, without relying on ``docker-compose`` application inside of Docker containers, without relying on
to start multiple containers, and in tests (default: False). ``docker-compose`` to start multiple containers, and in tests
(default: False).
:param redis_host: Host of the Redis server to be used. The order of :param redis_host: Host of the Redis server to be used. The order of
precedence is: precedence is:
@ -168,6 +173,16 @@ class Application:
the configuration file. the configuration file.
- ``6379`` - ``6379``
:param redis_bin: Path to the Redis server executable, if ``start_redis``
is set. Alternative drop-in Redis implementations such as
``keydb-server``, ``valkey``, ``redict`` can be used. The order of
precedence is:
- The ``redis_bin`` parameter (or the ``--redis-bin`` command
line argument).
- The ``PLATYPUSH_REDIS_BIN`` environment variable.
- ``redis-server``
:param ctrl_sock: If set, it identifies a path to a UNIX domain socket :param ctrl_sock: If set, it identifies a path to a UNIX domain socket
that the application can use to send control messages (e.g. STOP that the application can use to send control messages (e.g. STOP
and RESTART) to its parent. and RESTART) to its parent.
@ -180,6 +195,8 @@ class Application:
or os.environ.get('PLATYPUSH_REDIS_QUEUE') or os.environ.get('PLATYPUSH_REDIS_QUEUE')
or RedisBus.DEFAULT_REDIS_QUEUE or RedisBus.DEFAULT_REDIS_QUEUE
) )
os.environ['PLATYPUSH_REDIS_QUEUE'] = self.redis_queue
self.config_file = config_file or os.environ.get('PLATYPUSH_CONFIG') self.config_file = config_file or os.environ.get('PLATYPUSH_CONFIG')
self.verbose = verbose self.verbose = verbose
self.db_engine = db or os.environ.get('PLATYPUSH_DB') self.db_engine = db or os.environ.get('PLATYPUSH_DB')
@ -209,6 +226,11 @@ class Application:
self.start_redis = start_redis self.start_redis = start_redis
self.redis_host = redis_host or os.environ.get('PLATYPUSH_REDIS_HOST') self.redis_host = redis_host or os.environ.get('PLATYPUSH_REDIS_HOST')
self.redis_port = redis_port or os.environ.get('PLATYPUSH_REDIS_PORT') self.redis_port = redis_port or os.environ.get('PLATYPUSH_REDIS_PORT')
self.redis_bin = (
redis_bin
or os.environ.get('PLATYPUSH_REDIS_BIN')
or self._default_redis_bin
)
self._redis_conf = { self._redis_conf = {
'host': self.redis_host or 'localhost', 'host': self.redis_host or 'localhost',
'port': self.redis_port or self._default_redis_port, 'port': self.redis_port or self._default_redis_port,
@ -260,7 +282,7 @@ class Application:
port = self._redis_conf['port'] port = self._redis_conf['port']
log.info('Starting local Redis instance on %s', port) log.info('Starting local Redis instance on %s', port)
redis_cmd_args = [ redis_cmd_args = [
'redis-server', self.redis_bin,
'--bind', '--bind',
'localhost', 'localhost',
'--port', '--port',
@ -343,7 +365,13 @@ class Application:
elif isinstance(msg, Response): elif isinstance(msg, Response):
msg.log() msg.log()
elif isinstance(msg, Event): elif isinstance(msg, Event):
msg.log() log.info(
'Received event: %s.%s[id=%s]',
msg.__class__.__module__,
msg.__class__.__name__,
msg.id,
)
msg.log(level=logging.DEBUG)
self.event_processor.process_event(msg) self.event_processor.process_event(msg)
return _f return _f
@ -420,7 +448,21 @@ class Application:
if not self.no_capture_stderr: if not self.no_capture_stderr:
sys.stderr = Logger(log.warning) sys.stderr = Logger(log.warning)
log.info('---- Starting platypush v.%s', __version__) log.info(
dedent(
r'''
_____ _ _ _
| __ \| | | | | |
| |__) | | __ _| |_ _ _ _ __ _ _ ___| |__
| ___/| |/ _` | __| | | | '_ \| | | / __| '_ \
| | | | (_| | |_| |_| | |_) | |_| \__ \ | | |
|_| |_|\__,_|\__|\__, | .__/ \__,_|___/_| |_|
__/ | |
|___/|_|
'''
)
)
log.info('---- Starting Platypush v.%s', __version__)
# Start the local Redis service if required # Start the local Redis service if required
if self.start_redis: if self.start_redis:
@ -445,7 +487,6 @@ class Application:
self.cron_scheduler.start() self.cron_scheduler.start()
assert self.bus, 'The bus is not running' assert self.bus, 'The bus is not running'
self.bus.post(ApplicationStartedEvent())
# Poll for messages on the bus # Poll for messages on the bus
try: try:
@ -464,10 +505,15 @@ class Application:
self._run() self._run()
app: Optional[Application] = None
def main(*args: str): def main(*args: str):
""" """
Application entry point. Application entry point.
""" """
global app
app = Application.from_cmdline(args) app = Application.from_cmdline(args)
try: try:

View file

@ -402,6 +402,13 @@ class Backend(Thread, EventGenerator, ExtensionWithManifest):
) )
return return
if self.zeroconf:
self.logger.info(
'Zeroconf service already registered for %s, removing the previous instance',
self.__class__.__name__,
)
self.unregister_service()
self.zeroconf = Zeroconf() self.zeroconf = Zeroconf()
srv_desc = { srv_desc = {
'name': 'Platypush', 'name': 'Platypush',

View file

@ -10,10 +10,8 @@ from multiprocessing import Process
from time import time from time import time
from typing import Mapping, Optional from typing import Mapping, Optional
import psutil
from tornado.httpserver import HTTPServer from tornado.httpserver import HTTPServer
from tornado.netutil import bind_sockets from tornado.netutil import bind_sockets, bind_unix_socket
from tornado.process import cpu_count, fork_processes from tornado.process import cpu_count, fork_processes
from tornado.wsgi import WSGIContainer from tornado.wsgi import WSGIContainer
from tornado.web import Application, FallbackHandler from tornado.web import Application, FallbackHandler
@ -153,14 +151,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'
@ -201,7 +198,8 @@ class HttpBackend(Backend):
def __init__( def __init__(
self, self,
port: int = DEFAULT_HTTP_PORT, port: int = DEFAULT_HTTP_PORT,
bind_address: str = '0.0.0.0', bind_address: Optional[str] = '0.0.0.0',
bind_socket: Optional[str] = None,
resource_dirs: Optional[Mapping[str, str]] = None, resource_dirs: Optional[Mapping[str, str]] = None,
secret_key_file: Optional[str] = None, secret_key_file: Optional[str] = None,
num_workers: Optional[int] = None, num_workers: Optional[int] = None,
@ -210,7 +208,16 @@ class HttpBackend(Backend):
): ):
""" """
:param port: Listen port for the web server (default: 8008) :param port: Listen port for the web server (default: 8008)
:param bind_address: Address/interface to bind to (default: 0.0.0.0, accept connection from any IP) :param bind_address: Address/interface to bind to (default: 0.0.0.0,
accept connection from any IP). You can set it to null to disable
the network interface binding, but then you must set ``bind_socket``
as an alternative.
:param bind_socket: Path to the Unix socket to bind to. If set, the
server will bind to the path of the specified Unix socket. If set to
``true``, then a socket will be automatically initialized on
``<workdir>/platypush-<device_id>.sock``. If not set, the server will
only listen on the specified bind address and port. Note that either
``bind_socket`` or ``socket_path`` must be set.
:param resource_dirs: Static resources directories that will be :param resource_dirs: Static resources directories that will be
accessible through ``/resources/<path>``. It is expressed as a map accessible through ``/resources/<path>``. It is expressed as a map
where the key is the relative path under ``/resources`` to expose and where the key is the relative path under ``/resources`` to expose and
@ -232,11 +239,23 @@ class HttpBackend(Backend):
super().__init__(**kwargs) super().__init__(**kwargs)
assert (
bind_address or bind_socket
), 'Either bind_address or bind_socket must be set'
self.port = port self.port = port
self._server_proc: Optional[Process] = None self._server_proc: Optional[Process] = None
self._service_registry_thread = None self._service_registry_thread = None
self.bind_address = bind_address self.bind_address = bind_address
if bind_socket is True:
bind_socket = os.path.join(
Config.get_workdir(), f'platypush-{Config.get_device_id()}.sock'
)
self.socket_path = None
if bind_socket:
self.socket_path = os.path.expanduser(bind_socket)
if resource_dirs: if resource_dirs:
self.resource_dirs = { self.resource_dirs = {
name: os.path.abspath(os.path.expanduser(d)) name: os.path.abspath(os.path.expanduser(d))
@ -261,8 +280,8 @@ class HttpBackend(Backend):
super().on_stop() super().on_stop()
self.logger.info('Received STOP event on HttpBackend') self.logger.info('Received STOP event on HttpBackend')
start = time() start = time()
remaining_time: partial[float] = partial( # type: ignore remaining_time: partial[float] = partial(
get_remaining_timeout, timeout=self._STOP_TIMEOUT, start=start get_remaining_timeout, timeout=self._STOP_TIMEOUT, start=start # type: ignore
) )
if self._server_proc: if self._server_proc:
@ -365,6 +384,7 @@ class HttpBackend(Backend):
) )
if self.use_werkzeug_server: if self.use_werkzeug_server:
assert self.bind_address, 'bind_address must be set when using Werkzeug'
application.config['redis_queue'] = self.bus.redis_queue # type: ignore application.config['redis_queue'] = self.bus.redis_queue # type: ignore
application.run( application.run(
host=self.bind_address, host=self.bind_address,
@ -373,9 +393,13 @@ class HttpBackend(Backend):
debug=True, debug=True,
) )
else: else:
sockets = bind_sockets( sockets = []
self.port, address=self.bind_address, reuse_port=True
) if self.bind_address:
sockets.extend(bind_sockets(self.port, address=self.bind_address))
if self.socket_path:
sockets.append(bind_unix_socket(self.socket_path))
try: try:
fork_processes(self.num_workers) fork_processes(self.num_workers)
@ -395,6 +419,14 @@ class HttpBackend(Backend):
workers when the server terminates: workers when the server terminates:
https://github.com/tornadoweb/tornado/issues/1912. https://github.com/tornadoweb/tornado/issues/1912.
""" """
try:
import psutil
except ImportError:
self.logger.warning(
'Could not import psutil, hanging worker processes might remain active'
)
return
parent_pid = ( parent_pid = (
self._server_proc.pid self._server_proc.pid
if self._server_proc and self._server_proc.pid if self._server_proc and self._server_proc.pid
@ -422,8 +454,8 @@ class HttpBackend(Backend):
# Initialize the timeout # Initialize the timeout
start = time() start = time()
remaining_time: partial[int] = partial( # type: ignore remaining_time: partial[int] = partial(
get_remaining_timeout, timeout=self._STOP_TIMEOUT, start=start, cls=int get_remaining_timeout, timeout=self._STOP_TIMEOUT, start=start, cls=int # type: ignore
) )
# Wait for all children to terminate (with timeout) # Wait for all children to terminate (with timeout)

View file

@ -4,8 +4,15 @@ import logging
from flask import Blueprint, request, abort, jsonify from flask import Blueprint, request, abort, jsonify
from platypush.backend.http.app.utils import authenticate
from platypush.backend.http.app.utils.auth import (
UserAuthStatus,
current_user,
get_current_user_or_auth_status,
)
from platypush.exceptions.user import UserException from platypush.exceptions.user import UserException
from platypush.user import UserManager from platypush.user import User, UserManager
from platypush.utils import utcnow
auth = Blueprint('auth', __name__) auth = Blueprint('auth', __name__)
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -16,39 +23,24 @@ __routes__ = [
] ]
@auth.route('/auth', methods=['POST']) def _dump_session(session, redirect_page='/'):
def auth_endpoint(): return jsonify(
"""
Authentication endpoint. It validates the user credentials provided over a JSON payload with the following
structure:
.. code-block:: json
{ {
"username": "USERNAME", 'status': 'ok',
"password": "PASSWORD", 'user_id': session.user_id,
"expiry_days": "The generated token should be valid for these many days" 'session_token': session.session_token,
'expires_at': session.expires_at,
'redirect': redirect_page,
} }
)
``expiry_days`` is optional, and if omitted or set to zero the token will be valid indefinitely.
Upon successful validation, a new JWT token will be generated using the service's self-generated RSA key-pair and it def _jwt_auth():
will be returned to the user. The token can then be used to authenticate API calls to ``/execute`` by setting the
``Authorization: Bearer <TOKEN_HERE>`` header upon HTTP calls.
:return: Return structure:
.. code-block:: json
{
"token": "<generated token here>"
}
"""
try: try:
payload = json.loads(request.get_data(as_text=True)) payload = json.loads(request.get_data(as_text=True))
username, password = payload['username'], payload['password'] username, password = payload['username'], payload['password']
except Exception as e: except Exception:
log.warning('Invalid payload passed to the auth endpoint: ' + str(e)) log.warning('Invalid payload passed to the auth endpoint')
abort(400) abort(400)
expiry_days = payload.get('expiry_days') expiry_days = payload.get('expiry_days')
@ -59,8 +51,365 @@ def auth_endpoint():
user_manager = UserManager() user_manager = UserManager()
try: try:
return jsonify({ return jsonify(
'token': user_manager.generate_jwt_token(username=username, password=password, expires_at=expires_at), {
}) 'token': user_manager.generate_jwt_token(
username=username, password=password, expires_at=expires_at
),
}
)
except UserException as e: except UserException as e:
abort(401, str(e)) abort(401, str(e))
def _session_auth():
user_manager = UserManager()
session_token = request.cookies.get('session_token')
redirect_page = request.args.get('redirect') or '/'
if session_token:
user, session = user_manager.authenticate_user_session(session_token)[:2]
if user and session:
return _dump_session(session, redirect_page)
if request.form:
username = request.form.get('username')
password = request.form.get('password')
code = request.form.get('code')
remember = request.form.get('remember')
expires = utcnow() + datetime.timedelta(days=365) if remember else None
session, status = user_manager.create_user_session( # type: ignore
username=username,
password=password,
code=code,
expires_at=expires,
with_status=True,
)
if session:
return _dump_session(session, redirect_page)
if status:
auth_status = UserAuthStatus.by_status(status)
assert auth_status
return auth_status.to_response()
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
def _create_token():
payload = {}
try:
payload = json.loads(request.get_data(as_text=True))
except json.JSONDecodeError:
pass
user = None
username = payload.get('username')
password = payload.get('password')
name = payload.get('name')
expiry_days = payload.get('expiry_days')
user_manager = UserManager()
response = get_current_user_or_auth_status(request)
# Try and authenticate with the credentials passed in the JSON payload
if username and password:
user = user_manager.authenticate_user(username, password, skip_2fa=True)
if not isinstance(user, User):
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
if not user:
if not (response and isinstance(response, User)):
return response.to_response()
user = response
expires_at = None
if expiry_days:
expires_at = datetime.datetime.now() + datetime.timedelta(days=expiry_days)
try:
token = UserManager().generate_api_token(
username=str(user.username), name=name, expires_at=expires_at
)
return jsonify({'token': token})
except UserException:
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
def _delete_token():
try:
payload = json.loads(request.get_data(as_text=True))
token = payload.get('token')
assert token
except (AssertionError, json.JSONDecodeError):
return UserAuthStatus.INVALID_TOKEN.to_response()
user_manager = UserManager()
try:
token = payload.get('token')
if not token:
return UserAuthStatus.INVALID_TOKEN.to_response()
ret = user_manager.delete_api_token(token)
if not ret:
return UserAuthStatus.INVALID_TOKEN.to_response()
return jsonify({'status': 'ok'})
except UserException:
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
def _register_route():
"""Registration endpoint"""
user_manager = UserManager()
session_token = request.cookies.get('session_token')
redirect_page = request.args.get('redirect') or '/'
if session_token:
user, session = user_manager.authenticate_user_session(session_token)[:2]
if user and session:
return _dump_session(session, redirect_page)
if user_manager.get_user_count() > 0:
return UserAuthStatus.REGISTRATION_DISABLED.to_response()
if not request.form:
return UserAuthStatus.MISSING_USERNAME.to_response()
username = request.form.get('username')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
remember = request.form.get('remember')
if not username:
return UserAuthStatus.MISSING_USERNAME.to_response()
if not password:
return UserAuthStatus.MISSING_PASSWORD.to_response()
if password != confirm_password:
return UserAuthStatus.PASSWORD_MISMATCH.to_response()
user_manager.create_user(username=username, password=password)
session, status = user_manager.create_user_session( # type: ignore
username=username,
password=password,
expires_at=(utcnow() + datetime.timedelta(days=365) if remember else None),
with_status=True,
)
if session:
return _dump_session(session, redirect_page)
if status:
return status.to_response() # type: ignore
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
def _auth_get():
"""
Get the current authentication status of the user session.
"""
user_manager = UserManager()
session_token = request.cookies.get('session_token')
redirect_page = request.args.get('redirect') or '/'
user, session, status = user_manager.authenticate_user_session( # type: ignore
session_token, with_status=True
)
if user and session:
return _dump_session(session, redirect_page)
response = get_current_user_or_auth_status(request)
if isinstance(response, User):
user = response
return jsonify(
{'status': 'ok', 'user_id': user.user_id, 'username': user.username}
)
if response:
status = response
if status:
if not isinstance(status, UserAuthStatus):
status = UserAuthStatus.by_status(status)
if not status:
status = UserAuthStatus.INVALID_CREDENTIALS
return status.to_response()
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
def _auth_post():
"""
Authenticate the user session.
"""
auth_type = request.args.get('type') or 'token'
if auth_type == 'token':
return _create_token()
if auth_type == 'jwt':
return _jwt_auth()
if auth_type == 'register':
return _register_route()
if auth_type == 'login':
return _session_auth()
return UserAuthStatus.INVALID_AUTH_TYPE.to_response()
def _auth_delete():
"""
Logout/invalidate a token or the current user session.
"""
# Delete the specified API token if it's passed on the JSON payload
token = None
try:
payload = json.loads(request.get_data(as_text=True))
token = payload.get('token')
except json.JSONDecodeError:
pass
if token:
return _delete_token()
user_manager = UserManager()
session_token = request.cookies.get('session_token')
redirect_page = request.args.get('redirect') or '/'
if session_token:
user, session = user_manager.authenticate_user_session(session_token)[:2]
if user and session:
user_manager.delete_user_session(session_token)
return jsonify({'status': 'ok', 'redirect': redirect_page})
return UserAuthStatus.INVALID_SESSION.to_response()
def _tokens_get():
user = current_user()
if not user:
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
tokens = UserManager().get_api_tokens(username=str(user.username))
return jsonify(
{
'tokens': [
{
'id': t.id,
'name': t.name,
'created_at': t.created_at,
'expires_at': t.expires_at,
}
for t in tokens
]
}
)
def _tokens_delete():
args = {}
try:
payload = json.loads(request.get_data(as_text=True))
token = payload.get('token')
if token:
args['token'] = token
else:
token_id = payload.get('token_id')
if token_id:
args['token_id'] = token_id
assert args, 'No token or token_id specified'
except (AssertionError, json.JSONDecodeError):
return UserAuthStatus.INVALID_TOKEN.to_response()
user_manager = UserManager()
user = current_user()
if not user:
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
args['username'] = str(user.username)
try:
user_manager.delete_api_token(**args)
return jsonify({'status': 'ok'})
except AssertionError as e:
return (
jsonify({'status': 'error', 'error': 'bad_request', 'message': str(e)}),
400,
)
except UserException:
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
except Exception as e:
log.error('Token deletion error', exc_info=e)
return UserAuthStatus.UNKNOWN_ERROR.to_response()
@auth.route('/auth', methods=['GET', 'POST', 'DELETE'])
def auth_endpoint():
"""
Authentication endpoint. It validates the user credentials provided over a
JSON payload with the following structure:
.. code-block:: json
{
"username": "USERNAME",
"password": "PASSWORD",
"code": "2FA_CODE",
"expiry_days": "The generated token should be valid for these many days"
}
``expiry_days`` is optional, and if omitted or set to zero the token will
be valid indefinitely.
Upon successful validation, a new JWT token will be generated using the
service's self-generated RSA key-pair and it will be returned to the user.
The token can then be used to authenticate API calls to ``/execute`` by
setting the ``Authorization: Bearer <TOKEN_HERE>`` header upon HTTP calls.
:return: Return structure:
.. code-block:: json
{
"token": "<generated token here>"
}
"""
if request.method == 'GET':
return _auth_get()
if request.method == 'POST':
return _auth_post()
if request.method == 'DELETE':
return _auth_delete()
return UserAuthStatus.INVALID_METHOD.to_response()
@auth.route('/tokens', methods=['GET', 'DELETE'])
@authenticate()
def tokens_route():
"""
:return: The list of API tokens created by the logged in user.
Note that this endpoint is only accessible by authenticated users
and it won't return the clear-text token values, as those aren't
stored in the database anyway.
"""
if request.method == 'GET':
return _tokens_get()
if request.method == 'DELETE':
return _tokens_delete()
return UserAuthStatus.INVALID_METHOD.to_response()
# vim:sw=4:ts=4:et:

View file

@ -14,9 +14,27 @@ __routes__ = [
@index.route('/') @index.route('/')
@authenticate() @authenticate()
def index(): def index_route():
"""Route to the main web panel""" """Route to the main web panel"""
return render_template('index.html', utils=HttpUtils) return render_template('index.html', utils=HttpUtils)
@index.route('/login', methods=['GET'])
def login_route():
"""
Login GET route. It simply renders the index template, which will
redirect to the login page if the user is not authenticated.
"""
return render_template('index.html', utils=HttpUtils)
@index.route('/register', methods=['GET'])
def register_route():
"""
Register GET route. It simply renders the index template, which will
redirect to the registration page if no users are present.
"""
return render_template('index.html', utils=HttpUtils)
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -1,55 +0,0 @@
import datetime
import re
from flask import Blueprint, request, redirect, render_template, make_response
from platypush.backend.http.app import template_folder
from platypush.backend.http.utils import HttpUtils
from platypush.user import UserManager
login = Blueprint('login', __name__, template_folder=template_folder)
# Declare routes list
__routes__ = [
login,
]
@login.route('/login', methods=['GET', 'POST'])
def login():
""" Login page """
user_manager = UserManager()
session_token = request.cookies.get('session_token')
redirect_page = request.args.get('redirect')
if not redirect_page:
redirect_page = request.headers.get('Referer', '/')
if re.search('(^https?://[^/]+)?/login[^?#]?', redirect_page):
# Prevent redirect loop
redirect_page = '/'
if session_token:
user, session = user_manager.authenticate_user_session(session_token)
if user:
return redirect(redirect_page, 302) # lgtm [py/url-redirection]
if request.form:
username = request.form.get('username')
password = request.form.get('password')
remember = request.form.get('remember')
expires = datetime.datetime.utcnow() + datetime.timedelta(days=365) \
if remember else None
session = user_manager.create_user_session(username=username, password=password,
expires_at=expires)
if session:
redirect_target = redirect(redirect_page, 302) # lgtm [py/url-redirection]
response = make_response(redirect_target)
response.set_cookie('session_token', session.session_token, expires=expires)
return response
return render_template('index.html', utils=HttpUtils)
# vim:sw=4:ts=4:et:

View file

@ -12,7 +12,7 @@ __routes__ = [
@logout.route('/logout', methods=['GET', 'POST']) @logout.route('/logout', methods=['GET', 'POST'])
def logout(): def logout_route():
"""Logout page""" """Logout page"""
user_manager = UserManager() user_manager = UserManager()
redirect_page = request.args.get( redirect_page = request.args.get(
@ -23,7 +23,7 @@ def logout():
if not session_token: if not session_token:
abort(417, 'Not logged in') abort(417, 'Not logged in')
user, _ = user_manager.authenticate_user_session(session_token) user, _ = user_manager.authenticate_user_session(session_token)[:2]
if not user: if not user:
abort(403, 'Invalid session token') abort(403, 'Invalid session token')

View file

@ -0,0 +1,220 @@
from typing import List, Optional
from flask import Blueprint, jsonify, request
from platypush.backend.http.app import template_folder
from platypush.backend.http.app.utils import UserAuthStatus, authenticate
from platypush.backend.http.utils import HttpUtils
from platypush.exceptions.user import (
InvalidCredentialsException,
InvalidOtpCodeException,
UserException,
)
from platypush.config import Config
from platypush.context import get_plugin
from platypush.user import UserManager
otp = Blueprint('otp', __name__, template_folder=template_folder)
# Declare routes list
__routes__ = [
otp,
]
def _get_otp_and_qrcode():
otp = get_plugin('otp') # pylint: disable=redefined-outer-name
qrcode = get_plugin('qrcode')
assert (
otp and qrcode
), 'The otp and/or qrcode plugins are not available in your installation'
return otp, qrcode
def _get_username():
user = HttpUtils.current_user()
if not user:
raise InvalidCredentialsException('Invalid user session')
return str(user.username)
def _get_otp_uri_and_qrcode(username: str, otp_secret: Optional[str] = None):
if not otp_secret:
return None, None
otp, qrcode = _get_otp_and_qrcode() # pylint: disable=redefined-outer-name
otp_uri = (
otp.provision_time_otp(
name=username,
secret=otp_secret,
issuer=f'platypush@{Config.get_device_id()}',
).output
if otp_secret
else None
)
otp_qrcode = (
qrcode.generate(content=otp_uri, format='png').output.get('data')
if otp_uri
else None
)
return otp_uri, otp_qrcode
def _verify_code(code: str, otp_secret: str) -> bool:
otp, _ = _get_otp_and_qrcode() # pylint: disable=redefined-outer-name
return otp.verify_time_otp(otp=code, secret=otp_secret).output
def _dump_response(
username: str,
otp_secret: Optional[str] = None,
backup_codes: Optional[List[str]] = None,
):
otp_uri, otp_qrcode = _get_otp_uri_and_qrcode(username, otp_secret)
return jsonify(
{
'username': username,
'otp_secret': otp_secret,
'otp_uri': otp_uri,
'qrcode': otp_qrcode,
'backup_codes': backup_codes or [],
}
)
def _get_otp():
username = _get_username()
user_manager = UserManager()
otp_secret = user_manager.get_otp_secret(username)
return _dump_response(
username=username,
otp_secret=otp_secret,
)
def _authenticate_user(username: str, password: Optional[str]):
assert password, 'The password field is required when setting up OTP'
user, auth_status = UserManager().authenticate_user( # type: ignore
username, password, skip_2fa=True, with_status=True
)
if not user:
raise InvalidCredentialsException(auth_status.value[2])
def _post_otp():
body = request.json
assert body, 'Invalid request body'
username = _get_username()
dry_run = body.get('dry_run', False)
otp_secret = body.get('otp_secret')
if not dry_run:
_authenticate_user(username, body.get('password'))
if otp_secret:
code = body.get('code')
assert code, 'The code field is required when setting up OTP'
if not _verify_code(code, otp_secret):
raise InvalidOtpCodeException()
user_manager = UserManager()
user_otp, backup_codes = user_manager.enable_otp(
username=username,
otp_secret=otp_secret,
dry_run=dry_run,
)
return _dump_response(
username=username,
otp_secret=str(user_otp.otp_secret),
backup_codes=backup_codes,
)
def _delete_otp():
body = request.json
assert body, 'Invalid request body'
username = _get_username()
_authenticate_user(username, body.get('password'))
user_manager = UserManager()
user_manager.disable_otp(username)
return jsonify({'status': 'ok'})
@otp.route('/otp/config', methods=['GET', 'POST', 'DELETE'])
@authenticate()
def otp_route():
"""
:return: The user's current MFA/OTP configuration:
.. code-block:: json
{
"username": "testuser",
"otp_secret": "JBSA6ZUZ5DPEK7YV",
"otp_uri": "otpauth://totp/testuser?secret=JBSA6ZUZ5DPEK7YV&issuer=platypush@localhost",
"qrcode": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAYAAACtWK6eAAABwklEQVR4nO3dMW7CQBAF0",
"backup_codes": [
"1A2B3C4D5E",
"6F7G8H9I0J",
"KLMNOPQRST",
"UVWXYZ1234",
"567890ABCD",
"EFGHIJKLMN",
"OPQRSTUVWX",
"YZ12345678",
"90ABCDEF12",
"34567890AB"
]
}
"""
try:
if request.method.lower() == 'get':
return _get_otp()
if request.method.lower() == 'post':
return _post_otp()
if request.method.lower() == 'delete':
return _delete_otp()
return jsonify({'error': 'Method not allowed'}), 405
except AssertionError as e:
return jsonify({'error': str(e)}), 400
except InvalidCredentialsException:
return UserAuthStatus.INVALID_CREDENTIALS.to_response()
except InvalidOtpCodeException:
return UserAuthStatus.INVALID_OTP_CODE.to_response()
except UserException as e:
return jsonify({'error': e.__class__.__name__, 'message': str(e)}), 401
except Exception as e:
HttpUtils.log.error(f'Error while processing OTP request: {e}', exc_info=True)
return jsonify({'error': str(e)}), 500
@otp.route('/otp/refresh-codes', methods=['POST'])
def refresh_codes():
"""
:return: A new set of backup codes for the user.
"""
username = _get_username()
user_manager = UserManager()
otp_secret = user_manager.get_otp_secret(username)
if not otp_secret:
return jsonify({'error': 'OTP not configured for the user'}), 400
backup_codes = user_manager.refresh_user_backup_codes(username)
return jsonify({'backup_codes': backup_codes})
# vim:sw=4:ts=4:et:

View file

@ -1,4 +1,7 @@
from flask import Blueprint, jsonify, send_from_directory from typing import Optional
from urllib.parse import urlparse
from flask import Blueprint, jsonify, request, send_from_directory
from platypush.config import Config from platypush.config import Config
from platypush.backend.http.app import template_folder from platypush.backend.http.app import template_folder
@ -11,13 +14,37 @@ __routes__ = [
] ]
def _get_plugin(url: Optional[str] = None) -> Optional[str]:
if not url:
return None
path = urlparse(url).path.lstrip('/').split('/')
if len(path) > 1 and path[0] == 'plugin':
return path[1]
return None
@pwa.route('/manifest.json', methods=['GET']) @pwa.route('/manifest.json', methods=['GET'])
def manifest_json(): def manifest_json():
"""Generated manifest file for the PWA""" """Generated manifest file for the PWA"""
device_id = Config.get_device_id()
referer = request.headers.get('Referer')
plugin = _get_plugin(referer)
start_url = '/'
name = f'Platypush @ {device_id}'
short_name = device_id
if plugin:
start_url = f'/plugin/{plugin}'
name = f'{plugin} @ {device_id}'
short_name = plugin
return jsonify( return jsonify(
{ {
"name": f'Platypush @ {Config.get("device_id")}', "name": name,
"short_name": Config.get('device_id'), "short_name": short_name,
"icons": [ "icons": [
{ {
"src": "/img/icons/favicon-16x16.png", "src": "/img/icons/favicon-16x16.png",
@ -94,9 +121,9 @@ def manifest_json():
], ],
"gcm_sender_id": "", "gcm_sender_id": "",
"gcm_user_visible_only": True, "gcm_user_visible_only": True,
"start_url": "/", "start_url": start_url,
"permissions": ["gcm"], "permissions": ["gcm"],
"orientation": "portrait", "orientation": "any",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#ffffff", "background_color": "#ffffff",

View file

@ -1,62 +0,0 @@
import datetime
import re
from flask import Blueprint, request, redirect, render_template, make_response, abort
from platypush.backend.http.app import template_folder
from platypush.backend.http.utils import HttpUtils
from platypush.user import UserManager
register = Blueprint('register', __name__, template_folder=template_folder)
# Declare routes list
__routes__ = [
register,
]
@register.route('/register', methods=['GET', 'POST'])
def register():
""" Registration page """
user_manager = UserManager()
redirect_page = request.args.get('redirect')
if not redirect_page:
redirect_page = request.headers.get('Referer', '/')
if re.search('(^https?://[^/]+)?/register[^?#]?', redirect_page):
# Prevent redirect loop
redirect_page = '/'
session_token = request.cookies.get('session_token')
if session_token:
user, session = user_manager.authenticate_user_session(session_token)
if user:
return redirect(redirect_page, 302) # lgtm [py/url-redirection]
if user_manager.get_user_count() > 0:
return redirect('/login?redirect=' + redirect_page, 302) # lgtm [py/url-redirection]
if request.form:
username = request.form.get('username')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
remember = request.form.get('remember')
if password == confirm_password:
user_manager.create_user(username=username, password=password)
session = user_manager.create_user_session(username=username, password=password,
expires_at=datetime.datetime.utcnow() + datetime.timedelta(days=1)
if not remember else None)
if session:
redirect_target = redirect(redirect_page, 302) # lgtm [py/url-redirection]
response = make_response(redirect_target)
response.set_cookie('session_token', session.session_token)
return response
else:
abort(400, 'Password mismatch')
return render_template('index.html', utils=HttpUtils)
# vim:sw=4:ts=4:et:

View file

@ -7,7 +7,7 @@ from typing import Optional
from tornado.web import RequestHandler, stream_request_body from tornado.web import RequestHandler, stream_request_body
from platypush.backend.http.app.utils import logger from platypush.backend.http.app.utils import logger
from platypush.backend.http.app.utils.auth import AuthStatus, get_auth_status from platypush.backend.http.app.utils.auth import UserAuthStatus, get_auth_status
from ..mixins import PubSubMixin from ..mixins import PubSubMixin
@ -29,8 +29,9 @@ class StreamingRoute(RequestHandler, PubSubMixin, ABC):
""" """
if self.auth_required: if self.auth_required:
auth_status = get_auth_status(self.request) auth_status = get_auth_status(self.request)
if auth_status != AuthStatus.OK: if auth_status != UserAuthStatus.OK:
self.send_error(auth_status.value.code, error=auth_status.value.message) self.send_error(auth_status.value.code, error=auth_status.value.message)
self.finish()
return return
self.logger.info( self.logger.info(
@ -42,6 +43,7 @@ class StreamingRoute(RequestHandler, PubSubMixin, ABC):
Make sure that errors are always returned in JSON format. Make sure that errors are always returned in JSON format.
""" """
self.set_header("Content-Type", "application/json") self.set_header("Content-Type", "application/json")
self.set_status(status_code)
self.finish( self.finish(
json.dumps( json.dumps(
{"status": status_code, "error": error or responses.get(status_code)} {"status": status_code, "error": error or responses.get(status_code)}

View file

@ -0,0 +1,205 @@
import os
import pathlib
from contextlib import contextmanager
from datetime import datetime as dt
from typing import IO, Optional, Tuple
from tornado.web import stream_request_body
from platypush.utils import get_mime_type
from .. import StreamingRoute
@stream_request_body
class FileRoute(StreamingRoute):
"""
Generic route to read the content of a file on the server.
"""
BUFSIZE = 1024
_bytes_written = 0
_out_f: Optional[IO[bytes]] = None
@classmethod
def path(cls) -> str:
"""
Route: GET /file?path=<path>[&download]
"""
return r"^/file$"
@property
def download(self) -> bool:
return 'download' in self.request.arguments
@property
def file_path(self) -> str:
return os.path.expanduser(
self.request.arguments.get('path', [b''])[0].decode('utf-8')
)
@property
def file_size(self) -> int:
return os.path.getsize(self.file_path)
@property
def _content_length(self) -> int:
return int(self.request.headers.get('Content-Length', 0))
@property
def range(self) -> Tuple[Optional[int], Optional[int]]:
range_hdr = self.request.headers.get('Range')
if not range_hdr:
return None, None
start, end = range_hdr.split('=')[-1].split('-')
start = int(start) if start else 0
end = int(end) if end else self.file_size - 1
return start, end
def set_headers(self):
self.set_header('Content-Length', str(os.path.getsize(self.file_path)))
self.set_header(
'Content-Type', get_mime_type(self.file_path) or 'application/octet-stream'
)
self.set_header('Accept-Ranges', 'bytes')
self.set_header(
'Last-Modified',
dt.fromtimestamp(os.path.getmtime(self.file_path)).strftime(
'%a, %d %b %Y %H:%M:%S GMT'
),
)
if self.download:
self.set_header(
'Content-Disposition',
f'attachment; filename="{os.path.basename(self.file_path)}"',
)
if self.range[0] is not None:
start, end = self.range
self.set_header(
'Content-Range',
f'bytes {start}-{end}/{self.file_size}',
)
self.set_status(206)
@contextmanager
def _serve(self):
path = self.file_path
if not path:
self.write_error(400, 'Missing path argument')
return
self.logger.debug('Received file read request for %r', path)
try:
with open(path, 'rb') as f:
self.set_headers()
yield f
except FileNotFoundError:
self.write_error(404, 'File not found')
yield
return
except PermissionError:
self.write_error(403, 'Permission denied')
yield
return
except Exception as e:
self.write_error(500, str(e))
yield
return
self.finish()
def on_finish(self) -> None:
if self._out_f:
try:
if not (self._out_f and self._out_f.closed):
self._out_f.close()
except Exception as e:
self.logger.warning('Error while closing the output file: %s', e)
self._out_f = None
return super().on_finish()
def _validate_upload(self, force: bool = False) -> bool:
if not self.file_path:
self.write_error(400, 'Missing path argument')
return False
if not self._out_f:
if not force and os.path.exists(self.file_path):
self.write_error(409, f'{self.file_path} already exists')
return False
self._bytes_written = 0
dir_path = os.path.dirname(self.file_path)
try:
pathlib.Path(dir_path).mkdir(parents=True, exist_ok=True)
self._out_f = open( # pylint: disable=consider-using-with
self.file_path, 'wb'
)
except PermissionError:
self.write_error(403, 'Permission denied')
return False
return True
def finish(self, *args, **kwargs): # type: ignore
try:
return super().finish(*args, **kwargs)
except Exception as e:
self.logger.warning('Error while finishing the request: %s', e)
def data_received(self, chunk: bytes):
# Ignore unless we're in POST/PUT mode
if self.request.method not in ('POST', 'PUT'):
return
force = self.request.method == 'PUT'
if not self._validate_upload(force=force):
self.finish()
return
if not chunk:
self.logger.debug('Received EOF from client')
self.finish()
return
assert self._out_f
self._out_f.write(chunk)
self._out_f.flush()
self._bytes_written += len(chunk)
self.logger.debug(
'Written chunk of size %d to %s, progress: %d/%d',
len(chunk),
self.file_path,
self._bytes_written,
self._content_length,
)
self.flush()
def get(self) -> None:
with self._serve() as f:
if f:
while True:
chunk = f.read(self.BUFSIZE)
if not chunk:
break
self.write(chunk)
self.flush()
def head(self) -> None:
with self._serve():
pass
def post(self) -> None:
self.logger.info('Receiving file POST upload request for %r', self.file_path)
def put(self) -> None:
self.logger.info('Receiving file PUT upload request for %r', self.file_path)

View file

@ -3,7 +3,9 @@ from typing import Optional
from platypush.backend.http.app.utils import logger, send_request from platypush.backend.http.app.utils import logger, send_request
from platypush.backend.http.media.handlers import MediaHandler from platypush.backend.http.media.handlers import MediaHandler
from ._registry import load_media_map, save_media_map from ._registry import clear_media_map, load_media_map, save_media_map
_init = False
def get_media_url(media_id: str) -> str: def get_media_url(media_id: str) -> str:
@ -17,6 +19,12 @@ def register_media(source: str, subtitles: Optional[str] = None) -> MediaHandler
""" """
Registers a media file and returns its associated media handler. Registers a media file and returns its associated media handler.
""" """
global _init
if not _init:
clear_media_map()
_init = True
media_id = MediaHandler.get_media_id(source) media_id = MediaHandler.get_media_id(source)
media_url = get_media_url(media_id) media_url = get_media_url(media_id)
media_map = load_media_map() media_map = load_media_map()

View file

@ -25,10 +25,15 @@ def load_media_map() -> MediaMap:
logger().warning('Could not load media map: %s', e) logger().warning('Could not load media map: %s', e)
return {} return {}
return { parsed_map = {}
media_id: MediaHandler.build(**media_info) for media_id, media_info in media_map.items():
for media_id, media_info in media_map.items() try:
} parsed_map[media_id] = MediaHandler.build(**media_info)
except Exception as e:
logger().debug('Could not load media %s: %s', media_id, e)
continue
return parsed_map
def save_media_map(new_map: MediaMap): def save_media_map(new_map: MediaMap):
@ -38,3 +43,12 @@ def save_media_map(new_map: MediaMap):
with media_map_lock: with media_map_lock:
redis = get_redis() redis = get_redis()
redis.mset({MEDIA_MAP_VAR: json.dumps(new_map, cls=Message.Encoder)}) redis.mset({MEDIA_MAP_VAR: json.dumps(new_map, cls=Message.Encoder)})
def clear_media_map():
"""
Clears the media map from the server.
"""
with media_map_lock:
redis = get_redis()
redis.delete(MEDIA_MAP_VAR)

View file

@ -17,7 +17,7 @@ class MediaStreamRoute(StreamingRoute):
Route for media streams. Route for media streams.
""" """
SUPPORTED_METHODS = ['GET', 'PUT', 'DELETE'] SUPPORTED_METHODS = ['GET', 'HEAD', 'PUT', 'DELETE']
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@ -50,6 +50,23 @@ class MediaStreamRoute(StreamingRoute):
except Exception as e: except Exception as e:
self._on_error(e) self._on_error(e)
def head(self, media_id: Optional[str] = None):
"""
Streams a media resource by ID.
"""
if not media_id:
self.finish()
return
# Strip the extension
media_id = '.'.join(media_id.split('.')[:-1])
try:
self.stream_media(media_id, head=True)
except Exception as e:
self._on_error(e)
def put(self, *_, **__): def put(self, *_, **__):
""" """
The `PUT` route is used to prepare a new media resource for streaming. The `PUT` route is used to prepare a new media resource for streaming.
@ -93,10 +110,10 @@ class MediaStreamRoute(StreamingRoute):
""" """
Returns the list of registered media resources. Returns the list of registered media resources.
""" """
self.add_header('Content-Type', 'application/json') self.set_header('Content-Type', 'application/json')
self.finish(json.dumps([dict(media) for media in load_media_map().values()])) self.finish(json.dumps([dict(media) for media in load_media_map().values()]))
def stream_media(self, media_id: str): def stream_media(self, media_id: str, head: bool = False):
""" """
Route to stream a media file given its ID. Route to stream a media file given its ID.
""" """
@ -107,11 +124,11 @@ class MediaStreamRoute(StreamingRoute):
range_hdr = self.request.headers.get('Range') range_hdr = self.request.headers.get('Range')
content_length = media_hndl.content_length content_length = media_hndl.content_length
self.add_header('Accept-Ranges', 'bytes') self.set_header('Accept-Ranges', 'bytes')
self.add_header('Content-Type', media_hndl.mime_type) self.set_header('Content-Type', media_hndl.mime_type)
if 'download' in self.request.arguments: if 'download' in self.request.arguments:
self.add_header( self.set_header(
'Content-Disposition', 'Content-Disposition',
'attachment' 'attachment'
+ ('; filename="{media_hndl.filename}"' if media_hndl.filename else ''), + ('; filename="{media_hndl.filename}"' if media_hndl.filename else ''),
@ -129,7 +146,7 @@ class MediaStreamRoute(StreamingRoute):
content_length = to_bytes - from_bytes content_length = to_bytes - from_bytes
self.set_status(206) self.set_status(206)
self.add_header( self.set_header(
'Content-Range', 'Content-Range',
f'bytes {from_bytes}-{to_bytes}/{media_hndl.content_length}', f'bytes {from_bytes}-{to_bytes}/{media_hndl.content_length}',
) )
@ -137,7 +154,13 @@ class MediaStreamRoute(StreamingRoute):
from_bytes = 0 from_bytes = 0
to_bytes = STREAMING_BLOCK_SIZE to_bytes = STREAMING_BLOCK_SIZE
self.add_header('Content-Length', str(content_length)) self.set_header('Content-Length', str(content_length))
if head:
self.flush()
self.finish()
return
for chunk in media_hndl.get_data( for chunk in media_hndl.get_data(
from_bytes=from_bytes, from_bytes=from_bytes,
to_bytes=to_bytes, to_bytes=to_bytes,

View file

@ -1,7 +1,9 @@
from .auth import ( from .auth import (
UserAuthStatus,
authenticate, authenticate,
authenticate_token, authenticate_token,
authenticate_user_pass, authenticate_user_pass,
current_user,
get_auth_status, get_auth_status,
) )
from .bus import bus, send_message, send_request from .bus import bus, send_message, send_request
@ -17,10 +19,12 @@ from .streaming import get_streaming_routes
from .ws import get_ws_routes from .ws import get_ws_routes
__all__ = [ __all__ = [
'UserAuthStatus',
'authenticate', 'authenticate',
'authenticate_token', 'authenticate_token',
'authenticate_user_pass', 'authenticate_user_pass',
'bus', 'bus',
'current_user',
'get_auth_status', 'get_auth_status',
'get_http_port', 'get_http_port',
'get_ip_or_hostname', 'get_ip_or_hostname',

View file

@ -1,15 +1,15 @@
import base64 import base64
from functools import wraps from functools import wraps
from typing import Optional from typing import Optional, Union
from flask import request, redirect, jsonify from flask import request, redirect
from flask.wrappers import Response from flask.wrappers import Response
from platypush.config import Config from platypush.config import Config
from platypush.user import UserManager from platypush.user import User, UserManager
from ..logger import logger from ..logger import logger
from .status import AuthStatus from .status import UserAuthStatus
user_manager = UserManager() user_manager = UserManager()
@ -41,8 +41,8 @@ def get_cookie(req, name: str) -> Optional[str]:
return cookie.value return cookie.value
def authenticate_token(req): def authenticate_token(req) -> Optional[User]:
token = Config.get('token') global_token = Config.get('user.global_token')
user_token = None user_token = None
if 'X-Token' in req.headers: if 'X-Token' in req.headers:
@ -55,14 +55,27 @@ def authenticate_token(req):
user_token = get_arg(req, 'token') user_token = get_arg(req, 'token')
if not user_token: if not user_token:
return False return None
try: try:
user_manager.validate_jwt_token(user_token) # Stantard API token authentication
return True return user_manager.validate_api_token(user_token)
except Exception as e: except Exception as e:
logger().debug(str(e)) try:
return bool(token and user_token == token) # Legacy JWT token authentication
return user_manager.validate_jwt_token(user_token)
except Exception as ee:
logger().debug(
'Invalid token. API token error: %s, JWT token error: %s', e, ee
)
# Legacy global token authentication.
# The global token should be specified in the configuration file,
# as a root parameter named `token`.
if bool(global_token and user_token == global_token):
return User(username='__token__', user_id=1)
logger().info(e)
def authenticate_user_pass(req): def authenticate_user_pass(req):
@ -91,7 +104,7 @@ def authenticate_user_pass(req):
return user_manager.authenticate_user(username, password) return user_manager.authenticate_user(username, password)
def authenticate_session(req): def authenticate_session(req) -> Optional[User]:
user = None user = None
# Check the X-Session-Token header # Check the X-Session-Token header
@ -106,9 +119,9 @@ def authenticate_session(req):
user_session_token = get_cookie(req, 'session_token') user_session_token = get_cookie(req, 'session_token')
if user_session_token: if user_session_token:
user, _ = user_manager.authenticate_user_session(user_session_token) user, _ = user_manager.authenticate_user_session(user_session_token)[:2]
return user is not None return user
def authenticate( def authenticate(
@ -128,18 +141,18 @@ def authenticate(
skip_auth_methods=skip_auth_methods, skip_auth_methods=skip_auth_methods,
) )
if auth_status == AuthStatus.OK: if auth_status == UserAuthStatus.OK:
return f(*args, **kwargs) return f(*args, **kwargs)
if json: if json:
return jsonify(auth_status.to_dict()), auth_status.value.code return auth_status.to_response()
if auth_status == AuthStatus.NO_USERS: if auth_status == UserAuthStatus.REGISTRATION_REQUIRED:
return redirect( return redirect(
f'/register?redirect={redirect_page or request.url}', 307 f'/register?redirect={redirect_page or request.url}', 307
) )
if auth_status == AuthStatus.UNAUTHORIZED: if auth_status == UserAuthStatus.INVALID_CREDENTIALS:
return redirect(f'/login?redirect={redirect_page or request.url}', 307) return redirect(f'/login?redirect={redirect_page or request.url}', 307)
return Response( return Response(
@ -154,43 +167,67 @@ def authenticate(
# pylint: disable=too-many-return-statements # pylint: disable=too-many-return-statements
def get_auth_status(req, skip_auth_methods=None) -> AuthStatus: def get_current_user_or_auth_status(
req, skip_auth_methods=None
) -> Union[User, UserAuthStatus]:
""" """
Check against the available authentication methods (except those listed in Returns the current user if authenticated, and the authentication status if
``skip_auth_methods``) if the user is properly authenticated. ``with_status`` is True.
""" """
n_users = user_manager.get_user_count() n_users = user_manager.get_user_count()
skip_methods = skip_auth_methods or [] skip_methods = skip_auth_methods or []
# User/pass HTTP authentication # User/pass HTTP authentication
http_auth_ok = True http_auth_ok = True
if n_users > 0 and 'http' not in skip_methods: if n_users > 0 and 'http' not in skip_methods:
http_auth_ok = authenticate_user_pass(req) response = authenticate_user_pass(req)
if http_auth_ok: if response:
return AuthStatus.OK user = response[0] if isinstance(response, tuple) else response
if user:
return user
# Token-based authentication # Token-based authentication
token_auth_ok = True token_auth_ok = True
if 'token' not in skip_methods: if 'token' not in skip_methods:
token_auth_ok = authenticate_token(req) user = authenticate_token(req)
if token_auth_ok: if user:
return AuthStatus.OK return user
# Session token based authentication # Session token based authentication
session_auth_ok = True session_auth_ok = True
if n_users > 0 and 'session' not in skip_methods: if n_users > 0 and 'session' not in skip_methods:
return AuthStatus.OK if authenticate_session(req) else AuthStatus.UNAUTHORIZED user = authenticate_session(req)
if user:
return user
return UserAuthStatus.INVALID_CREDENTIALS
# At least a user should be created before accessing an authenticated resource # At least a user should be created before accessing an authenticated resource
if n_users == 0 and 'session' not in skip_methods: if n_users == 0 and 'session' not in skip_methods:
return AuthStatus.NO_USERS return UserAuthStatus.REGISTRATION_REQUIRED
if ( # pylint: disable=too-many-boolean-expressions if ( # pylint: disable=too-many-boolean-expressions
('http' not in skip_methods and http_auth_ok) ('http' not in skip_methods and http_auth_ok)
or ('token' not in skip_methods and token_auth_ok) or ('token' not in skip_methods and token_auth_ok)
or ('session' not in skip_methods and session_auth_ok) or ('session' not in skip_methods and session_auth_ok)
): ):
return AuthStatus.OK return UserAuthStatus.OK
return AuthStatus.UNAUTHORIZED return UserAuthStatus.INVALID_CREDENTIALS
def get_auth_status(req, skip_auth_methods=None) -> UserAuthStatus:
"""
Check against the available authentication methods (except those listed in
``skip_auth_methods``) if the user is properly authenticated.
"""
ret = get_current_user_or_auth_status(req, skip_auth_methods=skip_auth_methods)
return UserAuthStatus.OK if isinstance(ret, User) else ret
def current_user() -> Optional[User]:
"""
Returns the current user if authenticated.
"""
ret = get_current_user_or_auth_status(request)
return ret if isinstance(ret, User) else None

View file

@ -1,21 +1,76 @@
from collections import namedtuple from collections import namedtuple
from enum import Enum from enum import Enum
from flask import jsonify
StatusValue = namedtuple('StatusValue', ['code', 'message']) from platypush.user import AuthenticationStatus
StatusValue = namedtuple('StatusValue', ['code', 'error', 'message'])
class AuthStatus(Enum): class UserAuthStatus(Enum):
""" """
Models the status of the authentication. Models the status of the authentication.
""" """
OK = StatusValue(200, 'OK') OK = StatusValue(200, AuthenticationStatus.OK, 'OK')
UNAUTHORIZED = StatusValue(401, 'Unauthorized') INVALID_AUTH_TYPE = StatusValue(
NO_USERS = StatusValue(412, 'Please create a user first') 400, AuthenticationStatus.INVALID_AUTH_TYPE, 'Invalid authentication type'
)
INVALID_CREDENTIALS = StatusValue(
401, AuthenticationStatus.INVALID_CREDENTIALS, 'Invalid credentials'
)
INVALID_JWT_TOKEN = StatusValue(
401, AuthenticationStatus.INVALID_JWT_TOKEN, 'Invalid JWT token'
)
INVALID_OTP_CODE = StatusValue(
401, AuthenticationStatus.INVALID_OTP_CODE, 'Invalid OTP code'
)
INVALID_METHOD = StatusValue(
405, AuthenticationStatus.INVALID_METHOD, 'Invalid method'
)
MISSING_OTP_CODE = StatusValue(
401, AuthenticationStatus.MISSING_OTP_CODE, 'Missing OTP code'
)
MISSING_PASSWORD = StatusValue(
400, AuthenticationStatus.MISSING_PASSWORD, 'Missing password'
)
INVALID_SESSION = StatusValue(
401, AuthenticationStatus.INVALID_CREDENTIALS, 'Invalid session'
)
INVALID_TOKEN = StatusValue(
400, AuthenticationStatus.INVALID_JWT_TOKEN, 'Invalid token'
)
MISSING_USERNAME = StatusValue(
400, AuthenticationStatus.MISSING_USERNAME, 'Missing username'
)
PASSWORD_MISMATCH = StatusValue(
400, AuthenticationStatus.PASSWORD_MISMATCH, 'Password mismatch'
)
REGISTRATION_DISABLED = StatusValue(
401, AuthenticationStatus.REGISTRATION_DISABLED, 'Registrations are disabled'
)
REGISTRATION_REQUIRED = StatusValue(
412, AuthenticationStatus.REGISTRATION_REQUIRED, 'Please create a user first'
)
UNKNOWN_ERROR = StatusValue(
500, AuthenticationStatus.UNKNOWN_ERROR, 'Unknown error'
)
def to_dict(self): def to_dict(self):
return { return {
'code': self.value[0], 'code': self.value[0],
'message': self.value[1], 'error': self.value[1].name,
'message': self.value[2],
} }
def to_response(self):
return jsonify(self.to_dict()), self.value[0]
@staticmethod
def by_status(status: AuthenticationStatus):
for auth_status in UserAuthStatus:
if auth_status.value[1] == status:
return auth_status
return None

View file

@ -1,24 +1,57 @@
from multiprocessing import Lock
from platypush.bus.redis import RedisBus from platypush.bus.redis import RedisBus
from platypush.context import get_bus
from platypush.config import Config from platypush.config import Config
from platypush.context import get_backend
from platypush.message import Message from platypush.message import Message
from platypush.message.request import Request from platypush.message.request import Request
from platypush.utils import get_redis_conf, get_message_response from platypush.utils import get_message_response
from .logger import logger from .logger import logger
_bus = None
class BusWrapper: # pylint: disable=too-few-public-methods
"""
Lazy singleton wrapper for the bus object.
"""
def __init__(self):
self._redis_queue = None
self._bus = None
self._bus_lock = Lock()
@property
def bus(self) -> RedisBus:
"""
Lazy getter/initializer for the bus object.
"""
with self._bus_lock:
if not self._bus:
self._bus = get_bus()
bus_: RedisBus = self._bus # type: ignore
return bus_
def post(self, msg):
"""
Send a message to the bus.
:param msg: The message to send.
"""
try:
self.bus.post(msg)
except Exception as e:
logger().exception(e)
_bus = BusWrapper()
def bus(): def bus():
""" """
Lazy getter/initializer for the bus object. Lazy getter/initializer for the bus object.
""" """
global _bus # pylint: disable=global-statement return _bus.bus
if _bus is None:
redis_queue = get_backend('http').bus.redis_queue # type: ignore
_bus = RedisBus(**get_redis_conf(), redis_queue=redis_queue)
return _bus
def send_message(msg, wait_for_response=True): def send_message(msg, wait_for_response=True):

View file

@ -5,7 +5,7 @@ from threading import Thread
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop
from tornado.websocket import WebSocketHandler from tornado.websocket import WebSocketHandler
from platypush.backend.http.app.utils.auth import AuthStatus, get_auth_status from platypush.backend.http.app.utils.auth import UserAuthStatus, get_auth_status
from ..mixins import MessageType, PubSubMixin from ..mixins import MessageType, PubSubMixin
@ -25,9 +25,9 @@ class WSRoute(WebSocketHandler, Thread, PubSubMixin, ABC):
def open(self, *_, **__): def open(self, *_, **__):
auth_status = get_auth_status(self.request) auth_status = get_auth_status(self.request)
if auth_status != AuthStatus.OK: if auth_status != UserAuthStatus.OK:
self.close(code=1008, reason=auth_status.value.message) # Policy Violation self.close(code=1008, reason=auth_status.value.message) # Policy Violation
return raise ValueError(f'Unauthorized connection: {auth_status.value.message}')
logger.info( logger.info(
'Client %s connected to %s', self.request.remote_ip, self.request.path 'Client %s connected to %s', self.request.remote_ip, self.request.path

View file

@ -0,0 +1,10 @@
{
"manifest": {
"events": {},
"install": {
"pip": []
},
"package": "platypush.backend.http",
"type": "backend"
}
}

View file

@ -1,6 +0,0 @@
manifest:
events: {}
install:
pip: []
package: platypush.backend.http
type: backend

View file

@ -1,7 +1,6 @@
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
import hashlib import hashlib
import logging import logging
import os
from typing import Generator, Optional from typing import Generator, Optional
from platypush.message import JSONAble from platypush.message import JSONAble
@ -57,9 +56,6 @@ class MediaHandler(JSONAble, ABC):
logging.exception(e) logging.exception(e)
errors[hndl_class.__name__] = str(e) errors[hndl_class.__name__] = str(e)
if os.path.exists(source):
source = f'file://{source}'
raise AttributeError( raise AttributeError(
f'The source {source} has no handlers associated. Errors: {errors}' f'The source {source} has no handlers associated. Errors: {errors}'
) )

View file

@ -15,6 +15,9 @@ class FileHandler(MediaHandler):
prefix_handlers = ['file://'] prefix_handlers = ['file://']
def __init__(self, source, *args, **kwargs): def __init__(self, source, *args, **kwargs):
if isinstance(source, str) and os.path.exists(source):
source = f'file://{source}'
super().__init__(source, *args, **kwargs) super().__init__(source, *args, **kwargs)
self.path = os.path.abspath( self.path = os.path.abspath(
@ -33,7 +36,7 @@ class FileHandler(MediaHandler):
), f'{source} is not a valid media file (detected format: {self.mime_type})' ), f'{source} is not a valid media file (detected format: {self.mime_type})'
self.extension = mimetypes.guess_extension(self.mime_type) self.extension = mimetypes.guess_extension(self.mime_type)
if self.url and self.extension: if self.url and self.extension and not self.url.endswith(self.extension):
self.url += self.extension self.url += self.extension
self.content_length = os.path.getsize(self.path) self.content_length = os.path.getsize(self.path)

View file

@ -7,6 +7,7 @@ import re
from platypush.config import Config from platypush.config import Config
from platypush.backend.http.app import template_folder from platypush.backend.http.app import template_folder
from platypush.backend.http.app.utils import current_user
class HttpUtils: class HttpUtils:
@ -130,5 +131,9 @@ class HttpUtils:
path = path[0] if len(path) == 1 else os.path.join(*path) path = path[0] if len(path) == 1 else os.path.join(*path)
return os.path.isfile(path) return os.path.isfile(path)
@staticmethod
def current_user():
return current_user()
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -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.aeea9c55.js"></script><script defer="defer" src="/static/js/app.e71ae2ab.js"></script><link href="/static/css/chunk-vendors.a2412607.css" rel="stylesheet"><link href="/static/css/app.5b1362a4.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.83e191d2.js"></script><script defer="defer" src="/static/js/app.668abf05.js"></script><link href="/static/css/chunk-vendors.d510eff2.css" rel="stylesheet"><link href="/static/css/app.f97a4bca.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

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

Some files were not shown because too many files have changed in this diff Show more