From ce4441cfc542bf50b40d26a0716401c5db58b64f Mon Sep 17 00:00:00 2001 From: Luke Hagar Date: Thu, 26 Oct 2023 21:45:48 -0500 Subject: [PATCH] SDK update generated by liblab --- .devcontainer/devcontainer.json | 12 + .env.example | 1 + .gitignore | 160 ++ LICENSE | 19 + README.md | 2004 ++++++++++++++++- examples/README.md | 33 + examples/install.sh | 5 + examples/install_py3.sh | 5 + examples/sample.py | 10 + install.sh | 3 + pyproject.toml | 21 + requirements.txt | 4 + src/plexsdk/README.md | 2003 ++++++++++++++++ src/plexsdk/__init__.py | 2 + src/plexsdk/hooks/__init__.py | 0 src/plexsdk/hooks/hook.py | 1 + src/plexsdk/models/Download.py | 7 + src/plexsdk/models/Force.py | 7 + .../models/GetAvailableClientsResponse.py | 32 + src/plexsdk/models/GetButlerTasksResponse.py | 62 + src/plexsdk/models/GetDevicesResponse.py | 70 + .../models/GetMyPlexAccountResponse.py | 73 + src/plexsdk/models/GetOnDeckResponse.py | 446 ++++ .../models/GetRecentlyAddedResponse.py | 382 ++++ .../models/GetSearchResultsResponse.py | 392 ++++ .../models/GetServerActivitiesResponse.py | 90 + .../models/GetServerCapabilitiesResponse.py | 250 ++ .../models/GetServerIdentityResponse.py | 41 + src/plexsdk/models/GetServerListResponse.py | 67 + .../models/GetTranscodeSessionsResponse.py | 134 ++ src/plexsdk/models/IncludeDetails.py | 7 + src/plexsdk/models/Level.py | 10 + src/plexsdk/models/MinSize.py | 7 + src/plexsdk/models/OnlyTransient.py | 7 + src/plexsdk/models/PlaylistType.py | 10 + src/plexsdk/models/README.md | 90 + src/plexsdk/models/Scope.py | 8 + src/plexsdk/models/SecurityType.py | 8 + src/plexsdk/models/Skip.py | 7 + src/plexsdk/models/Smart.py | 7 + src/plexsdk/models/State.py | 10 + src/plexsdk/models/TaskName.py | 21 + src/plexsdk/models/Tonight.py | 7 + src/plexsdk/models/Type.py | 10 + src/plexsdk/models/Upscale.py | 7 + src/plexsdk/models/__init__.py | 28 + src/plexsdk/models/base.py | 60 + src/plexsdk/net/__init__.py | 0 src/plexsdk/net/environment.py | 11 + src/plexsdk/net/http_client.py | 280 +++ src/plexsdk/net/http_content_types.py | 42 + src/plexsdk/net/query_serializer.py | 74 + src/plexsdk/net/response.py | 4 + src/plexsdk/net/utils.py | 65 + src/plexsdk/sdk.py | 129 ++ src/plexsdk/services/README.md | 1896 ++++++++++++++++ src/plexsdk/services/__init__.py | 0 src/plexsdk/services/activities.py | 53 + src/plexsdk/services/base.py | 106 + src/plexsdk/services/butler.py | 116 + src/plexsdk/services/hubs.py | 93 + src/plexsdk/services/library.py | 354 +++ src/plexsdk/services/log.py | 75 + src/plexsdk/services/media.py | 79 + src/plexsdk/services/playlists.py | 326 +++ src/plexsdk/services/search.py | 102 + src/plexsdk/services/security.py | 60 + src/plexsdk/services/server.py | 210 ++ src/plexsdk/services/sessions.py | 75 + src/plexsdk/services/updater.py | 84 + src/plexsdk/services/video.py | 266 +++ src/plexsdk/setup.py | 9 + test/__init__.py | 0 test/models/__init__.py | 0 test/models/test_base.py | 14 + test/models/test_get_butler_tasks_response.py | 16 + test/models/test_get_devices_response.py | 16 + .../test_get_my_plex_account_response.py | 16 + test/models/test_get_on_deck_response.py | 16 + .../test_get_recently_added_response.py | 16 + .../test_get_search_results_response.py | 16 + .../test_get_server_activities_response.py | 16 + .../test_get_server_capabilities_response.py | 18 + .../test_get_server_identity_response.py | 16 + test/models/test_get_server_list_response.py | 16 + .../test_get_transcode_sessions_response.py | 16 + test/services/__init__.py | 0 test/services/test_activities.py | 67 + test/services/test_base.py | 14 + test/services/test_butler.py | 139 ++ test/services/test_hubs.py | 67 + test/services/test_library.py | 399 ++++ test/services/test_log.py | 80 + test/services/test_media.py | 98 + test/services/test_playlists.py | 297 +++ test/services/test_search.py | 98 + test/services/test_security.py | 76 + test/services/test_server.py | 175 ++ test/services/test_sessions.py | 115 + test/services/test_updater.py | 71 + test/services/test_video.py | 129 ++ 101 files changed, 13165 insertions(+), 1 deletion(-) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 examples/README.md create mode 100644 examples/install.sh create mode 100644 examples/install_py3.sh create mode 100644 examples/sample.py create mode 100644 install.sh create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100644 src/plexsdk/README.md create mode 100644 src/plexsdk/__init__.py create mode 100644 src/plexsdk/hooks/__init__.py create mode 100644 src/plexsdk/hooks/hook.py create mode 100644 src/plexsdk/models/Download.py create mode 100644 src/plexsdk/models/Force.py create mode 100644 src/plexsdk/models/GetAvailableClientsResponse.py create mode 100644 src/plexsdk/models/GetButlerTasksResponse.py create mode 100644 src/plexsdk/models/GetDevicesResponse.py create mode 100644 src/plexsdk/models/GetMyPlexAccountResponse.py create mode 100644 src/plexsdk/models/GetOnDeckResponse.py create mode 100644 src/plexsdk/models/GetRecentlyAddedResponse.py create mode 100644 src/plexsdk/models/GetSearchResultsResponse.py create mode 100644 src/plexsdk/models/GetServerActivitiesResponse.py create mode 100644 src/plexsdk/models/GetServerCapabilitiesResponse.py create mode 100644 src/plexsdk/models/GetServerIdentityResponse.py create mode 100644 src/plexsdk/models/GetServerListResponse.py create mode 100644 src/plexsdk/models/GetTranscodeSessionsResponse.py create mode 100644 src/plexsdk/models/IncludeDetails.py create mode 100644 src/plexsdk/models/Level.py create mode 100644 src/plexsdk/models/MinSize.py create mode 100644 src/plexsdk/models/OnlyTransient.py create mode 100644 src/plexsdk/models/PlaylistType.py create mode 100644 src/plexsdk/models/README.md create mode 100644 src/plexsdk/models/Scope.py create mode 100644 src/plexsdk/models/SecurityType.py create mode 100644 src/plexsdk/models/Skip.py create mode 100644 src/plexsdk/models/Smart.py create mode 100644 src/plexsdk/models/State.py create mode 100644 src/plexsdk/models/TaskName.py create mode 100644 src/plexsdk/models/Tonight.py create mode 100644 src/plexsdk/models/Type.py create mode 100644 src/plexsdk/models/Upscale.py create mode 100644 src/plexsdk/models/__init__.py create mode 100644 src/plexsdk/models/base.py create mode 100644 src/plexsdk/net/__init__.py create mode 100644 src/plexsdk/net/environment.py create mode 100644 src/plexsdk/net/http_client.py create mode 100644 src/plexsdk/net/http_content_types.py create mode 100644 src/plexsdk/net/query_serializer.py create mode 100644 src/plexsdk/net/response.py create mode 100644 src/plexsdk/net/utils.py create mode 100644 src/plexsdk/sdk.py create mode 100644 src/plexsdk/services/README.md create mode 100644 src/plexsdk/services/__init__.py create mode 100644 src/plexsdk/services/activities.py create mode 100644 src/plexsdk/services/base.py create mode 100644 src/plexsdk/services/butler.py create mode 100644 src/plexsdk/services/hubs.py create mode 100644 src/plexsdk/services/library.py create mode 100644 src/plexsdk/services/log.py create mode 100644 src/plexsdk/services/media.py create mode 100644 src/plexsdk/services/playlists.py create mode 100644 src/plexsdk/services/search.py create mode 100644 src/plexsdk/services/security.py create mode 100644 src/plexsdk/services/server.py create mode 100644 src/plexsdk/services/sessions.py create mode 100644 src/plexsdk/services/updater.py create mode 100644 src/plexsdk/services/video.py create mode 100644 src/plexsdk/setup.py create mode 100644 test/__init__.py create mode 100644 test/models/__init__.py create mode 100644 test/models/test_base.py create mode 100644 test/models/test_get_butler_tasks_response.py create mode 100644 test/models/test_get_devices_response.py create mode 100644 test/models/test_get_my_plex_account_response.py create mode 100644 test/models/test_get_on_deck_response.py create mode 100644 test/models/test_get_recently_added_response.py create mode 100644 test/models/test_get_search_results_response.py create mode 100644 test/models/test_get_server_activities_response.py create mode 100644 test/models/test_get_server_capabilities_response.py create mode 100644 test/models/test_get_server_identity_response.py create mode 100644 test/models/test_get_server_list_response.py create mode 100644 test/models/test_get_transcode_sessions_response.py create mode 100644 test/services/__init__.py create mode 100644 test/services/test_activities.py create mode 100644 test/services/test_base.py create mode 100644 test/services/test_butler.py create mode 100644 test/services/test_hubs.py create mode 100644 test/services/test_library.py create mode 100644 test/services/test_log.py create mode 100644 test/services/test_media.py create mode 100644 test/services/test_playlists.py create mode 100644 test/services/test_search.py create mode 100644 test/services/test_security.py create mode 100644 test/services/test_server.py create mode 100644 test/services/test_sessions.py create mode 100644 test/services/test_updater.py create mode 100644 test/services/test_video.py diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..428b4b4 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,12 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/python +{ + "name": "Python SDK", + "image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye", + "postCreateCommand": "pip3 install --user -r requirements.txt && cd examples && sh install.sh", + "customizations": { + "codespaces":{ + "openFiles": ["examples/sample.py", "examples/README.md"] + } + } +} diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..6caed6e --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +PLEXSDK_API_KEY= diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..cb511c8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2023 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index 78ec173..228d8a7 100644 --- a/README.md +++ b/README.md @@ -1 +1,2003 @@ -# plexpy +# PlexSDK Python SDK 0.0.1 +A Python SDK for PlexSDK. + +An Open API Spec for interacting with Plex.tv and Plex Servers + +- API version: 0.0.1 +- SDK version: 0.0.1 + +## Table of Contents +- [Requirements](#requirements) +- [Installation](#installation) + - [Dependencies](#dependencies) +- [Authentication](#authentication) + - [API Key](#api-key) +- [API Endpoint Services](#api-endpoint-services) +- [API Models](#api-models) +- [Testing](#testing) +- [Configuration](#configuration) +- [Sample Usage](#sample-usage) +- [PlexSDK Services](#plexsdk-services) +- [License](#license) + +## Installation +```bash +pip install plexpy +``` + +### Dependencies + +This SDK uses the following dependencies: +- requests 2.28.1 +- http-exceptions 0.2.10 +- pytest 7.1.2 +- responses 0.21.0 + +## Authentication + +To see whether an endpoint needs a specific type of authentication check the endpoint's documentation. +### API Key +The PlexSDK API uses API keys as a form of authentication. An API key is a unique identifier used to authenticate a user, developer, or a program that is calling the API. You can set the API key when initializing the SDK through the constructor: + +```python +sdk = PlexSDK('YOUR_API_KEY', 'YOUR_API_KEY_HEADER') +``` + +If you omit `YOUR_API_KEY_HEADER`, the SDK default API key header will be `X-Plex-Token`. + +You can also set it for each service individually: + +```python +sdk = PlexSDK() +sdk.server.set_api_key('YOUR_API_KEY', 'YOUR_API_KEY_HEADER') +``` + +## API Endpoint Services + +All URIs are relative to {protocol}://{ip}:{port}. + +Click the service name for a full list of the service methods. + +| Service | +| :------ | +|[Server](./services/README.md#server)| +|[Media](./services/README.md#media)| +|[Activities](./services/README.md#activities)| +|[Butler](./services/README.md#butler)| +|[Hubs](./services/README.md#hubs)| +|[Search](./services/README.md#search)| +|[Library](./services/README.md#library)| +|[Log](./services/README.md#log)| +|[Playlists](./services/README.md#playlists)| +|[Security](./services/README.md#security)| +|[Sessions](./services/README.md#sessions)| +|[Updater](./services/README.md#updater)| +|[Video](./services/README.md#video)| + +## API Models +[A list documenting all API models for this SDK](./models/README.md#plexsdk-models). + +## Testing + +Run unit tests with this command: + +```sh +python -m unittest discover -p "test*.py" +``` + +## Sample Usage + +```py +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK + +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) + +results = sdk.server.get_server_capabilities() + +pprint(vars(results)) +``` + + +# PlexSDK Services +A list of all services and services methods. +- Services + + - [Server](#server) + + - [Media](#media) + + - [Activities](#activities) + + - [Butler](#butler) + + - [Hubs](#hubs) + + - [Search](#search) + + - [Library](#library) + + - [Log](#log) + + - [Playlists](#playlists) + + - [Security](#security) + + - [Sessions](#sessions) + + - [Updater](#updater) + + - [Video](#video) +- [All Methods](#all-methods) + + +## Server + +| Method | Description| +| :-------- | :----------| +| [get_server_capabilities](#get_server_capabilities) | Server Capabilities | +| [get_server_preferences](#get_server_preferences) | Get Server Preferences | +| [get_available_clients](#get_available_clients) | Get Available Clients | +| [get_devices](#get_devices) | Get Devices | +| [get_server_identity](#get_server_identity) | Get Server Identity | +| [get_my_plex_account](#get_my_plex_account) | Get MyPlex Account | +| [get_resized_photo](#get_resized_photo) | Get a Resized Photo | +| [get_server_list](#get_server_list) | Get Server List | + + +## Media + +| Method | Description| +| :-------- | :----------| +| [mark_played](#mark_played) | Mark Media Played | +| [mark_unplayed](#mark_unplayed) | Mark Media Unplayed | +| [update_play_progress](#update_play_progress) | Update Media Play Progress | + + +## Activities + +| Method | Description| +| :-------- | :----------| +| [get_server_activities](#get_server_activities) | Get Server Activities | +| [cancel_server_activities](#cancel_server_activities) | Cancel Server Activities | + + +## Butler + +| Method | Description| +| :-------- | :----------| +| [start_all_tasks](#start_all_tasks) | Start all Butler tasks | +| [get_butler_tasks](#get_butler_tasks) | Get Butler tasks | +| [stop_all_tasks](#stop_all_tasks) | Stop all Butler tasks | +| [start_task](#start_task) | Start a single Butler task | +| [stop_task](#stop_task) | Stop a single Butler task | + + +## Hubs + +| Method | Description| +| :-------- | :----------| +| [get_global_hubs](#get_global_hubs) | Get Global Hubs | +| [get_library_hubs](#get_library_hubs) | Get library specific hubs | + + +## Search + +| Method | Description| +| :-------- | :----------| +| [perform_search](#perform_search) | Perform a search | +| [perform_voice_search](#perform_voice_search) | Perform a voice search | +| [get_search_results](#get_search_results) | Get Search Results | + + +## Library + +| Method | Description| +| :-------- | :----------| +| [get_file_hash](#get_file_hash) | Get Hash Value | +| [get_recently_added](#get_recently_added) | Get Recently Added | +| [get_libraries](#get_libraries) | Get All Libraries | +| [get_library](#get_library) | Get Library Details | +| [delete_library](#delete_library) | Delete Library Section | +| [get_library_items](#get_library_items) | Get Library Items | +| [refresh_library](#refresh_library) | Refresh Library | +| [get_latest_library_items](#get_latest_library_items) | Get Latest Library Items | +| [get_common_library_items](#get_common_library_items) | Get Common Library Items | +| [get_metadata](#get_metadata) | Get Items Metadata | +| [get_metadata_children](#get_metadata_children) | Get Items Children | +| [get_on_deck](#get_on_deck) | Get On Deck | + + +## Log + +| Method | Description| +| :-------- | :----------| +| [log_multi_line](#log_multi_line) | Logging a multi-line message | +| [log_line](#log_line) | Logging a single line message. | +| [enable_paper_trail](#enable_paper_trail) | Enabling Papertrail | + + +## Playlists + +| Method | Description| +| :-------- | :----------| +| [create_playlist](#create_playlist) | Create a Playlist | +| [get_playlists](#get_playlists) | Get All Playlists | +| [get_playlist](#get_playlist) | Retrieve Playlist | +| [delete_playlist](#delete_playlist) | Deletes a Playlist | +| [update_playlist](#update_playlist) | Update a Playlist | +| [get_playlist_contents](#get_playlist_contents) | Retrieve Playlist Contents | +| [clear_playlist_contents](#clear_playlist_contents) | Delete Playlist Contents | +| [add_playlist_contents](#add_playlist_contents) | Adding to a Playlist | +| [upload_playlist](#upload_playlist) | Upload Playlist | + + +## Security + +| Method | Description| +| :-------- | :----------| +| [get_transient_token](#get_transient_token) | Get a Transient Token. | +| [get_source_connection_information](#get_source_connection_information) | Get Source Connection Information | + + +## Sessions + +| Method | Description| +| :-------- | :----------| +| [get_sessions](#get_sessions) | Get Active Sessions | +| [get_session_history](#get_session_history) | Get Session History | +| [get_transcode_sessions](#get_transcode_sessions) | Get Transcode Sessions | +| [stop_transcode_session](#stop_transcode_session) | Stop a Transcode Session | + + +## Updater + +| Method | Description| +| :-------- | :----------| +| [get_update_status](#get_update_status) | Querying status of updates | +| [check_for_updates](#check_for_updates) | Checking for updates | +| [apply_updates](#apply_updates) | Apply Updates | + + +## Video + +| Method | Description| +| :-------- | :----------| +| [start_universal_transcode](#start_universal_transcode) | Start Universal Transcode | +| [get_timeline](#get_timeline) | Get the timeline for a media item | + + + + +## All Methods + + +### **get_server_capabilities** +Server Capabilities +- HTTP Method: GET +- Endpoint: / + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerCapabilitiesResponse](/src/plexsdk/models/README.md#getservercapabilitiesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_capabilities() + +pprint(vars(results)) + +``` + +### **get_server_preferences** +Get Server Preferences +- HTTP Method: GET +- Endpoint: /:/prefs + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_preferences() + +pprint(vars(results)) + +``` + +### **get_available_clients** +Get Available Clients +- HTTP Method: GET +- Endpoint: /clients + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetAvailableClientsResponse](/src/plexsdk/models/README.md#getavailableclientsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_available_clients() + +pprint(vars(results)) + +``` + +### **get_devices** +Get Devices +- HTTP Method: GET +- Endpoint: /devices + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetDevicesResponse](/src/plexsdk/models/README.md#getdevicesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_devices() + +pprint(vars(results)) + +``` + +### **get_server_identity** +Get Server Identity +- HTTP Method: GET +- Endpoint: /identity + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerIdentityResponse](/src/plexsdk/models/README.md#getserveridentityresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_identity() + +pprint(vars(results)) + +``` + +### **get_my_plex_account** +Get MyPlex Account +- HTTP Method: GET +- Endpoint: /myplex/account + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetMyPlexAccountResponse](/src/plexsdk/models/README.md#getmyplexaccountresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_my_plex_account() + +pprint(vars(results)) + +``` + +### **get_resized_photo** +Get a Resized Photo +- HTTP Method: GET +- Endpoint: /photo/:/transcode + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| width | float | Required | The width for the resized photo | +| height | float | Required | The height for the resized photo | +| opacity | int | Required | The opacity for the resized photo | +| blur | float | Required | The width for the resized photo | +| min_size | [MinSize](/src/plexsdk/models/README.md#minsize) | Required | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. | +| upscale | [Upscale](/src/plexsdk/models/README.md#upscale) | Required | allow images to be resized beyond native dimensions. | +| url | str | Required | path to image within Plex | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_resized_photo( + width = 110, + height = 165, + opacity = 100, + blur = 4000, + min_size = 1, + upscale = 1, + url = '/library/metadata/49564/thumb/1654258204' +) + +pprint(vars(results)) + +``` + +### **get_server_list** +Get Server List +- HTTP Method: GET +- Endpoint: /servers + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerListResponse](/src/plexsdk/models/README.md#getserverlistresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_list() + +pprint(vars(results)) + +``` + + +### **mark_played** +Mark Media Played +- HTTP Method: GET +- Endpoint: /:/scrobble + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | float | Required | The media key to mark as played | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.mark_played(key = 59398) + +pprint(vars(results)) + +``` + +### **mark_unplayed** +Mark Media Unplayed +- HTTP Method: GET +- Endpoint: /:/unscrobble + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | float | Required | The media key to mark as Unplayed | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.mark_unplayed(key = 59398) + +pprint(vars(results)) + +``` + +### **update_play_progress** +Update Media Play Progress +- HTTP Method: POST +- Endpoint: /:/progress + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | str | Required | the media key | +| time | float | Required | The time, in milliseconds, used to set the media playback progress. | +| state | str | Required | The playback state of the media item. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.update_play_progress( + key = 'key', + time = 8535962.551714689, + state = 'state' +) + +pprint(vars(results)) + +``` + + +### **get_server_activities** +Get Server Activities +- HTTP Method: GET +- Endpoint: /activities + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerActivitiesResponse](/src/plexsdk/models/README.md#getserveractivitiesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.activities.get_server_activities() + +pprint(vars(results)) + +``` + +### **cancel_server_activities** +Cancel Server Activities +- HTTP Method: DELETE +- Endpoint: /activities/{activityUUID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| activity_uuid | str | Required | The UUID of the activity to cancel. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.activities.cancel_server_activities(activity_uuid = 'activityUUID') + +pprint(vars(results)) + +``` + + +### **start_all_tasks** +Start all Butler tasks +- HTTP Method: POST +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.start_all_tasks() + +pprint(vars(results)) + +``` + +### **get_butler_tasks** +Get Butler tasks +- HTTP Method: GET +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetButlerTasksResponse](/src/plexsdk/models/README.md#getbutlertasksresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.get_butler_tasks() + +pprint(vars(results)) + +``` + +### **stop_all_tasks** +Stop all Butler tasks +- HTTP Method: DELETE +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.stop_all_tasks() + +pprint(vars(results)) + +``` + +### **start_task** +Start a single Butler task +- HTTP Method: POST +- Endpoint: /butler/{taskName} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | the name of the task to be started. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.start_task(task_name = 'CleanOldBundles') + +pprint(vars(results)) + +``` + +### **stop_task** +Stop a single Butler task +- HTTP Method: DELETE +- Endpoint: /butler/{taskName} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | The name of the task to be started. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.stop_task(task_name = 'BuildGracenoteCollections') + +pprint(vars(results)) + +``` + + +### **get_global_hubs** +Get Global Hubs +- HTTP Method: GET +- Endpoint: /hubs + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| count | float | Optional | The number of items to return with each hub. | +| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.hubs.get_global_hubs( + count = -21278281.03559582, + only_transient = 42.1 +) + +pprint(vars(results)) + +``` + +### **get_library_hubs** +Get library specific hubs +- HTTP Method: GET +- Endpoint: /hubs/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| count | float | Optional | The number of items to return with each hub. | +| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.hubs.get_library_hubs( + section_id = -58449853.97546232, + count = 19599615.466092095, + only_transient = 42.1 +) + +pprint(vars(results)) + +``` + + +### **perform_search** +Perform a search +- HTTP Method: GET +- Endpoint: /hubs/search + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The query term | +| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | float | Optional | The number of items to return per hub | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.perform_search( + query = 'arnold', + section_id = -56180545.95318425, + limit = 5 +) + +pprint(vars(results)) + +``` + +### **perform_voice_search** +Perform a voice search +- HTTP Method: GET +- Endpoint: /hubs/search/voice + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The query term | +| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | float | Optional | The number of items to return per hub | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.perform_voice_search( + query = 'dead+poop', + section_id = 69248804.72578311, + limit = 5 +) + +pprint(vars(results)) + +``` + +### **get_search_results** +Get Search Results +- HTTP Method: GET +- Endpoint: /search + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The search query string to use | + +**Return Type** + +[GetSearchResultsResponse](/src/plexsdk/models/README.md#getsearchresultsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.get_search_results(query = '110') + +pprint(vars(results)) + +``` + + +### **get_file_hash** +Get Hash Value +- HTTP Method: GET +- Endpoint: /library/hashes + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| url | str | Required | This is the path to the local file, must be prefixed by `file://` | +| type | float | Optional | Item type | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_file_hash( + url = 'file://C:\Image.png&type=13', + type = -87442821.49664992 +) + +pprint(vars(results)) + +``` + +### **get_recently_added** +Get Recently Added +- HTTP Method: GET +- Endpoint: /library/recentlyAdded + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetRecentlyAddedResponse](/src/plexsdk/models/README.md#getrecentlyaddedresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_recently_added() + +pprint(vars(results)) + +``` + +### **get_libraries** +Get All Libraries +- HTTP Method: GET +- Endpoint: /library/sections + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_libraries() + +pprint(vars(results)) + +``` + +### **get_library** +Get Library Details +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| include_details | [IncludeDetails](/src/plexsdk/models/README.md#includedetails) | Optional | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
| + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_library( + section_id = 1000, + include_details = 42.1 +) + +pprint(vars(results)) + +``` + +### **delete_library** +Delete Library Section +- HTTP Method: DELETE +- Endpoint: /library/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.delete_library(section_id = 1000) + +pprint(vars(results)) + +``` + +### **get_library_items** +Get Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/all + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Optional | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_library_items( + section_id = 36759422.79452938, + type = 85955460.47229531, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **refresh_library** +Refresh Library +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/refresh + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to refresh | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.refresh_library(section_id = -11046452.742861003) + +pprint(vars(results)) + +``` + +### **get_latest_library_items** +Get Latest Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/latest + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Required | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_latest_library_items( + section_id = -72167450.51781249, + type = 84030430.945622, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **get_common_library_items** +Get Common Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/common + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Required | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_common_library_items( + section_id = 84909442.48641255, + type = 81418870.30483484, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **get_metadata** +Get Items Metadata +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | the id of the library item to return the children of. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_metadata(rating_key = -79180953.0704827) + +pprint(vars(results)) + +``` + +### **get_metadata_children** +Get Items Children +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey}/children + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | the id of the library item to return the children of. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_metadata_children(rating_key = 22112069.91905561) + +pprint(vars(results)) + +``` + +### **get_on_deck** +Get On Deck +- HTTP Method: GET +- Endpoint: /library/onDeck + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetOnDeckResponse](/src/plexsdk/models/README.md#getondeckresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_on_deck() + +pprint(vars(results)) + +``` + + +### **log_multi_line** +Logging a multi-line message +- HTTP Method: POST +- Endpoint: /log + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.log_multi_line() + +pprint(vars(results)) + +``` + +### **log_line** +Logging a single line message. +- HTTP Method: GET +- Endpoint: /log + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| level | [Level](/src/plexsdk/models/README.md#level) | Required | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
| +| message | str | Required | The text of the message to write to the log. | +| source | str | Required | a string indicating the source of the message. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.log_line( + level = 1, + message = 'message', + source = 'source' +) + +pprint(vars(results)) + +``` + +### **enable_paper_trail** +Enabling Papertrail +- HTTP Method: GET +- Endpoint: /log/networked + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.enable_paper_trail() + +pprint(vars(results)) + +``` + + +### **create_playlist** +Create a Playlist +- HTTP Method: POST +- Endpoint: /playlists + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| title | str | Required | name of the playlist | +| type | [Type](/src/plexsdk/models/README.md#type) | Required | type of playlist to create | +| smart | [Smart](/src/plexsdk/models/README.md#smart) | Required | whether the playlist is smart or not | +| uri | str | Optional | the content URI for the playlist | +| play_queue_id | float | Optional | the play queue to copy to a playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.create_playlist( + title = 'title', + type = 'audio', + smart = 1, + uri = 'uri', + play_queue_id = -92047882.95265284 +) + +pprint(vars(results)) + +``` + +### **get_playlists** +Get All Playlists +- HTTP Method: GET +- Endpoint: /playlists/all + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_type | [PlaylistType](/src/plexsdk/models/README.md#playlisttype) | Optional | limit to a type of playlist. | +| smart | [Smart](/src/plexsdk/models/README.md#smart) | Optional | type of playlists to return (default is all). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlists( + playlist_type = 'photo', + smart = 1 +) + +pprint(vars(results)) + +``` + +### **get_playlist** +Retrieve Playlist +- HTTP Method: GET +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlist(playlist_id = 83585352.9850379) + +pprint(vars(results)) + +``` + +### **delete_playlist** +Deletes a Playlist +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.delete_playlist(playlist_id = 97044833.44888172) + +pprint(vars(results)) + +``` + +### **update_playlist** +Update a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.update_playlist(playlist_id = -48390592.04133318) + +pprint(vars(results)) + +``` + +### **get_playlist_contents** +Retrieve Playlist Contents +- HTTP Method: GET +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | +| type | float | Required | the metadata type of the item to return | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlist_contents( + playlist_id = 86594016.93713528, + type = 92063710.76575199 +) + +pprint(vars(results)) + +``` + +### **clear_playlist_contents** +Delete Playlist Contents +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.clear_playlist_contents(playlist_id = -5395985.032092914) + +pprint(vars(results)) + +``` + +### **add_playlist_contents** +Adding to a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | +| uri | str | Required | the content URI for the playlist | +| play_queue_id | float | Required | the play queue to add to a playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.add_playlist_contents( + playlist_id = -6920661.3914530575, + uri = 'library://..', + play_queue_id = 123 +) + +pprint(vars(results)) + +``` + +### **upload_playlist** +Upload Playlist +- HTTP Method: POST +- Endpoint: /playlists/upload + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| path | str | Required | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
| +| force | [Force](/src/plexsdk/models/README.md#force) | Required | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
| + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.upload_playlist( + path = '/home/barkley/playlist.m3u', + force = 1 +) + +pprint(vars(results)) + +``` + + +### **get_transient_token** +Get a Transient Token. +- HTTP Method: GET +- Endpoint: /security/token + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| type | [SecurityType](/src/plexsdk/models/README.md#securitytype) | Required | `delegation` - This is the only supported `type` parameter. | +| scope | [Scope](/src/plexsdk/models/README.md#scope) | Required | `all` - This is the only supported `scope` parameter. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.security.get_transient_token( + type = 'delegation', + scope = 'all' +) + +pprint(vars(results)) + +``` + +### **get_source_connection_information** +Get Source Connection Information +- HTTP Method: GET +- Endpoint: /security/resources + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| source | str | Required | The source identifier with an included prefix. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.security.get_source_connection_information(source = 'provider://provider-identifier') + +pprint(vars(results)) + +``` + + +### **get_sessions** +Get Active Sessions +- HTTP Method: GET +- Endpoint: /status/sessions + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_sessions() + +pprint(vars(results)) + +``` + +### **get_session_history** +Get Session History +- HTTP Method: GET +- Endpoint: /status/sessions/history/all + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_session_history() + +pprint(vars(results)) + +``` + +### **get_transcode_sessions** +Get Transcode Sessions +- HTTP Method: GET +- Endpoint: /transcode/sessions + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetTranscodeSessionsResponse](/src/plexsdk/models/README.md#gettranscodesessionsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_transcode_sessions() + +pprint(vars(results)) + +``` + +### **stop_transcode_session** +Stop a Transcode Session +- HTTP Method: DELETE +- Endpoint: /transcode/sessions/{sessionKey} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| session_key | str | Required | the Key of the transcode session to stop | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.stop_transcode_session(session_key = 'zz7llzqlx8w9vnrsbnwhbmep') + +pprint(vars(results)) + +``` + + +### **get_update_status** +Querying status of updates +- HTTP Method: GET +- Endpoint: /updater/status + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.get_update_status() + +pprint(vars(results)) + +``` + +### **check_for_updates** +Checking for updates +- HTTP Method: PUT +- Endpoint: /updater/check + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| download | [Download](/src/plexsdk/models/README.md#download) | Optional | Indicate that you want to start download any updates found. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.check_for_updates(download = 1) + +pprint(vars(results)) + +``` + +### **apply_updates** +Apply Updates +- HTTP Method: PUT +- Endpoint: /updater/apply + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| tonight | [Tonight](/src/plexsdk/models/README.md#tonight) | Optional | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install | +| skip | [Skip](/src/plexsdk/models/README.md#skip) | Optional | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.apply_updates( + tonight = 'foo', + skip = 1 +) + +pprint(vars(results)) + +``` + + +### **start_universal_transcode** +Start Universal Transcode +- HTTP Method: GET +- Endpoint: /video/:/transcode/universal/start.mpd + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| has_mde | float | Required | Whether the media item has MDE | +| path | str | Required | The path to the media item to transcode | +| media_index | float | Required | The index of the media item to transcode | +| part_index | float | Required | The index of the part to transcode | +| protocol | str | Required | The protocol to use for the transcode session | +| fast_seek | float | Optional | Whether to use fast seek or not | +| direct_play | float | Optional | Whether to use direct play or not | +| direct_stream | float | Optional | Whether to use direct stream or not | +| subtitle_size | float | Optional | The size of the subtitles | +| subtites | str | Optional | The subtitles | +| audio_boost | float | Optional | The audio boost | +| location | str | Optional | The location of the transcode session | +| media_buffer_size | float | Optional | The size of the media buffer | +| session | str | Optional | The session ID | +| add_debug_overlay | float | Optional | Whether to add a debug overlay or not | +| auto_adjust_quality | float | Optional | Whether to auto adjust quality or not | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.video.start_universal_transcode( + has_mde = 68681526.6739457, + path = 'path', + media_index = 38635292.15502611, + part_index = 56377101.56072605, + protocol = 'protocol', + fast_seek = -48546578.06404572, + direct_play = -4004169.7057704926, + direct_stream = -63607905.2844202, + subtitle_size = -95264880.14792101, + subtites = 'subtites', + audio_boost = 92032906.1650356, + location = 'location', + media_buffer_size = 43422490.76220566, + session = 'session', + add_debug_overlay = -40848683.38562142, + auto_adjust_quality = 63926343.42811155 +) + +pprint(vars(results)) + +``` + +### **get_timeline** +Get the timeline for a media item +- HTTP Method: GET +- Endpoint: /:/timeline + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | The rating key of the media item | +| key | str | Required | The key of the media item to get the timeline for | +| state | [State](/src/plexsdk/models/README.md#state) | Required | The state of the media item | +| has_mde | float | Required | Whether the media item has MDE | +| time | float | Required | The time of the media item | +| duration | float | Required | The duration of the media item | +| context | str | Required | The context of the media item | +| play_queue_item_id | float | Required | The play queue item ID of the media item | +| play_back_time | float | Required | The playback time of the media item | +| row | float | Required | The row of the media item | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.video.get_timeline( + rating_key = 45215776.89077535, + key = 'key', + state = 'playing', + has_mde = -17905072.874770716, + time = -7347989.028095856, + duration = 82330750.57461244, + context = 'context', + play_queue_item_id = -69611222.19233666, + play_back_time = -80252961.1853132, + row = -54653572.50923404 +) + +pprint(vars(results)) + +``` + + + + + +## License + +License: MIT. See license in LICENSE. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..6240441 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,33 @@ +# plexpy-example +A basic example of how to use the plexpy package. + +## Installation + +If `plexpy` is published to pypi: +```sh +pip install plexpy==0.0.1 +``` + +In the event `plexpy` is not published to pypi, you can install it locally by running the following command in the example folder: +```sh +. ./install.sh +``` + +This will create and start a virtual environment and install the package locally in it. + +To close the virtual environment run: +```sh +deactivate +``` + +To re-activate the virtual environment once it is installed run: +```sh +source .venv/bin/activate +``` + +## Usage + +To run the example, run the following command in the examples folder: +```sh +python sample.py +``` diff --git a/examples/install.sh b/examples/install.sh new file mode 100644 index 0000000..b3c850a --- /dev/null +++ b/examples/install.sh @@ -0,0 +1,5 @@ +python -m venv .venv +source .venv/bin/activate +pip install build +python -m build --outdir dist ../ +pip install dist/plexpy-0.0.1-py3-none-any.whl diff --git a/examples/install_py3.sh b/examples/install_py3.sh new file mode 100644 index 0000000..7aa9f77 --- /dev/null +++ b/examples/install_py3.sh @@ -0,0 +1,5 @@ +python3 -m venv .venv +source .venv/bin/activate +pip3 install build +python3 -m build --outdir dist ../ +pip3 install dist/plexpy-0.0.1-py3-none-any.whl \ No newline at end of file diff --git a/examples/sample.py b/examples/sample.py new file mode 100644 index 0000000..10c581d --- /dev/null +++ b/examples/sample.py @@ -0,0 +1,10 @@ +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK + +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) + +results = sdk.server.get_server_capabilities() + +pprint(vars(results)) diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..0701976 --- /dev/null +++ b/install.sh @@ -0,0 +1,3 @@ +pip3 install -r requirements.txt +pip3 install -e src/plexsdk/ +python3 -m unittest discover -p "test*.py" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..fc80391 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "plexpy" +version = "0.0.1" +license = { text = "MIT" } +authors = [ + { name="Luke Hagar", email="lukeslakemail@gmail.com" } +] +description = """An Open API Spec for interacting with Plex.tv and Plex Servers""" +readme = "README.md" +requires-python = ">=3.7" +dependencies = [ + "requests", + "http-exceptions", + "pytest", + "responses" +] + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..47d00c4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +requests +http-exceptions +pytest +responses \ No newline at end of file diff --git a/src/plexsdk/README.md b/src/plexsdk/README.md new file mode 100644 index 0000000..228d8a7 --- /dev/null +++ b/src/plexsdk/README.md @@ -0,0 +1,2003 @@ +# PlexSDK Python SDK 0.0.1 +A Python SDK for PlexSDK. + +An Open API Spec for interacting with Plex.tv and Plex Servers + +- API version: 0.0.1 +- SDK version: 0.0.1 + +## Table of Contents +- [Requirements](#requirements) +- [Installation](#installation) + - [Dependencies](#dependencies) +- [Authentication](#authentication) + - [API Key](#api-key) +- [API Endpoint Services](#api-endpoint-services) +- [API Models](#api-models) +- [Testing](#testing) +- [Configuration](#configuration) +- [Sample Usage](#sample-usage) +- [PlexSDK Services](#plexsdk-services) +- [License](#license) + +## Installation +```bash +pip install plexpy +``` + +### Dependencies + +This SDK uses the following dependencies: +- requests 2.28.1 +- http-exceptions 0.2.10 +- pytest 7.1.2 +- responses 0.21.0 + +## Authentication + +To see whether an endpoint needs a specific type of authentication check the endpoint's documentation. +### API Key +The PlexSDK API uses API keys as a form of authentication. An API key is a unique identifier used to authenticate a user, developer, or a program that is calling the API. You can set the API key when initializing the SDK through the constructor: + +```python +sdk = PlexSDK('YOUR_API_KEY', 'YOUR_API_KEY_HEADER') +``` + +If you omit `YOUR_API_KEY_HEADER`, the SDK default API key header will be `X-Plex-Token`. + +You can also set it for each service individually: + +```python +sdk = PlexSDK() +sdk.server.set_api_key('YOUR_API_KEY', 'YOUR_API_KEY_HEADER') +``` + +## API Endpoint Services + +All URIs are relative to {protocol}://{ip}:{port}. + +Click the service name for a full list of the service methods. + +| Service | +| :------ | +|[Server](./services/README.md#server)| +|[Media](./services/README.md#media)| +|[Activities](./services/README.md#activities)| +|[Butler](./services/README.md#butler)| +|[Hubs](./services/README.md#hubs)| +|[Search](./services/README.md#search)| +|[Library](./services/README.md#library)| +|[Log](./services/README.md#log)| +|[Playlists](./services/README.md#playlists)| +|[Security](./services/README.md#security)| +|[Sessions](./services/README.md#sessions)| +|[Updater](./services/README.md#updater)| +|[Video](./services/README.md#video)| + +## API Models +[A list documenting all API models for this SDK](./models/README.md#plexsdk-models). + +## Testing + +Run unit tests with this command: + +```sh +python -m unittest discover -p "test*.py" +``` + +## Sample Usage + +```py +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK + +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) + +results = sdk.server.get_server_capabilities() + +pprint(vars(results)) +``` + + +# PlexSDK Services +A list of all services and services methods. +- Services + + - [Server](#server) + + - [Media](#media) + + - [Activities](#activities) + + - [Butler](#butler) + + - [Hubs](#hubs) + + - [Search](#search) + + - [Library](#library) + + - [Log](#log) + + - [Playlists](#playlists) + + - [Security](#security) + + - [Sessions](#sessions) + + - [Updater](#updater) + + - [Video](#video) +- [All Methods](#all-methods) + + +## Server + +| Method | Description| +| :-------- | :----------| +| [get_server_capabilities](#get_server_capabilities) | Server Capabilities | +| [get_server_preferences](#get_server_preferences) | Get Server Preferences | +| [get_available_clients](#get_available_clients) | Get Available Clients | +| [get_devices](#get_devices) | Get Devices | +| [get_server_identity](#get_server_identity) | Get Server Identity | +| [get_my_plex_account](#get_my_plex_account) | Get MyPlex Account | +| [get_resized_photo](#get_resized_photo) | Get a Resized Photo | +| [get_server_list](#get_server_list) | Get Server List | + + +## Media + +| Method | Description| +| :-------- | :----------| +| [mark_played](#mark_played) | Mark Media Played | +| [mark_unplayed](#mark_unplayed) | Mark Media Unplayed | +| [update_play_progress](#update_play_progress) | Update Media Play Progress | + + +## Activities + +| Method | Description| +| :-------- | :----------| +| [get_server_activities](#get_server_activities) | Get Server Activities | +| [cancel_server_activities](#cancel_server_activities) | Cancel Server Activities | + + +## Butler + +| Method | Description| +| :-------- | :----------| +| [start_all_tasks](#start_all_tasks) | Start all Butler tasks | +| [get_butler_tasks](#get_butler_tasks) | Get Butler tasks | +| [stop_all_tasks](#stop_all_tasks) | Stop all Butler tasks | +| [start_task](#start_task) | Start a single Butler task | +| [stop_task](#stop_task) | Stop a single Butler task | + + +## Hubs + +| Method | Description| +| :-------- | :----------| +| [get_global_hubs](#get_global_hubs) | Get Global Hubs | +| [get_library_hubs](#get_library_hubs) | Get library specific hubs | + + +## Search + +| Method | Description| +| :-------- | :----------| +| [perform_search](#perform_search) | Perform a search | +| [perform_voice_search](#perform_voice_search) | Perform a voice search | +| [get_search_results](#get_search_results) | Get Search Results | + + +## Library + +| Method | Description| +| :-------- | :----------| +| [get_file_hash](#get_file_hash) | Get Hash Value | +| [get_recently_added](#get_recently_added) | Get Recently Added | +| [get_libraries](#get_libraries) | Get All Libraries | +| [get_library](#get_library) | Get Library Details | +| [delete_library](#delete_library) | Delete Library Section | +| [get_library_items](#get_library_items) | Get Library Items | +| [refresh_library](#refresh_library) | Refresh Library | +| [get_latest_library_items](#get_latest_library_items) | Get Latest Library Items | +| [get_common_library_items](#get_common_library_items) | Get Common Library Items | +| [get_metadata](#get_metadata) | Get Items Metadata | +| [get_metadata_children](#get_metadata_children) | Get Items Children | +| [get_on_deck](#get_on_deck) | Get On Deck | + + +## Log + +| Method | Description| +| :-------- | :----------| +| [log_multi_line](#log_multi_line) | Logging a multi-line message | +| [log_line](#log_line) | Logging a single line message. | +| [enable_paper_trail](#enable_paper_trail) | Enabling Papertrail | + + +## Playlists + +| Method | Description| +| :-------- | :----------| +| [create_playlist](#create_playlist) | Create a Playlist | +| [get_playlists](#get_playlists) | Get All Playlists | +| [get_playlist](#get_playlist) | Retrieve Playlist | +| [delete_playlist](#delete_playlist) | Deletes a Playlist | +| [update_playlist](#update_playlist) | Update a Playlist | +| [get_playlist_contents](#get_playlist_contents) | Retrieve Playlist Contents | +| [clear_playlist_contents](#clear_playlist_contents) | Delete Playlist Contents | +| [add_playlist_contents](#add_playlist_contents) | Adding to a Playlist | +| [upload_playlist](#upload_playlist) | Upload Playlist | + + +## Security + +| Method | Description| +| :-------- | :----------| +| [get_transient_token](#get_transient_token) | Get a Transient Token. | +| [get_source_connection_information](#get_source_connection_information) | Get Source Connection Information | + + +## Sessions + +| Method | Description| +| :-------- | :----------| +| [get_sessions](#get_sessions) | Get Active Sessions | +| [get_session_history](#get_session_history) | Get Session History | +| [get_transcode_sessions](#get_transcode_sessions) | Get Transcode Sessions | +| [stop_transcode_session](#stop_transcode_session) | Stop a Transcode Session | + + +## Updater + +| Method | Description| +| :-------- | :----------| +| [get_update_status](#get_update_status) | Querying status of updates | +| [check_for_updates](#check_for_updates) | Checking for updates | +| [apply_updates](#apply_updates) | Apply Updates | + + +## Video + +| Method | Description| +| :-------- | :----------| +| [start_universal_transcode](#start_universal_transcode) | Start Universal Transcode | +| [get_timeline](#get_timeline) | Get the timeline for a media item | + + + + +## All Methods + + +### **get_server_capabilities** +Server Capabilities +- HTTP Method: GET +- Endpoint: / + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerCapabilitiesResponse](/src/plexsdk/models/README.md#getservercapabilitiesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_capabilities() + +pprint(vars(results)) + +``` + +### **get_server_preferences** +Get Server Preferences +- HTTP Method: GET +- Endpoint: /:/prefs + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_preferences() + +pprint(vars(results)) + +``` + +### **get_available_clients** +Get Available Clients +- HTTP Method: GET +- Endpoint: /clients + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetAvailableClientsResponse](/src/plexsdk/models/README.md#getavailableclientsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_available_clients() + +pprint(vars(results)) + +``` + +### **get_devices** +Get Devices +- HTTP Method: GET +- Endpoint: /devices + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetDevicesResponse](/src/plexsdk/models/README.md#getdevicesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_devices() + +pprint(vars(results)) + +``` + +### **get_server_identity** +Get Server Identity +- HTTP Method: GET +- Endpoint: /identity + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerIdentityResponse](/src/plexsdk/models/README.md#getserveridentityresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_identity() + +pprint(vars(results)) + +``` + +### **get_my_plex_account** +Get MyPlex Account +- HTTP Method: GET +- Endpoint: /myplex/account + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetMyPlexAccountResponse](/src/plexsdk/models/README.md#getmyplexaccountresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_my_plex_account() + +pprint(vars(results)) + +``` + +### **get_resized_photo** +Get a Resized Photo +- HTTP Method: GET +- Endpoint: /photo/:/transcode + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| width | float | Required | The width for the resized photo | +| height | float | Required | The height for the resized photo | +| opacity | int | Required | The opacity for the resized photo | +| blur | float | Required | The width for the resized photo | +| min_size | [MinSize](/src/plexsdk/models/README.md#minsize) | Required | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. | +| upscale | [Upscale](/src/plexsdk/models/README.md#upscale) | Required | allow images to be resized beyond native dimensions. | +| url | str | Required | path to image within Plex | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_resized_photo( + width = 110, + height = 165, + opacity = 100, + blur = 4000, + min_size = 1, + upscale = 1, + url = '/library/metadata/49564/thumb/1654258204' +) + +pprint(vars(results)) + +``` + +### **get_server_list** +Get Server List +- HTTP Method: GET +- Endpoint: /servers + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerListResponse](/src/plexsdk/models/README.md#getserverlistresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_list() + +pprint(vars(results)) + +``` + + +### **mark_played** +Mark Media Played +- HTTP Method: GET +- Endpoint: /:/scrobble + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | float | Required | The media key to mark as played | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.mark_played(key = 59398) + +pprint(vars(results)) + +``` + +### **mark_unplayed** +Mark Media Unplayed +- HTTP Method: GET +- Endpoint: /:/unscrobble + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | float | Required | The media key to mark as Unplayed | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.mark_unplayed(key = 59398) + +pprint(vars(results)) + +``` + +### **update_play_progress** +Update Media Play Progress +- HTTP Method: POST +- Endpoint: /:/progress + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | str | Required | the media key | +| time | float | Required | The time, in milliseconds, used to set the media playback progress. | +| state | str | Required | The playback state of the media item. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.update_play_progress( + key = 'key', + time = 8535962.551714689, + state = 'state' +) + +pprint(vars(results)) + +``` + + +### **get_server_activities** +Get Server Activities +- HTTP Method: GET +- Endpoint: /activities + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerActivitiesResponse](/src/plexsdk/models/README.md#getserveractivitiesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.activities.get_server_activities() + +pprint(vars(results)) + +``` + +### **cancel_server_activities** +Cancel Server Activities +- HTTP Method: DELETE +- Endpoint: /activities/{activityUUID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| activity_uuid | str | Required | The UUID of the activity to cancel. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.activities.cancel_server_activities(activity_uuid = 'activityUUID') + +pprint(vars(results)) + +``` + + +### **start_all_tasks** +Start all Butler tasks +- HTTP Method: POST +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.start_all_tasks() + +pprint(vars(results)) + +``` + +### **get_butler_tasks** +Get Butler tasks +- HTTP Method: GET +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetButlerTasksResponse](/src/plexsdk/models/README.md#getbutlertasksresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.get_butler_tasks() + +pprint(vars(results)) + +``` + +### **stop_all_tasks** +Stop all Butler tasks +- HTTP Method: DELETE +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.stop_all_tasks() + +pprint(vars(results)) + +``` + +### **start_task** +Start a single Butler task +- HTTP Method: POST +- Endpoint: /butler/{taskName} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | the name of the task to be started. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.start_task(task_name = 'CleanOldBundles') + +pprint(vars(results)) + +``` + +### **stop_task** +Stop a single Butler task +- HTTP Method: DELETE +- Endpoint: /butler/{taskName} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | The name of the task to be started. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.stop_task(task_name = 'BuildGracenoteCollections') + +pprint(vars(results)) + +``` + + +### **get_global_hubs** +Get Global Hubs +- HTTP Method: GET +- Endpoint: /hubs + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| count | float | Optional | The number of items to return with each hub. | +| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.hubs.get_global_hubs( + count = -21278281.03559582, + only_transient = 42.1 +) + +pprint(vars(results)) + +``` + +### **get_library_hubs** +Get library specific hubs +- HTTP Method: GET +- Endpoint: /hubs/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| count | float | Optional | The number of items to return with each hub. | +| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.hubs.get_library_hubs( + section_id = -58449853.97546232, + count = 19599615.466092095, + only_transient = 42.1 +) + +pprint(vars(results)) + +``` + + +### **perform_search** +Perform a search +- HTTP Method: GET +- Endpoint: /hubs/search + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The query term | +| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | float | Optional | The number of items to return per hub | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.perform_search( + query = 'arnold', + section_id = -56180545.95318425, + limit = 5 +) + +pprint(vars(results)) + +``` + +### **perform_voice_search** +Perform a voice search +- HTTP Method: GET +- Endpoint: /hubs/search/voice + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The query term | +| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | float | Optional | The number of items to return per hub | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.perform_voice_search( + query = 'dead+poop', + section_id = 69248804.72578311, + limit = 5 +) + +pprint(vars(results)) + +``` + +### **get_search_results** +Get Search Results +- HTTP Method: GET +- Endpoint: /search + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The search query string to use | + +**Return Type** + +[GetSearchResultsResponse](/src/plexsdk/models/README.md#getsearchresultsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.get_search_results(query = '110') + +pprint(vars(results)) + +``` + + +### **get_file_hash** +Get Hash Value +- HTTP Method: GET +- Endpoint: /library/hashes + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| url | str | Required | This is the path to the local file, must be prefixed by `file://` | +| type | float | Optional | Item type | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_file_hash( + url = 'file://C:\Image.png&type=13', + type = -87442821.49664992 +) + +pprint(vars(results)) + +``` + +### **get_recently_added** +Get Recently Added +- HTTP Method: GET +- Endpoint: /library/recentlyAdded + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetRecentlyAddedResponse](/src/plexsdk/models/README.md#getrecentlyaddedresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_recently_added() + +pprint(vars(results)) + +``` + +### **get_libraries** +Get All Libraries +- HTTP Method: GET +- Endpoint: /library/sections + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_libraries() + +pprint(vars(results)) + +``` + +### **get_library** +Get Library Details +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| include_details | [IncludeDetails](/src/plexsdk/models/README.md#includedetails) | Optional | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
| + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_library( + section_id = 1000, + include_details = 42.1 +) + +pprint(vars(results)) + +``` + +### **delete_library** +Delete Library Section +- HTTP Method: DELETE +- Endpoint: /library/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.delete_library(section_id = 1000) + +pprint(vars(results)) + +``` + +### **get_library_items** +Get Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/all + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Optional | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_library_items( + section_id = 36759422.79452938, + type = 85955460.47229531, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **refresh_library** +Refresh Library +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/refresh + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to refresh | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.refresh_library(section_id = -11046452.742861003) + +pprint(vars(results)) + +``` + +### **get_latest_library_items** +Get Latest Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/latest + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Required | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_latest_library_items( + section_id = -72167450.51781249, + type = 84030430.945622, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **get_common_library_items** +Get Common Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/common + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Required | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_common_library_items( + section_id = 84909442.48641255, + type = 81418870.30483484, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **get_metadata** +Get Items Metadata +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | the id of the library item to return the children of. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_metadata(rating_key = -79180953.0704827) + +pprint(vars(results)) + +``` + +### **get_metadata_children** +Get Items Children +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey}/children + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | the id of the library item to return the children of. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_metadata_children(rating_key = 22112069.91905561) + +pprint(vars(results)) + +``` + +### **get_on_deck** +Get On Deck +- HTTP Method: GET +- Endpoint: /library/onDeck + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetOnDeckResponse](/src/plexsdk/models/README.md#getondeckresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_on_deck() + +pprint(vars(results)) + +``` + + +### **log_multi_line** +Logging a multi-line message +- HTTP Method: POST +- Endpoint: /log + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.log_multi_line() + +pprint(vars(results)) + +``` + +### **log_line** +Logging a single line message. +- HTTP Method: GET +- Endpoint: /log + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| level | [Level](/src/plexsdk/models/README.md#level) | Required | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
| +| message | str | Required | The text of the message to write to the log. | +| source | str | Required | a string indicating the source of the message. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.log_line( + level = 1, + message = 'message', + source = 'source' +) + +pprint(vars(results)) + +``` + +### **enable_paper_trail** +Enabling Papertrail +- HTTP Method: GET +- Endpoint: /log/networked + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.enable_paper_trail() + +pprint(vars(results)) + +``` + + +### **create_playlist** +Create a Playlist +- HTTP Method: POST +- Endpoint: /playlists + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| title | str | Required | name of the playlist | +| type | [Type](/src/plexsdk/models/README.md#type) | Required | type of playlist to create | +| smart | [Smart](/src/plexsdk/models/README.md#smart) | Required | whether the playlist is smart or not | +| uri | str | Optional | the content URI for the playlist | +| play_queue_id | float | Optional | the play queue to copy to a playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.create_playlist( + title = 'title', + type = 'audio', + smart = 1, + uri = 'uri', + play_queue_id = -92047882.95265284 +) + +pprint(vars(results)) + +``` + +### **get_playlists** +Get All Playlists +- HTTP Method: GET +- Endpoint: /playlists/all + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_type | [PlaylistType](/src/plexsdk/models/README.md#playlisttype) | Optional | limit to a type of playlist. | +| smart | [Smart](/src/plexsdk/models/README.md#smart) | Optional | type of playlists to return (default is all). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlists( + playlist_type = 'photo', + smart = 1 +) + +pprint(vars(results)) + +``` + +### **get_playlist** +Retrieve Playlist +- HTTP Method: GET +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlist(playlist_id = 83585352.9850379) + +pprint(vars(results)) + +``` + +### **delete_playlist** +Deletes a Playlist +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.delete_playlist(playlist_id = 97044833.44888172) + +pprint(vars(results)) + +``` + +### **update_playlist** +Update a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.update_playlist(playlist_id = -48390592.04133318) + +pprint(vars(results)) + +``` + +### **get_playlist_contents** +Retrieve Playlist Contents +- HTTP Method: GET +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | +| type | float | Required | the metadata type of the item to return | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlist_contents( + playlist_id = 86594016.93713528, + type = 92063710.76575199 +) + +pprint(vars(results)) + +``` + +### **clear_playlist_contents** +Delete Playlist Contents +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.clear_playlist_contents(playlist_id = -5395985.032092914) + +pprint(vars(results)) + +``` + +### **add_playlist_contents** +Adding to a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | +| uri | str | Required | the content URI for the playlist | +| play_queue_id | float | Required | the play queue to add to a playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.add_playlist_contents( + playlist_id = -6920661.3914530575, + uri = 'library://..', + play_queue_id = 123 +) + +pprint(vars(results)) + +``` + +### **upload_playlist** +Upload Playlist +- HTTP Method: POST +- Endpoint: /playlists/upload + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| path | str | Required | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
| +| force | [Force](/src/plexsdk/models/README.md#force) | Required | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
| + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.upload_playlist( + path = '/home/barkley/playlist.m3u', + force = 1 +) + +pprint(vars(results)) + +``` + + +### **get_transient_token** +Get a Transient Token. +- HTTP Method: GET +- Endpoint: /security/token + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| type | [SecurityType](/src/plexsdk/models/README.md#securitytype) | Required | `delegation` - This is the only supported `type` parameter. | +| scope | [Scope](/src/plexsdk/models/README.md#scope) | Required | `all` - This is the only supported `scope` parameter. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.security.get_transient_token( + type = 'delegation', + scope = 'all' +) + +pprint(vars(results)) + +``` + +### **get_source_connection_information** +Get Source Connection Information +- HTTP Method: GET +- Endpoint: /security/resources + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| source | str | Required | The source identifier with an included prefix. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.security.get_source_connection_information(source = 'provider://provider-identifier') + +pprint(vars(results)) + +``` + + +### **get_sessions** +Get Active Sessions +- HTTP Method: GET +- Endpoint: /status/sessions + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_sessions() + +pprint(vars(results)) + +``` + +### **get_session_history** +Get Session History +- HTTP Method: GET +- Endpoint: /status/sessions/history/all + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_session_history() + +pprint(vars(results)) + +``` + +### **get_transcode_sessions** +Get Transcode Sessions +- HTTP Method: GET +- Endpoint: /transcode/sessions + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetTranscodeSessionsResponse](/src/plexsdk/models/README.md#gettranscodesessionsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_transcode_sessions() + +pprint(vars(results)) + +``` + +### **stop_transcode_session** +Stop a Transcode Session +- HTTP Method: DELETE +- Endpoint: /transcode/sessions/{sessionKey} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| session_key | str | Required | the Key of the transcode session to stop | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.stop_transcode_session(session_key = 'zz7llzqlx8w9vnrsbnwhbmep') + +pprint(vars(results)) + +``` + + +### **get_update_status** +Querying status of updates +- HTTP Method: GET +- Endpoint: /updater/status + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.get_update_status() + +pprint(vars(results)) + +``` + +### **check_for_updates** +Checking for updates +- HTTP Method: PUT +- Endpoint: /updater/check + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| download | [Download](/src/plexsdk/models/README.md#download) | Optional | Indicate that you want to start download any updates found. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.check_for_updates(download = 1) + +pprint(vars(results)) + +``` + +### **apply_updates** +Apply Updates +- HTTP Method: PUT +- Endpoint: /updater/apply + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| tonight | [Tonight](/src/plexsdk/models/README.md#tonight) | Optional | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install | +| skip | [Skip](/src/plexsdk/models/README.md#skip) | Optional | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.apply_updates( + tonight = 'foo', + skip = 1 +) + +pprint(vars(results)) + +``` + + +### **start_universal_transcode** +Start Universal Transcode +- HTTP Method: GET +- Endpoint: /video/:/transcode/universal/start.mpd + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| has_mde | float | Required | Whether the media item has MDE | +| path | str | Required | The path to the media item to transcode | +| media_index | float | Required | The index of the media item to transcode | +| part_index | float | Required | The index of the part to transcode | +| protocol | str | Required | The protocol to use for the transcode session | +| fast_seek | float | Optional | Whether to use fast seek or not | +| direct_play | float | Optional | Whether to use direct play or not | +| direct_stream | float | Optional | Whether to use direct stream or not | +| subtitle_size | float | Optional | The size of the subtitles | +| subtites | str | Optional | The subtitles | +| audio_boost | float | Optional | The audio boost | +| location | str | Optional | The location of the transcode session | +| media_buffer_size | float | Optional | The size of the media buffer | +| session | str | Optional | The session ID | +| add_debug_overlay | float | Optional | Whether to add a debug overlay or not | +| auto_adjust_quality | float | Optional | Whether to auto adjust quality or not | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.video.start_universal_transcode( + has_mde = 68681526.6739457, + path = 'path', + media_index = 38635292.15502611, + part_index = 56377101.56072605, + protocol = 'protocol', + fast_seek = -48546578.06404572, + direct_play = -4004169.7057704926, + direct_stream = -63607905.2844202, + subtitle_size = -95264880.14792101, + subtites = 'subtites', + audio_boost = 92032906.1650356, + location = 'location', + media_buffer_size = 43422490.76220566, + session = 'session', + add_debug_overlay = -40848683.38562142, + auto_adjust_quality = 63926343.42811155 +) + +pprint(vars(results)) + +``` + +### **get_timeline** +Get the timeline for a media item +- HTTP Method: GET +- Endpoint: /:/timeline + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | The rating key of the media item | +| key | str | Required | The key of the media item to get the timeline for | +| state | [State](/src/plexsdk/models/README.md#state) | Required | The state of the media item | +| has_mde | float | Required | Whether the media item has MDE | +| time | float | Required | The time of the media item | +| duration | float | Required | The duration of the media item | +| context | str | Required | The context of the media item | +| play_queue_item_id | float | Required | The play queue item ID of the media item | +| play_back_time | float | Required | The playback time of the media item | +| row | float | Required | The row of the media item | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.video.get_timeline( + rating_key = 45215776.89077535, + key = 'key', + state = 'playing', + has_mde = -17905072.874770716, + time = -7347989.028095856, + duration = 82330750.57461244, + context = 'context', + play_queue_item_id = -69611222.19233666, + play_back_time = -80252961.1853132, + row = -54653572.50923404 +) + +pprint(vars(results)) + +``` + + + + + +## License + +License: MIT. See license in LICENSE. diff --git a/src/plexsdk/__init__.py b/src/plexsdk/__init__.py new file mode 100644 index 0000000..78ac085 --- /dev/null +++ b/src/plexsdk/__init__.py @@ -0,0 +1,2 @@ +from .sdk import PlexSDK +from .net.environment import Environment diff --git a/src/plexsdk/hooks/__init__.py b/src/plexsdk/hooks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/plexsdk/hooks/hook.py b/src/plexsdk/hooks/hook.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/plexsdk/hooks/hook.py @@ -0,0 +1 @@ + diff --git a/src/plexsdk/models/Download.py b/src/plexsdk/models/Download.py new file mode 100644 index 0000000..4cb5ba2 --- /dev/null +++ b/src/plexsdk/models/Download.py @@ -0,0 +1,7 @@ +from enum import Enum + +class Download(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, Download._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/Force.py b/src/plexsdk/models/Force.py new file mode 100644 index 0000000..416c472 --- /dev/null +++ b/src/plexsdk/models/Force.py @@ -0,0 +1,7 @@ +from enum import Enum + +class Force(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, Force._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/GetAvailableClientsResponse.py b/src/plexsdk/models/GetAvailableClientsResponse.py new file mode 100644 index 0000000..ade22a2 --- /dev/null +++ b/src/plexsdk/models/GetAvailableClientsResponse.py @@ -0,0 +1,32 @@ +from .base import BaseModel +from typing import List + + +class MediaContainer(BaseModel): + def __init__(self, size: float = None, Server: list = None, **kwargs): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + Server: list of dict + """ + if size is not None: + self.size = size + if Server is not None: + self.Server = Server + + +class GetAvailableClientsResponseItem(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetAvailableClientsResponseItem + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer + + +GetAvailableClientsResponse = List[GetAvailableClientsResponseItem] diff --git a/src/plexsdk/models/GetButlerTasksResponse.py b/src/plexsdk/models/GetButlerTasksResponse.py new file mode 100644 index 0000000..03e06ac --- /dev/null +++ b/src/plexsdk/models/GetButlerTasksResponse.py @@ -0,0 +1,62 @@ +from .base import BaseModel +from typing import List + + +class ButlerTasksButlerTask(BaseModel): + def __init__( + self, + name: str = None, + interval: float = None, + scheduleRandomized: bool = None, + enabled: bool = None, + title: str = None, + description: str = None, + **kwargs, + ): + """ + Initialize ButlerTasksButlerTask + Parameters: + ---------- + name: str + interval: float + scheduleRandomized: bool + enabled: bool + title: str + description: str + """ + if name is not None: + self.name = name + if interval is not None: + self.interval = interval + if scheduleRandomized is not None: + self.scheduleRandomized = scheduleRandomized + if enabled is not None: + self.enabled = enabled + if title is not None: + self.title = title + if description is not None: + self.description = description + + +class ButlerTasks(BaseModel): + def __init__(self, ButlerTask: List[ButlerTasksButlerTask] = None, **kwargs): + """ + Initialize ButlerTasks + Parameters: + ---------- + ButlerTask: list of ButlerTasksButlerTask + """ + if ButlerTask is not None: + self.ButlerTask = ButlerTask + + +class GetButlerTasksResponse(BaseModel): + def __init__(self, ButlerTasks: ButlerTasks = None, **kwargs): + """ + Initialize GetButlerTasksResponse + Parameters: + ---------- + ButlerTasks: ButlerTasks + """ + if ButlerTasks is not None: + self.ButlerTasks = ButlerTasks diff --git a/src/plexsdk/models/GetDevicesResponse.py b/src/plexsdk/models/GetDevicesResponse.py new file mode 100644 index 0000000..99db198 --- /dev/null +++ b/src/plexsdk/models/GetDevicesResponse.py @@ -0,0 +1,70 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerDevice(BaseModel): + def __init__( + self, + id: float = None, + name: str = None, + platform: str = None, + clientIdentifier: str = None, + createdAt: float = None, + **kwargs, + ): + """ + Initialize MediaContainerDevice + Parameters: + ---------- + id: float + name: str + platform: str + clientIdentifier: str + createdAt: float + """ + if id is not None: + self.id = id + if name is not None: + self.name = name + if platform is not None: + self.platform = platform + if clientIdentifier is not None: + self.clientIdentifier = clientIdentifier + if createdAt is not None: + self.createdAt = createdAt + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + identifier: str = None, + Device: List[MediaContainerDevice] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + identifier: str + Device: list of MediaContainerDevice + """ + if size is not None: + self.size = size + if identifier is not None: + self.identifier = identifier + if Device is not None: + self.Device = Device + + +class GetDevicesResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetDevicesResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetMyPlexAccountResponse.py b/src/plexsdk/models/GetMyPlexAccountResponse.py new file mode 100644 index 0000000..3bf0edd --- /dev/null +++ b/src/plexsdk/models/GetMyPlexAccountResponse.py @@ -0,0 +1,73 @@ +from .base import BaseModel + + +class MyPlex(BaseModel): + def __init__( + self, + authToken: str = None, + username: str = None, + mappingState: str = None, + mappingError: str = None, + signInState: str = None, + publicAddress: str = None, + publicPort: float = None, + privateAddress: str = None, + privatePort: float = None, + subscriptionFeatures: str = None, + subscriptionActive: bool = None, + subscriptionState: str = None, + **kwargs, + ): + """ + Initialize MyPlex + Parameters: + ---------- + authToken: str + username: str + mappingState: str + mappingError: str + signInState: str + publicAddress: str + publicPort: float + privateAddress: str + privatePort: float + subscriptionFeatures: str + subscriptionActive: bool + subscriptionState: str + """ + if authToken is not None: + self.authToken = authToken + if username is not None: + self.username = username + if mappingState is not None: + self.mappingState = mappingState + if mappingError is not None: + self.mappingError = mappingError + if signInState is not None: + self.signInState = signInState + if publicAddress is not None: + self.publicAddress = publicAddress + if publicPort is not None: + self.publicPort = publicPort + if privateAddress is not None: + self.privateAddress = privateAddress + if privatePort is not None: + self.privatePort = privatePort + if subscriptionFeatures is not None: + self.subscriptionFeatures = subscriptionFeatures + if subscriptionActive is not None: + self.subscriptionActive = subscriptionActive + if subscriptionState is not None: + self.subscriptionState = subscriptionState + + +class GetMyPlexAccountResponse(BaseModel): + def __init__(self, MyPlex: MyPlex = None, **kwargs): + """ + Initialize GetMyPlexAccountResponse + Parameters: + ---------- + MyPlex: MyPlex + """ + if MyPlex is not None: + self.MyPlex = MyPlex diff --git a/src/plexsdk/models/GetOnDeckResponse.py b/src/plexsdk/models/GetOnDeckResponse.py new file mode 100644 index 0000000..9b7eb9b --- /dev/null +++ b/src/plexsdk/models/GetOnDeckResponse.py @@ -0,0 +1,446 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerMetadataMediaPartStream(BaseModel): + def __init__( + self, + id: float = None, + streamType: float = None, + default: bool = None, + codec: str = None, + index: float = None, + bitrate: float = None, + language: str = None, + languageTag: str = None, + languageCode: str = None, + bitDepth: float = None, + chromaLocation: str = None, + chromaSubsampling: str = None, + codedHeight: float = None, + codedWidth: float = None, + colorRange: str = None, + frameRate: float = None, + height: float = None, + level: float = None, + profile: str = None, + refFrames: float = None, + width: float = None, + displayTitle: str = None, + extendedDisplayTitle: str = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMediaPartStream + Parameters: + ---------- + id: float + streamType: float + default: bool + codec: str + index: float + bitrate: float + language: str + languageTag: str + languageCode: str + bitDepth: float + chromaLocation: str + chromaSubsampling: str + codedHeight: float + codedWidth: float + colorRange: str + frameRate: float + height: float + level: float + profile: str + refFrames: float + width: float + displayTitle: str + extendedDisplayTitle: str + """ + if id is not None: + self.id = id + if streamType is not None: + self.streamType = streamType + if default is not None: + self.default = default + if codec is not None: + self.codec = codec + if index is not None: + self.index = index + if bitrate is not None: + self.bitrate = bitrate + if language is not None: + self.language = language + if languageTag is not None: + self.languageTag = languageTag + if languageCode is not None: + self.languageCode = languageCode + if bitDepth is not None: + self.bitDepth = bitDepth + if chromaLocation is not None: + self.chromaLocation = chromaLocation + if chromaSubsampling is not None: + self.chromaSubsampling = chromaSubsampling + if codedHeight is not None: + self.codedHeight = codedHeight + if codedWidth is not None: + self.codedWidth = codedWidth + if colorRange is not None: + self.colorRange = colorRange + if frameRate is not None: + self.frameRate = frameRate + if height is not None: + self.height = height + if level is not None: + self.level = level + if profile is not None: + self.profile = profile + if refFrames is not None: + self.refFrames = refFrames + if width is not None: + self.width = width + if displayTitle is not None: + self.displayTitle = displayTitle + if extendedDisplayTitle is not None: + self.extendedDisplayTitle = extendedDisplayTitle + + +class MediaContainerMetadataMediaPart(BaseModel): + def __init__( + self, + id: float = None, + key: str = None, + duration: float = None, + file: str = None, + size: float = None, + audioProfile: str = None, + container: str = None, + videoProfile: str = None, + Stream: List[MediaContainerMetadataMediaPartStream] = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMediaPart + Parameters: + ---------- + id: float + key: str + duration: float + file: str + size: float + audioProfile: str + container: str + videoProfile: str + Stream: list of MediaContainerMetadataMediaPartStream + """ + if id is not None: + self.id = id + if key is not None: + self.key = key + if duration is not None: + self.duration = duration + if file is not None: + self.file = file + if size is not None: + self.size = size + if audioProfile is not None: + self.audioProfile = audioProfile + if container is not None: + self.container = container + if videoProfile is not None: + self.videoProfile = videoProfile + if Stream is not None: + self.Stream = Stream + + +class MediaContainerMetadataMedia(BaseModel): + def __init__( + self, + id: float = None, + duration: float = None, + bitrate: float = None, + width: float = None, + height: float = None, + aspectRatio: float = None, + audioChannels: float = None, + audioCodec: str = None, + videoCodec: str = None, + videoResolution: str = None, + container: str = None, + videoFrameRate: str = None, + audioProfile: str = None, + videoProfile: str = None, + Part: List[MediaContainerMetadataMediaPart] = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMedia + Parameters: + ---------- + id: float + duration: float + bitrate: float + width: float + height: float + aspectRatio: float + audioChannels: float + audioCodec: str + videoCodec: str + videoResolution: str + container: str + videoFrameRate: str + audioProfile: str + videoProfile: str + Part: list of MediaContainerMetadataMediaPart + """ + if id is not None: + self.id = id + if duration is not None: + self.duration = duration + if bitrate is not None: + self.bitrate = bitrate + if width is not None: + self.width = width + if height is not None: + self.height = height + if aspectRatio is not None: + self.aspectRatio = aspectRatio + if audioChannels is not None: + self.audioChannels = audioChannels + if audioCodec is not None: + self.audioCodec = audioCodec + if videoCodec is not None: + self.videoCodec = videoCodec + if videoResolution is not None: + self.videoResolution = videoResolution + if container is not None: + self.container = container + if videoFrameRate is not None: + self.videoFrameRate = videoFrameRate + if audioProfile is not None: + self.audioProfile = audioProfile + if videoProfile is not None: + self.videoProfile = videoProfile + if Part is not None: + self.Part = Part + + +class MediaContainerMetadataGuid(BaseModel): + def __init__(self, id: str = None, **kwargs): + """ + Initialize MediaContainerMetadataGuid + Parameters: + ---------- + id: str + """ + if id is not None: + self.id = id + + +class MediaContainerMetadata(BaseModel): + def __init__( + self, + allowSync: bool = None, + librarySectionID: float = None, + librarySectionTitle: str = None, + librarySectionUUID: str = None, + ratingKey: float = None, + key: str = None, + parentRatingKey: float = None, + grandparentRatingKey: float = None, + guid: str = None, + parentGuid: str = None, + grandparentGuid: str = None, + title: str = None, + grandparentKey: str = None, + parentKey: str = None, + librarySectionKey: str = None, + grandparentTitle: str = None, + parentTitle: str = None, + contentRating: str = None, + summary: str = None, + index: float = None, + parentIndex: float = None, + lastViewedAt: float = None, + year: float = None, + thumb: str = None, + art: str = None, + parentThumb: str = None, + grandparentThumb: str = None, + grandparentArt: str = None, + grandparentTheme: str = None, + duration: float = None, + originallyAvailableAt: str = None, + addedAt: float = None, + updatedAt: float = None, + Media: List[MediaContainerMetadataMedia] = None, + Guid: List[MediaContainerMetadataGuid] = None, + type_: str = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadata + Parameters: + ---------- + allowSync: bool + librarySectionID: float + librarySectionTitle: str + librarySectionUUID: str + ratingKey: float + key: str + parentRatingKey: float + grandparentRatingKey: float + guid: str + parentGuid: str + grandparentGuid: str + title: str + grandparentKey: str + parentKey: str + librarySectionKey: str + grandparentTitle: str + parentTitle: str + contentRating: str + summary: str + index: float + parentIndex: float + lastViewedAt: float + year: float + thumb: str + art: str + parentThumb: str + grandparentThumb: str + grandparentArt: str + grandparentTheme: str + duration: float + originallyAvailableAt: str + addedAt: float + updatedAt: float + Media: list of MediaContainerMetadataMedia + Guid: list of MediaContainerMetadataGuid + type_: str + """ + if allowSync is not None: + self.allowSync = allowSync + if librarySectionID is not None: + self.librarySectionID = librarySectionID + if librarySectionTitle is not None: + self.librarySectionTitle = librarySectionTitle + if librarySectionUUID is not None: + self.librarySectionUUID = librarySectionUUID + if ratingKey is not None: + self.ratingKey = ratingKey + if key is not None: + self.key = key + if parentRatingKey is not None: + self.parentRatingKey = parentRatingKey + if grandparentRatingKey is not None: + self.grandparentRatingKey = grandparentRatingKey + if guid is not None: + self.guid = guid + if parentGuid is not None: + self.parentGuid = parentGuid + if grandparentGuid is not None: + self.grandparentGuid = grandparentGuid + if title is not None: + self.title = title + if grandparentKey is not None: + self.grandparentKey = grandparentKey + if parentKey is not None: + self.parentKey = parentKey + if librarySectionKey is not None: + self.librarySectionKey = librarySectionKey + if grandparentTitle is not None: + self.grandparentTitle = grandparentTitle + if parentTitle is not None: + self.parentTitle = parentTitle + if contentRating is not None: + self.contentRating = contentRating + if summary is not None: + self.summary = summary + if index is not None: + self.index = index + if parentIndex is not None: + self.parentIndex = parentIndex + if lastViewedAt is not None: + self.lastViewedAt = lastViewedAt + if year is not None: + self.year = year + if thumb is not None: + self.thumb = thumb + if art is not None: + self.art = art + if parentThumb is not None: + self.parentThumb = parentThumb + if grandparentThumb is not None: + self.grandparentThumb = grandparentThumb + if grandparentArt is not None: + self.grandparentArt = grandparentArt + if grandparentTheme is not None: + self.grandparentTheme = grandparentTheme + if duration is not None: + self.duration = duration + if originallyAvailableAt is not None: + self.originallyAvailableAt = originallyAvailableAt + if addedAt is not None: + self.addedAt = addedAt + if updatedAt is not None: + self.updatedAt = updatedAt + if Media is not None: + self.Media = Media + if Guid is not None: + self.Guid = Guid + if type_ is not None: + self.type_ = type_ + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + allowSync: bool = None, + identifier: str = None, + mediaTagPrefix: str = None, + mediaTagVersion: float = None, + mixedParents: bool = None, + Metadata: List[MediaContainerMetadata] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + allowSync: bool + identifier: str + mediaTagPrefix: str + mediaTagVersion: float + mixedParents: bool + Metadata: list of MediaContainerMetadata + """ + if size is not None: + self.size = size + if allowSync is not None: + self.allowSync = allowSync + if identifier is not None: + self.identifier = identifier + if mediaTagPrefix is not None: + self.mediaTagPrefix = mediaTagPrefix + if mediaTagVersion is not None: + self.mediaTagVersion = mediaTagVersion + if mixedParents is not None: + self.mixedParents = mixedParents + if Metadata is not None: + self.Metadata = Metadata + + +class GetOnDeckResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetOnDeckResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetRecentlyAddedResponse.py b/src/plexsdk/models/GetRecentlyAddedResponse.py new file mode 100644 index 0000000..e437757 --- /dev/null +++ b/src/plexsdk/models/GetRecentlyAddedResponse.py @@ -0,0 +1,382 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerMetadataMediaPart(BaseModel): + def __init__( + self, + id: float = None, + key: str = None, + duration: float = None, + file: str = None, + size: float = None, + container: str = None, + has64bitOffsets: bool = None, + hasThumbnail: float = None, + optimizedForStreaming: bool = None, + videoProfile: str = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMediaPart + Parameters: + ---------- + id: float + key: str + duration: float + file: str + size: float + container: str + has64bitOffsets: bool + hasThumbnail: float + optimizedForStreaming: bool + videoProfile: str + """ + if id is not None: + self.id = id + if key is not None: + self.key = key + if duration is not None: + self.duration = duration + if file is not None: + self.file = file + if size is not None: + self.size = size + if container is not None: + self.container = container + if has64bitOffsets is not None: + self.has64bitOffsets = has64bitOffsets + if hasThumbnail is not None: + self.hasThumbnail = hasThumbnail + if optimizedForStreaming is not None: + self.optimizedForStreaming = optimizedForStreaming + if videoProfile is not None: + self.videoProfile = videoProfile + + +class MediaContainerMetadataMedia(BaseModel): + def __init__( + self, + id: float = None, + duration: float = None, + bitrate: float = None, + width: float = None, + height: float = None, + aspectRatio: float = None, + audioChannels: float = None, + audioCodec: str = None, + videoCodec: str = None, + videoResolution: float = None, + container: str = None, + videoFrameRate: str = None, + optimizedForStreaming: float = None, + has64bitOffsets: bool = None, + videoProfile: str = None, + Part: List[MediaContainerMetadataMediaPart] = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMedia + Parameters: + ---------- + id: float + duration: float + bitrate: float + width: float + height: float + aspectRatio: float + audioChannels: float + audioCodec: str + videoCodec: str + videoResolution: float + container: str + videoFrameRate: str + optimizedForStreaming: float + has64bitOffsets: bool + videoProfile: str + Part: list of MediaContainerMetadataMediaPart + """ + if id is not None: + self.id = id + if duration is not None: + self.duration = duration + if bitrate is not None: + self.bitrate = bitrate + if width is not None: + self.width = width + if height is not None: + self.height = height + if aspectRatio is not None: + self.aspectRatio = aspectRatio + if audioChannels is not None: + self.audioChannels = audioChannels + if audioCodec is not None: + self.audioCodec = audioCodec + if videoCodec is not None: + self.videoCodec = videoCodec + if videoResolution is not None: + self.videoResolution = videoResolution + if container is not None: + self.container = container + if videoFrameRate is not None: + self.videoFrameRate = videoFrameRate + if optimizedForStreaming is not None: + self.optimizedForStreaming = optimizedForStreaming + if has64bitOffsets is not None: + self.has64bitOffsets = has64bitOffsets + if videoProfile is not None: + self.videoProfile = videoProfile + if Part is not None: + self.Part = Part + + +class MediaContainerMetadataGenre(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataGenre + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataDirector(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataDirector + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataWriter(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataWriter + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataCountry(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataCountry + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataRole(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataRole + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadata(BaseModel): + def __init__( + self, + allowSync: bool = None, + librarySectionID: float = None, + librarySectionTitle: str = None, + librarySectionUUID: str = None, + ratingKey: float = None, + key: str = None, + guid: str = None, + studio: str = None, + title: str = None, + contentRating: str = None, + summary: str = None, + rating: float = None, + audienceRating: float = None, + year: float = None, + tagline: str = None, + thumb: str = None, + art: str = None, + duration: float = None, + originallyAvailableAt: str = None, + addedAt: float = None, + updatedAt: float = None, + audienceRatingImage: str = None, + chapterSource: str = None, + primaryExtraKey: str = None, + ratingImage: str = None, + Media: List[MediaContainerMetadataMedia] = None, + Genre: List[MediaContainerMetadataGenre] = None, + Director: List[MediaContainerMetadataDirector] = None, + Writer: List[MediaContainerMetadataWriter] = None, + Country: List[MediaContainerMetadataCountry] = None, + Role: List[MediaContainerMetadataRole] = None, + type_: str = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadata + Parameters: + ---------- + allowSync: bool + librarySectionID: float + librarySectionTitle: str + librarySectionUUID: str + ratingKey: float + key: str + guid: str + studio: str + title: str + contentRating: str + summary: str + rating: float + audienceRating: float + year: float + tagline: str + thumb: str + art: str + duration: float + originallyAvailableAt: str + addedAt: float + updatedAt: float + audienceRatingImage: str + chapterSource: str + primaryExtraKey: str + ratingImage: str + Media: list of MediaContainerMetadataMedia + Genre: list of MediaContainerMetadataGenre + Director: list of MediaContainerMetadataDirector + Writer: list of MediaContainerMetadataWriter + Country: list of MediaContainerMetadataCountry + Role: list of MediaContainerMetadataRole + type_: str + """ + if allowSync is not None: + self.allowSync = allowSync + if librarySectionID is not None: + self.librarySectionID = librarySectionID + if librarySectionTitle is not None: + self.librarySectionTitle = librarySectionTitle + if librarySectionUUID is not None: + self.librarySectionUUID = librarySectionUUID + if ratingKey is not None: + self.ratingKey = ratingKey + if key is not None: + self.key = key + if guid is not None: + self.guid = guid + if studio is not None: + self.studio = studio + if title is not None: + self.title = title + if contentRating is not None: + self.contentRating = contentRating + if summary is not None: + self.summary = summary + if rating is not None: + self.rating = rating + if audienceRating is not None: + self.audienceRating = audienceRating + if year is not None: + self.year = year + if tagline is not None: + self.tagline = tagline + if thumb is not None: + self.thumb = thumb + if art is not None: + self.art = art + if duration is not None: + self.duration = duration + if originallyAvailableAt is not None: + self.originallyAvailableAt = originallyAvailableAt + if addedAt is not None: + self.addedAt = addedAt + if updatedAt is not None: + self.updatedAt = updatedAt + if audienceRatingImage is not None: + self.audienceRatingImage = audienceRatingImage + if chapterSource is not None: + self.chapterSource = chapterSource + if primaryExtraKey is not None: + self.primaryExtraKey = primaryExtraKey + if ratingImage is not None: + self.ratingImage = ratingImage + if Media is not None: + self.Media = Media + if Genre is not None: + self.Genre = Genre + if Director is not None: + self.Director = Director + if Writer is not None: + self.Writer = Writer + if Country is not None: + self.Country = Country + if Role is not None: + self.Role = Role + if type_ is not None: + self.type_ = type_ + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + allowSync: bool = None, + identifier: str = None, + mediaTagPrefix: str = None, + mediaTagVersion: float = None, + mixedParents: bool = None, + Metadata: List[MediaContainerMetadata] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + allowSync: bool + identifier: str + mediaTagPrefix: str + mediaTagVersion: float + mixedParents: bool + Metadata: list of MediaContainerMetadata + """ + if size is not None: + self.size = size + if allowSync is not None: + self.allowSync = allowSync + if identifier is not None: + self.identifier = identifier + if mediaTagPrefix is not None: + self.mediaTagPrefix = mediaTagPrefix + if mediaTagVersion is not None: + self.mediaTagVersion = mediaTagVersion + if mixedParents is not None: + self.mixedParents = mixedParents + if Metadata is not None: + self.Metadata = Metadata + + +class GetRecentlyAddedResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetRecentlyAddedResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetSearchResultsResponse.py b/src/plexsdk/models/GetSearchResultsResponse.py new file mode 100644 index 0000000..728c04a --- /dev/null +++ b/src/plexsdk/models/GetSearchResultsResponse.py @@ -0,0 +1,392 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerMetadataMediaPart(BaseModel): + def __init__( + self, + id: float = None, + key: str = None, + duration: float = None, + file: str = None, + size: float = None, + audioProfile: str = None, + container: str = None, + videoProfile: str = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMediaPart + Parameters: + ---------- + id: float + key: str + duration: float + file: str + size: float + audioProfile: str + container: str + videoProfile: str + """ + if id is not None: + self.id = id + if key is not None: + self.key = key + if duration is not None: + self.duration = duration + if file is not None: + self.file = file + if size is not None: + self.size = size + if audioProfile is not None: + self.audioProfile = audioProfile + if container is not None: + self.container = container + if videoProfile is not None: + self.videoProfile = videoProfile + + +class MediaContainerMetadataMedia(BaseModel): + def __init__( + self, + id: float = None, + duration: float = None, + bitrate: float = None, + width: float = None, + height: float = None, + aspectRatio: float = None, + audioChannels: float = None, + audioCodec: str = None, + videoCodec: str = None, + videoResolution: float = None, + container: str = None, + videoFrameRate: str = None, + audioProfile: str = None, + videoProfile: str = None, + Part: List[MediaContainerMetadataMediaPart] = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadataMedia + Parameters: + ---------- + id: float + duration: float + bitrate: float + width: float + height: float + aspectRatio: float + audioChannels: float + audioCodec: str + videoCodec: str + videoResolution: float + container: str + videoFrameRate: str + audioProfile: str + videoProfile: str + Part: list of MediaContainerMetadataMediaPart + """ + if id is not None: + self.id = id + if duration is not None: + self.duration = duration + if bitrate is not None: + self.bitrate = bitrate + if width is not None: + self.width = width + if height is not None: + self.height = height + if aspectRatio is not None: + self.aspectRatio = aspectRatio + if audioChannels is not None: + self.audioChannels = audioChannels + if audioCodec is not None: + self.audioCodec = audioCodec + if videoCodec is not None: + self.videoCodec = videoCodec + if videoResolution is not None: + self.videoResolution = videoResolution + if container is not None: + self.container = container + if videoFrameRate is not None: + self.videoFrameRate = videoFrameRate + if audioProfile is not None: + self.audioProfile = audioProfile + if videoProfile is not None: + self.videoProfile = videoProfile + if Part is not None: + self.Part = Part + + +class MediaContainerMetadataGenre(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataGenre + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataDirector(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataDirector + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataWriter(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataWriter + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataCountry(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataCountry + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadataRole(BaseModel): + def __init__(self, tag: str = None, **kwargs): + """ + Initialize MediaContainerMetadataRole + Parameters: + ---------- + tag: str + """ + if tag is not None: + self.tag = tag + + +class MediaContainerMetadata(BaseModel): + def __init__( + self, + allowSync: bool = None, + librarySectionID: float = None, + librarySectionTitle: str = None, + librarySectionUUID: str = None, + personal: bool = None, + sourceTitle: str = None, + ratingKey: float = None, + key: str = None, + guid: str = None, + studio: str = None, + title: str = None, + contentRating: str = None, + summary: str = None, + rating: float = None, + audienceRating: float = None, + year: float = None, + tagline: str = None, + thumb: str = None, + art: str = None, + duration: float = None, + originallyAvailableAt: str = None, + addedAt: float = None, + updatedAt: float = None, + audienceRatingImage: str = None, + chapterSource: str = None, + primaryExtraKey: str = None, + ratingImage: str = None, + Media: List[MediaContainerMetadataMedia] = None, + Genre: List[MediaContainerMetadataGenre] = None, + Director: List[MediaContainerMetadataDirector] = None, + Writer: List[MediaContainerMetadataWriter] = None, + Country: List[MediaContainerMetadataCountry] = None, + Role: List[MediaContainerMetadataRole] = None, + type_: str = None, + **kwargs, + ): + """ + Initialize MediaContainerMetadata + Parameters: + ---------- + allowSync: bool + librarySectionID: float + librarySectionTitle: str + librarySectionUUID: str + personal: bool + sourceTitle: str + ratingKey: float + key: str + guid: str + studio: str + title: str + contentRating: str + summary: str + rating: float + audienceRating: float + year: float + tagline: str + thumb: str + art: str + duration: float + originallyAvailableAt: str + addedAt: float + updatedAt: float + audienceRatingImage: str + chapterSource: str + primaryExtraKey: str + ratingImage: str + Media: list of MediaContainerMetadataMedia + Genre: list of MediaContainerMetadataGenre + Director: list of MediaContainerMetadataDirector + Writer: list of MediaContainerMetadataWriter + Country: list of MediaContainerMetadataCountry + Role: list of MediaContainerMetadataRole + type_: str + """ + if allowSync is not None: + self.allowSync = allowSync + if librarySectionID is not None: + self.librarySectionID = librarySectionID + if librarySectionTitle is not None: + self.librarySectionTitle = librarySectionTitle + if librarySectionUUID is not None: + self.librarySectionUUID = librarySectionUUID + if personal is not None: + self.personal = personal + if sourceTitle is not None: + self.sourceTitle = sourceTitle + if ratingKey is not None: + self.ratingKey = ratingKey + if key is not None: + self.key = key + if guid is not None: + self.guid = guid + if studio is not None: + self.studio = studio + if title is not None: + self.title = title + if contentRating is not None: + self.contentRating = contentRating + if summary is not None: + self.summary = summary + if rating is not None: + self.rating = rating + if audienceRating is not None: + self.audienceRating = audienceRating + if year is not None: + self.year = year + if tagline is not None: + self.tagline = tagline + if thumb is not None: + self.thumb = thumb + if art is not None: + self.art = art + if duration is not None: + self.duration = duration + if originallyAvailableAt is not None: + self.originallyAvailableAt = originallyAvailableAt + if addedAt is not None: + self.addedAt = addedAt + if updatedAt is not None: + self.updatedAt = updatedAt + if audienceRatingImage is not None: + self.audienceRatingImage = audienceRatingImage + if chapterSource is not None: + self.chapterSource = chapterSource + if primaryExtraKey is not None: + self.primaryExtraKey = primaryExtraKey + if ratingImage is not None: + self.ratingImage = ratingImage + if Media is not None: + self.Media = Media + if Genre is not None: + self.Genre = Genre + if Director is not None: + self.Director = Director + if Writer is not None: + self.Writer = Writer + if Country is not None: + self.Country = Country + if Role is not None: + self.Role = Role + if type_ is not None: + self.type_ = type_ + + +class MediaContainerProvider(BaseModel): + def __init__(self, key: str = None, title: str = None, type_: str = None, **kwargs): + """ + Initialize MediaContainerProvider + Parameters: + ---------- + key: str + title: str + type_: str + """ + if key is not None: + self.key = key + if title is not None: + self.title = title + if type_ is not None: + self.type_ = type_ + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + identifier: str = None, + mediaTagPrefix: str = None, + mediaTagVersion: float = None, + Metadata: List[MediaContainerMetadata] = None, + Provider: List[MediaContainerProvider] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + identifier: str + mediaTagPrefix: str + mediaTagVersion: float + Metadata: list of MediaContainerMetadata + Provider: list of MediaContainerProvider + """ + if size is not None: + self.size = size + if identifier is not None: + self.identifier = identifier + if mediaTagPrefix is not None: + self.mediaTagPrefix = mediaTagPrefix + if mediaTagVersion is not None: + self.mediaTagVersion = mediaTagVersion + if Metadata is not None: + self.Metadata = Metadata + if Provider is not None: + self.Provider = Provider + + +class GetSearchResultsResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetSearchResultsResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetServerActivitiesResponse.py b/src/plexsdk/models/GetServerActivitiesResponse.py new file mode 100644 index 0000000..a46032b --- /dev/null +++ b/src/plexsdk/models/GetServerActivitiesResponse.py @@ -0,0 +1,90 @@ +from .base import BaseModel +from typing import List + + +class Context(BaseModel): + def __init__(self, librarySectionID: str = None, **kwargs): + """ + Initialize Context + Parameters: + ---------- + librarySectionID: str + """ + if librarySectionID is not None: + self.librarySectionID = librarySectionID + + +class MediaContainerActivity(BaseModel): + def __init__( + self, + uuid: str = None, + cancellable: bool = None, + userID: float = None, + title: str = None, + subtitle: str = None, + progress: float = None, + Context: Context = None, + type_: str = None, + **kwargs, + ): + """ + Initialize MediaContainerActivity + Parameters: + ---------- + uuid: str + cancellable: bool + userID: float + title: str + subtitle: str + progress: float + Context: Context + type_: str + """ + if uuid is not None: + self.uuid = uuid + if cancellable is not None: + self.cancellable = cancellable + if userID is not None: + self.userID = userID + if title is not None: + self.title = title + if subtitle is not None: + self.subtitle = subtitle + if progress is not None: + self.progress = progress + if Context is not None: + self.Context = Context + if type_ is not None: + self.type_ = type_ + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + Activity: List[MediaContainerActivity] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + Activity: list of MediaContainerActivity + """ + if size is not None: + self.size = size + if Activity is not None: + self.Activity = Activity + + +class GetServerActivitiesResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetServerActivitiesResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetServerCapabilitiesResponse.py b/src/plexsdk/models/GetServerCapabilitiesResponse.py new file mode 100644 index 0000000..aac8907 --- /dev/null +++ b/src/plexsdk/models/GetServerCapabilitiesResponse.py @@ -0,0 +1,250 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerDirectory(BaseModel): + def __init__( + self, count: float = None, key: str = None, title: str = None, **kwargs + ): + """ + Initialize MediaContainerDirectory + Parameters: + ---------- + count: float + key: str + title: str + """ + if count is not None: + self.count = count + if key is not None: + self.key = key + if title is not None: + self.title = title + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + allowCameraUpload: bool = None, + allowChannelAccess: bool = None, + allowMediaDeletion: bool = None, + allowSharing: bool = None, + allowSync: bool = None, + allowTuners: bool = None, + backgroundProcessing: bool = None, + certificate: bool = None, + companionProxy: bool = None, + countryCode: str = None, + diagnostics: str = None, + eventStream: bool = None, + friendlyName: str = None, + hubSearch: bool = None, + itemClusters: bool = None, + livetv: float = None, + machineIdentifier: str = None, + mediaProviders: bool = None, + multiuser: bool = None, + musicAnalysis: float = None, + myPlex: bool = None, + myPlexMappingState: str = None, + myPlexSigninState: str = None, + myPlexSubscription: bool = None, + myPlexUsername: str = None, + offlineTranscode: float = None, + ownerFeatures: str = None, + photoAutoTag: bool = None, + platform: str = None, + platformVersion: str = None, + pluginHost: bool = None, + pushNotifications: bool = None, + readOnlyLibraries: bool = None, + streamingBrainABRVersion: float = None, + streamingBrainVersion: float = None, + sync: bool = None, + transcoderActiveVideoSessions: float = None, + transcoderAudio: bool = None, + transcoderLyrics: bool = None, + transcoderPhoto: bool = None, + transcoderSubtitles: bool = None, + transcoderVideo: bool = None, + transcoderVideoBitrates: str = None, + transcoderVideoQualities: str = None, + transcoderVideoResolutions: str = None, + updatedAt: float = None, + updater: bool = None, + version: str = None, + voiceSearch: bool = None, + Directory: List[MediaContainerDirectory] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + allowCameraUpload: bool + allowChannelAccess: bool + allowMediaDeletion: bool + allowSharing: bool + allowSync: bool + allowTuners: bool + backgroundProcessing: bool + certificate: bool + companionProxy: bool + countryCode: str + diagnostics: str + eventStream: bool + friendlyName: str + hubSearch: bool + itemClusters: bool + livetv: float + machineIdentifier: str + mediaProviders: bool + multiuser: bool + musicAnalysis: float + myPlex: bool + myPlexMappingState: str + myPlexSigninState: str + myPlexSubscription: bool + myPlexUsername: str + offlineTranscode: float + ownerFeatures: str + photoAutoTag: bool + platform: str + platformVersion: str + pluginHost: bool + pushNotifications: bool + readOnlyLibraries: bool + streamingBrainABRVersion: float + streamingBrainVersion: float + sync: bool + transcoderActiveVideoSessions: float + transcoderAudio: bool + transcoderLyrics: bool + transcoderPhoto: bool + transcoderSubtitles: bool + transcoderVideo: bool + transcoderVideoBitrates: str + transcoderVideoQualities: str + transcoderVideoResolutions: str + updatedAt: float + updater: bool + version: str + voiceSearch: bool + Directory: list of MediaContainerDirectory + """ + if size is not None: + self.size = size + if allowCameraUpload is not None: + self.allowCameraUpload = allowCameraUpload + if allowChannelAccess is not None: + self.allowChannelAccess = allowChannelAccess + if allowMediaDeletion is not None: + self.allowMediaDeletion = allowMediaDeletion + if allowSharing is not None: + self.allowSharing = allowSharing + if allowSync is not None: + self.allowSync = allowSync + if allowTuners is not None: + self.allowTuners = allowTuners + if backgroundProcessing is not None: + self.backgroundProcessing = backgroundProcessing + if certificate is not None: + self.certificate = certificate + if companionProxy is not None: + self.companionProxy = companionProxy + if countryCode is not None: + self.countryCode = countryCode + if diagnostics is not None: + self.diagnostics = diagnostics + if eventStream is not None: + self.eventStream = eventStream + if friendlyName is not None: + self.friendlyName = friendlyName + if hubSearch is not None: + self.hubSearch = hubSearch + if itemClusters is not None: + self.itemClusters = itemClusters + if livetv is not None: + self.livetv = livetv + if machineIdentifier is not None: + self.machineIdentifier = machineIdentifier + if mediaProviders is not None: + self.mediaProviders = mediaProviders + if multiuser is not None: + self.multiuser = multiuser + if musicAnalysis is not None: + self.musicAnalysis = musicAnalysis + if myPlex is not None: + self.myPlex = myPlex + if myPlexMappingState is not None: + self.myPlexMappingState = myPlexMappingState + if myPlexSigninState is not None: + self.myPlexSigninState = myPlexSigninState + if myPlexSubscription is not None: + self.myPlexSubscription = myPlexSubscription + if myPlexUsername is not None: + self.myPlexUsername = myPlexUsername + if offlineTranscode is not None: + self.offlineTranscode = offlineTranscode + if ownerFeatures is not None: + self.ownerFeatures = ownerFeatures + if photoAutoTag is not None: + self.photoAutoTag = photoAutoTag + if platform is not None: + self.platform = platform + if platformVersion is not None: + self.platformVersion = platformVersion + if pluginHost is not None: + self.pluginHost = pluginHost + if pushNotifications is not None: + self.pushNotifications = pushNotifications + if readOnlyLibraries is not None: + self.readOnlyLibraries = readOnlyLibraries + if streamingBrainABRVersion is not None: + self.streamingBrainABRVersion = streamingBrainABRVersion + if streamingBrainVersion is not None: + self.streamingBrainVersion = streamingBrainVersion + if sync is not None: + self.sync = sync + if transcoderActiveVideoSessions is not None: + self.transcoderActiveVideoSessions = transcoderActiveVideoSessions + if transcoderAudio is not None: + self.transcoderAudio = transcoderAudio + if transcoderLyrics is not None: + self.transcoderLyrics = transcoderLyrics + if transcoderPhoto is not None: + self.transcoderPhoto = transcoderPhoto + if transcoderSubtitles is not None: + self.transcoderSubtitles = transcoderSubtitles + if transcoderVideo is not None: + self.transcoderVideo = transcoderVideo + if transcoderVideoBitrates is not None: + self.transcoderVideoBitrates = transcoderVideoBitrates + if transcoderVideoQualities is not None: + self.transcoderVideoQualities = transcoderVideoQualities + if transcoderVideoResolutions is not None: + self.transcoderVideoResolutions = transcoderVideoResolutions + if updatedAt is not None: + self.updatedAt = updatedAt + if updater is not None: + self.updater = updater + if version is not None: + self.version = version + if voiceSearch is not None: + self.voiceSearch = voiceSearch + if Directory is not None: + self.Directory = Directory + + +class GetServerCapabilitiesResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetServerCapabilitiesResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetServerIdentityResponse.py b/src/plexsdk/models/GetServerIdentityResponse.py new file mode 100644 index 0000000..68be9a2 --- /dev/null +++ b/src/plexsdk/models/GetServerIdentityResponse.py @@ -0,0 +1,41 @@ +from .base import BaseModel + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + claimed: bool = None, + machineIdentifier: str = None, + version: str = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + claimed: bool + machineIdentifier: str + version: str + """ + if size is not None: + self.size = size + if claimed is not None: + self.claimed = claimed + if machineIdentifier is not None: + self.machineIdentifier = machineIdentifier + if version is not None: + self.version = version + + +class GetServerIdentityResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetServerIdentityResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetServerListResponse.py b/src/plexsdk/models/GetServerListResponse.py new file mode 100644 index 0000000..75edb9f --- /dev/null +++ b/src/plexsdk/models/GetServerListResponse.py @@ -0,0 +1,67 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerServer(BaseModel): + def __init__( + self, + name: str = None, + host: str = None, + address: str = None, + port: float = None, + machineIdentifier: str = None, + version: str = None, + **kwargs, + ): + """ + Initialize MediaContainerServer + Parameters: + ---------- + name: str + host: str + address: str + port: float + machineIdentifier: str + version: str + """ + if name is not None: + self.name = name + if host is not None: + self.host = host + if address is not None: + self.address = address + if port is not None: + self.port = port + if machineIdentifier is not None: + self.machineIdentifier = machineIdentifier + if version is not None: + self.version = version + + +class MediaContainer(BaseModel): + def __init__( + self, size: float = None, Server: List[MediaContainerServer] = None, **kwargs + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + Server: list of MediaContainerServer + """ + if size is not None: + self.size = size + if Server is not None: + self.Server = Server + + +class GetServerListResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetServerListResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/GetTranscodeSessionsResponse.py b/src/plexsdk/models/GetTranscodeSessionsResponse.py new file mode 100644 index 0000000..2342429 --- /dev/null +++ b/src/plexsdk/models/GetTranscodeSessionsResponse.py @@ -0,0 +1,134 @@ +from .base import BaseModel +from typing import List + + +class MediaContainerTranscodeSession(BaseModel): + def __init__( + self, + key: str = None, + throttled: bool = None, + complete: bool = None, + progress: float = None, + size: float = None, + speed: float = None, + error: bool = None, + duration: float = None, + context: str = None, + sourceVideoCodec: str = None, + sourceAudioCodec: str = None, + videoDecision: str = None, + audioDecision: str = None, + protocol: str = None, + container: str = None, + videoCodec: str = None, + audioCodec: str = None, + audioChannels: float = None, + transcodeHwRequested: bool = None, + timeStamp: float = None, + maxOffsetAvailable: float = None, + minOffsetAvailable: float = None, + **kwargs, + ): + """ + Initialize MediaContainerTranscodeSession + Parameters: + ---------- + key: str + throttled: bool + complete: bool + progress: float + size: float + speed: float + error: bool + duration: float + context: str + sourceVideoCodec: str + sourceAudioCodec: str + videoDecision: str + audioDecision: str + protocol: str + container: str + videoCodec: str + audioCodec: str + audioChannels: float + transcodeHwRequested: bool + timeStamp: float + maxOffsetAvailable: float + minOffsetAvailable: float + """ + if key is not None: + self.key = key + if throttled is not None: + self.throttled = throttled + if complete is not None: + self.complete = complete + if progress is not None: + self.progress = progress + if size is not None: + self.size = size + if speed is not None: + self.speed = speed + if error is not None: + self.error = error + if duration is not None: + self.duration = duration + if context is not None: + self.context = context + if sourceVideoCodec is not None: + self.sourceVideoCodec = sourceVideoCodec + if sourceAudioCodec is not None: + self.sourceAudioCodec = sourceAudioCodec + if videoDecision is not None: + self.videoDecision = videoDecision + if audioDecision is not None: + self.audioDecision = audioDecision + if protocol is not None: + self.protocol = protocol + if container is not None: + self.container = container + if videoCodec is not None: + self.videoCodec = videoCodec + if audioCodec is not None: + self.audioCodec = audioCodec + if audioChannels is not None: + self.audioChannels = audioChannels + if transcodeHwRequested is not None: + self.transcodeHwRequested = transcodeHwRequested + if timeStamp is not None: + self.timeStamp = timeStamp + if maxOffsetAvailable is not None: + self.maxOffsetAvailable = maxOffsetAvailable + if minOffsetAvailable is not None: + self.minOffsetAvailable = minOffsetAvailable + + +class MediaContainer(BaseModel): + def __init__( + self, + size: float = None, + TranscodeSession: List[MediaContainerTranscodeSession] = None, + **kwargs, + ): + """ + Initialize MediaContainer + Parameters: + ---------- + size: float + TranscodeSession: list of MediaContainerTranscodeSession + """ + if size is not None: + self.size = size + if TranscodeSession is not None: + self.TranscodeSession = TranscodeSession + + +class GetTranscodeSessionsResponse(BaseModel): + def __init__(self, MediaContainer: MediaContainer = None, **kwargs): + """ + Initialize GetTranscodeSessionsResponse + Parameters: + ---------- + MediaContainer: MediaContainer + """ + if MediaContainer is not None: + self.MediaContainer = MediaContainer diff --git a/src/plexsdk/models/IncludeDetails.py b/src/plexsdk/models/IncludeDetails.py new file mode 100644 index 0000000..825461e --- /dev/null +++ b/src/plexsdk/models/IncludeDetails.py @@ -0,0 +1,7 @@ +from enum import Enum + +class IncludeDetails(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, IncludeDetails._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/Level.py b/src/plexsdk/models/Level.py new file mode 100644 index 0000000..994396c --- /dev/null +++ b/src/plexsdk/models/Level.py @@ -0,0 +1,10 @@ +from enum import Enum + +class Level(Enum): + = "0" + V1 = "1" + V2 = "2" + V3 = "3" + V4 = "4" + def list(): + return list(map(lambda x: x.value, Level._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/MinSize.py b/src/plexsdk/models/MinSize.py new file mode 100644 index 0000000..e491ad7 --- /dev/null +++ b/src/plexsdk/models/MinSize.py @@ -0,0 +1,7 @@ +from enum import Enum + +class MinSize(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, MinSize._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/OnlyTransient.py b/src/plexsdk/models/OnlyTransient.py new file mode 100644 index 0000000..b059f1e --- /dev/null +++ b/src/plexsdk/models/OnlyTransient.py @@ -0,0 +1,7 @@ +from enum import Enum + +class OnlyTransient(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, OnlyTransient._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/PlaylistType.py b/src/plexsdk/models/PlaylistType.py new file mode 100644 index 0000000..6a2b15f --- /dev/null +++ b/src/plexsdk/models/PlaylistType.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class PlaylistType(Enum): + AUDIO = "audio" + VIDEO = "video" + PHOTO = "photo" + + def list(): + return list(map(lambda x: x.value, PlaylistType._member_map_.values())) diff --git a/src/plexsdk/models/README.md b/src/plexsdk/models/README.md new file mode 100644 index 0000000..7b315de --- /dev/null +++ b/src/plexsdk/models/README.md @@ -0,0 +1,90 @@ +# PlexSDK Models + +A list of all models. +- [Level](#level) +- [Upscale](#upscale) +- [Type](#type) +- [Smart](#smart) +- [Force](#force) +- [SecurityType](#securitytype) +- [Scope](#scope) +- [Download](#download) +- [Tonight](#tonight) +- [Skip](#skip) +- [State](#state) +- [GetServerCapabilitiesResponse](#getservercapabilitiesresponse) +- [GetServerActivitiesResponse](#getserveractivitiesresponse) +- [GetButlerTasksResponse](#getbutlertasksresponse) +- [GetAvailableClientsResponse](#getavailableclientsresponse) +- [GetDevicesResponse](#getdevicesresponse) +- [GetServerIdentityResponse](#getserveridentityresponse) +- [GetRecentlyAddedResponse](#getrecentlyaddedresponse) +- [GetOnDeckResponse](#getondeckresponse) +- [GetMyPlexAccountResponse](#getmyplexaccountresponse) +- [GetSearchResultsResponse](#getsearchresultsresponse) +- [GetServerListResponse](#getserverlistresponse) +- [GetTranscodeSessionsResponse](#gettranscodesessionsresponse) +- [TaskName](#taskname) +- [OnlyTransient](#onlytransient) +- [IncludeDetails](#includedetails) +- [MinSize](#minsize) +- [PlaylistType](#playlisttype) + +## Level + +## Upscale + +## Type + +## Smart + +## Force + +## SecurityType + +## Scope + +## Download + +## Tonight + +## Skip + +## State + +## GetServerCapabilitiesResponse + +## GetServerActivitiesResponse + +## GetButlerTasksResponse + +## GetAvailableClientsResponse + +## GetDevicesResponse + +## GetServerIdentityResponse + +## GetRecentlyAddedResponse + +## GetOnDeckResponse + +## GetMyPlexAccountResponse + +## GetSearchResultsResponse + +## GetServerListResponse + +## GetTranscodeSessionsResponse + +## TaskName + +## OnlyTransient + +## IncludeDetails + +## MinSize + +## PlaylistType + + + diff --git a/src/plexsdk/models/Scope.py b/src/plexsdk/models/Scope.py new file mode 100644 index 0000000..d9c4103 --- /dev/null +++ b/src/plexsdk/models/Scope.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class Scope(Enum): + ALL = "all" + + def list(): + return list(map(lambda x: x.value, Scope._member_map_.values())) diff --git a/src/plexsdk/models/SecurityType.py b/src/plexsdk/models/SecurityType.py new file mode 100644 index 0000000..30fbe37 --- /dev/null +++ b/src/plexsdk/models/SecurityType.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class SecurityType(Enum): + DELEGATION = "delegation" + + def list(): + return list(map(lambda x: x.value, SecurityType._member_map_.values())) diff --git a/src/plexsdk/models/Skip.py b/src/plexsdk/models/Skip.py new file mode 100644 index 0000000..ddf334c --- /dev/null +++ b/src/plexsdk/models/Skip.py @@ -0,0 +1,7 @@ +from enum import Enum + +class Skip(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, Skip._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/Smart.py b/src/plexsdk/models/Smart.py new file mode 100644 index 0000000..ea395df --- /dev/null +++ b/src/plexsdk/models/Smart.py @@ -0,0 +1,7 @@ +from enum import Enum + +class Smart(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, Smart._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/State.py b/src/plexsdk/models/State.py new file mode 100644 index 0000000..8348363 --- /dev/null +++ b/src/plexsdk/models/State.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class State(Enum): + PLAYING = "playing" + PAUSED = "paused" + STOPPED = "stopped" + + def list(): + return list(map(lambda x: x.value, State._member_map_.values())) diff --git a/src/plexsdk/models/TaskName.py b/src/plexsdk/models/TaskName.py new file mode 100644 index 0000000..d124aa0 --- /dev/null +++ b/src/plexsdk/models/TaskName.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class TaskName(Enum): + BACKUPDATABASE = "BackupDatabase" + BUILDGRACENOTECOLLECTIONS = "BuildGracenoteCollections" + CHECKFORUPDATES = "CheckForUpdates" + CLEANOLDBUNDLES = "CleanOldBundles" + CLEANOLDCACHEFILES = "CleanOldCacheFiles" + DEEPMEDIAANALYSIS = "DeepMediaAnalysis" + GENERATEAUTOTAGS = "GenerateAutoTags" + GENERATECHAPTERTHUMBS = "GenerateChapterThumbs" + GENERATEMEDIAINDEXFILES = "GenerateMediaIndexFiles" + OPTIMIZEDATABASE = "OptimizeDatabase" + REFRESHLIBRARIES = "RefreshLibraries" + REFRESHLOCALMEDIA = "RefreshLocalMedia" + REFRESHPERIODICMETADATA = "RefreshPeriodicMetadata" + UPGRADEMEDIAANALYSIS = "UpgradeMediaAnalysis" + + def list(): + return list(map(lambda x: x.value, TaskName._member_map_.values())) diff --git a/src/plexsdk/models/Tonight.py b/src/plexsdk/models/Tonight.py new file mode 100644 index 0000000..87dc19e --- /dev/null +++ b/src/plexsdk/models/Tonight.py @@ -0,0 +1,7 @@ +from enum import Enum + +class Tonight(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, Tonight._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/Type.py b/src/plexsdk/models/Type.py new file mode 100644 index 0000000..5d3cc19 --- /dev/null +++ b/src/plexsdk/models/Type.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class Type(Enum): + AUDIO = "audio" + VIDEO = "video" + PHOTO = "photo" + + def list(): + return list(map(lambda x: x.value, Type._member_map_.values())) diff --git a/src/plexsdk/models/Upscale.py b/src/plexsdk/models/Upscale.py new file mode 100644 index 0000000..54ecbf6 --- /dev/null +++ b/src/plexsdk/models/Upscale.py @@ -0,0 +1,7 @@ +from enum import Enum + +class Upscale(Enum): + = "0" + V1 = "1" + def list(): + return list(map(lambda x: x.value, Upscale._member_map_.values())) \ No newline at end of file diff --git a/src/plexsdk/models/__init__.py b/src/plexsdk/models/__init__.py new file mode 100644 index 0000000..fe1a9d3 --- /dev/null +++ b/src/plexsdk/models/__init__.py @@ -0,0 +1,28 @@ +from .Level import Level +from .Upscale import Upscale +from .Type import Type +from .Smart import Smart +from .Force import Force +from .SecurityType import SecurityType +from .Scope import Scope +from .Download import Download +from .Tonight import Tonight +from .Skip import Skip +from .State import State +from .GetServerCapabilitiesResponse import GetServerCapabilitiesResponse +from .GetServerActivitiesResponse import GetServerActivitiesResponse +from .GetButlerTasksResponse import GetButlerTasksResponse +from .GetAvailableClientsResponse import GetAvailableClientsResponse +from .GetDevicesResponse import GetDevicesResponse +from .GetServerIdentityResponse import GetServerIdentityResponse +from .GetRecentlyAddedResponse import GetRecentlyAddedResponse +from .GetOnDeckResponse import GetOnDeckResponse +from .GetMyPlexAccountResponse import GetMyPlexAccountResponse +from .GetSearchResultsResponse import GetSearchResultsResponse +from .GetServerListResponse import GetServerListResponse +from .GetTranscodeSessionsResponse import GetTranscodeSessionsResponse +from .TaskName import TaskName +from .OnlyTransient import OnlyTransient +from .IncludeDetails import IncludeDetails +from .MinSize import MinSize +from .PlaylistType import PlaylistType diff --git a/src/plexsdk/models/base.py b/src/plexsdk/models/base.py new file mode 100644 index 0000000..341c6a6 --- /dev/null +++ b/src/plexsdk/models/base.py @@ -0,0 +1,60 @@ +import re +from typing import List, Union +from enum import Enum + + +class BaseModel: + """ + A base class that all models in the SDK inherit from (expect for Enum models). + + Methods + ------- + _pattern_matching(cls, value: str, pattern: str, variable_name: str) -> str: + Checks if a value matches a regex pattern. + Returns the value if there's a match, otherwise throws an error. + _enum_matching(cls, value: Union[str,Enum], enum_values: List[str], variable_name: str) -> str: + Checks if a value (str or enum) matches the required enum values. + Returns the value if there's a match, otherwise throws an error. + _one_of(cls, required_array, all_array, functions_array, input_data): + Validates whether an input_data satisfies the oneOf requirements. + """ + + def __init__(self): + pass + + def _pattern_matching(cls, value: str, pattern: str, variable_name: str): + if re.match(r"{}".format(pattern), value): + return value + else: + raise ValueError(f"Invalid value for {variable_name}: must match {pattern}") + + def _enum_matching( + cls, value: Union[str, Enum], enum_values: List[str], variable_name: str + ): + str_value = value.value if isinstance(value, Enum) else value + if str_value in enum_values: + return value + else: + raise ValueError( + f"Invalid value for {variable_name}: must match one of {enum_values}" + ) + + @classmethod + def _one_of(cls, required_array, all_array, functions_array, input_data): + input_array = list(input_data.keys()) + for model, fields in required_array.items(): + input_copy = input_array.copy() + matches_required = True + for param in fields: + if param not in input_copy: + matches_required = False + break + input_copy.remove(param) + if matches_required: + matches_all = True + for input in input_copy: + if input not in all_array[model]: + matches_all = False + break + if matches_all: + return functions_array[model](input_data) diff --git a/src/plexsdk/net/__init__.py b/src/plexsdk/net/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/plexsdk/net/environment.py b/src/plexsdk/net/environment.py new file mode 100644 index 0000000..36af8fe --- /dev/null +++ b/src/plexsdk/net/environment.py @@ -0,0 +1,11 @@ +""" +An enum class containing all the possible environments that +a user can switch between in this SDK. +""" +from enum import Enum + + +class Environment(Enum): + """The environments available for this SDK""" + + DEFAULT = "{protocol}://{ip}:{port}" diff --git a/src/plexsdk/net/http_client.py b/src/plexsdk/net/http_client.py new file mode 100644 index 0000000..16cd7f2 --- /dev/null +++ b/src/plexsdk/net/http_client.py @@ -0,0 +1,280 @@ +from time import sleep +import requests +from http_exceptions import HTTPException, client_exceptions, server_exceptions +from json import JSONDecodeError +from .http_content_types import multipart_form_data_request +from .utils import to_serialize, rename_reserved_keys, rename_to_reserved_keys + + +from .response import ResponseWithHeaders + + +class HTTPClient: + """ + Provides functionality for invoking HTTP-based API calls to web services. + """ + + _retry_codes = [500, 503, 504] + """list[int]: A list of status codes that invoke a retry.""" + + _initial_delay = 150 + """int: The delay (in milliseconds) before performing a retry.""" + + _max_retries = 3 + """int: The maximum number of retries.""" + + def __init__(self, hook): + self._hook = hook + + def _make_http_request(self, method, endpoint_url, headers, body_input): + """ + Places API calls according to the HTTP method and content type. + + Parameters: + ---------- + method : str + The type of http call to perform + endpoint_url : url + The endpoint url to make the http call on + headers : dict + The http call's headers + body_input : Any + The request's body + """ + request_method = getattr(requests, method) + serialized_body = rename_to_reserved_keys(to_serialize(body_input)) + if "Content-type" in headers: + data_type, subtype = headers["Content-type"].split("/") + if data_type == "multipart": + return multipart_form_data_request( + method, endpoint_url, headers, serialized_body + ) + if data_type in ["text", "image"]: + return request_method( + endpoint_url, headers=headers, data=serialized_body + ) + if not serialized_body or method in {"get", "delete"}: + return request_method(endpoint_url, headers=headers) + return request_method(endpoint_url, headers=headers, json=serialized_body) + + def delete(self, endpoint_url: str, headers: dict, retry: bool = True): + """ + Places API DELETE request and handles errors + + Parameters: + ---------- + endpoint_url : str + The endpoint url to make the http call on + headers : dict + The http call's headers + retry : bool + A boolean representing wether to attempt a retry + """ + + response = self._make_http_request("delete", endpoint_url, headers, None) + if response.status_code in self._retry_codes and retry: + try_cnt = 1 + while ( + response.status_code in self._retry_codes + and try_cnt - 1 < self._max_retries + ): + sleep(self._initial_delay ** (try_cnt - 1) / 1000) + response = self._make_http_request( + "delete", endpoint_url, headers, None + ) + try_cnt += 1 + + return self._handle_response(response) + + def get(self, endpoint_url: str, headers: dict, retry: bool = True): + """ + Places an API GET request and handles errors + + Parameters: + ---------- + endpoint_url : str + The endpoint url to make the http call on + headers : dict + The http call's headers + retry : bool + A boolean representing wether to attempt a retry + """ + + response = self._make_http_request("get", endpoint_url, headers, None) + if response.status_code in self._retry_codes and retry: + try_cnt = 1 + while ( + response.status_code in self._retry_codes + and try_cnt - 1 < self._max_retries + ): + sleep(self._initial_delay ** (try_cnt - 1) / 1000) + response = self._make_http_request("get", endpoint_url, headers, None) + try_cnt += 1 + + return self._handle_response(response) + + def patch(self, endpoint_url: str, headers: dict, body_input, retry: bool = True): + """ + Places an API PATCH request and handles errors + + Parameters: + ---------- + endpoint_url : str + The endpoint url to make the http call on + headers : dict + The http call's headers + body_input: + The patch request body + retry : bool + A boolean representing wether to attempt a retry + """ + + response = self._make_http_request("patch", endpoint_url, headers, body_input) + if response.status_code in self._retry_codes and retry: + try_cnt = 1 + while ( + response.status_code in self._retry_codes + and try_cnt - 1 < self._max_retries + ): + sleep(self._initial_delay ** (try_cnt - 1) / 1000) + response = self._make_http_request( + "patch", endpoint_url, headers, body_input + ) + try_cnt += 1 + + return self._handle_response(response) + + def post(self, endpoint_url: str, headers: dict, body_input, retry: bool = True): + """ + Places an API POST request and handles errors + + Parameters: + ---------- + endpoint_url : str + The endpoint url to make the http call on + headers : dict + The http call's headers + body_input: + The post request body + retry : bool + A boolean representing wether to attempt a retry + """ + + response = self._make_http_request("post", endpoint_url, headers, body_input) + if response.status_code in self._retry_codes and retry: + try_cnt = 1 + while ( + response.status_code in self._retry_codes + and try_cnt - 1 < self._max_retries + ): + sleep(self._initial_delay ** (try_cnt - 1) / 1000) + response = self._make_http_request( + "post", endpoint_url, headers, body_input + ) + try_cnt += 1 + + return self._handle_response(response) + + def put(self, endpoint_url: str, headers: dict, body_input, retry: bool = True): + """ + Places an API PUT request and handles errors + + Parameters: + ---------- + endpoint_url : str + The endpoint url to make the http call on + headers : dict + The http call's headers + body_input: + The put request body + retry : bool + A boolean representing whether to attempt a retry + """ + + response = self._make_http_request("put", endpoint_url, headers, body_input) + if response.status_code in self._retry_codes and retry: + try_cnt = 1 + while ( + response.status_code in self._retry_codes + and try_cnt - 1 < self._max_retries + ): + sleep(self._initial_delay ** (try_cnt - 1) / 1000) + response = self._make_http_request( + "put", endpoint_url, headers, body_input + ) + try_cnt += 1 + + return self._handle_response(response) + + def _create_exception_message(self, response, header: str) -> str: + """ + Creates an exception message using a specific header + + Parameters: + ---------- + response: + Response data received from an http request + header : str + Header name for the created exception + """ + if header in response.headers: + return f"{response.text}, Headers {header}: {response.headers[header]}" + return response.text + + def _handle_response(self, response: dict): + """ + Handles a response from an API call + + Parameters: + ---------- + response: + Response data received from an http request + """ + if response.status_code >= 200 and response.status_code < 400: + try: + return ResponseWithHeaders( + rename_reserved_keys(response.json()), response.headers + ) + except JSONDecodeError: + return response + else: + self._raise_from_status(response) + + def _raise_from_status(self, response) -> None: + """ + Raises exception based response status, with additional information appended if useful + + Parameters: + ---------- + response: + Response data received from an http request + """ + if response.status_code == 401: + raise client_exceptions.UnauthorizedException( + message=self._create_exception_message(response, "WWW-Authenticate") + ) + elif response.status_code == 405: + # this indicates a bug in the spec if it allows a method that the server rejects + raise client_exceptions.MethodNotAllowedException( + message=self._create_exception_message(response, "Allow") + ) + elif response.status_code == 407: + raise client_exceptions.ProxyAuthenticationRequiredException( + message=self._create_exception_message(response, "Proxy-Authenticate") + ) + elif response.status_code == 413: + raise client_exceptions.PayloadTooLargeException( + message=self._create_exception_message(response, "Retry-After") + ) + elif response.status_code == 429: + raise client_exceptions.TooManyRequestsException( + message=self._create_exception_message(response, "Retry-After") + ) + elif response.status_code == 503: + raise server_exceptions.ServiceUnavailableException( + message=self._create_exception_message(response, "Retry-After") + ) + else: + raise HTTPException.from_status_code(status_code=response.status_code)( + message=response.text + ) diff --git a/src/plexsdk/net/http_content_types.py b/src/plexsdk/net/http_content_types.py new file mode 100644 index 0000000..4b2b8f8 --- /dev/null +++ b/src/plexsdk/net/http_content_types.py @@ -0,0 +1,42 @@ +""" +Collection of API calls according to the HTTP method and content type. + +Functions: + multipart_form_data_request +""" +import requests +import io +from mimetypes import guess_type + + +def multipart_form_data_request(method, endpoint_url, headers, body_input): + """ + Places a multipart/formdata http request. + + Parameters: + ---------- + method : str + The type of http call to perform + endpoint_url : url + The endpoint url to make the http call on + headers : dict + The http call's headers + body_input : Any + The request's body + """ + data = {} + files = {} + request_method = getattr(requests, method) + del headers["Content-type"] + for key, value in body_input.items(): + if isinstance(value, (io.TextIOWrapper, io.BufferedIOBase)): + mime_type, encoding = guess_type(value.name) + file_tuple = ( + (value.name, value, mime_type) if mime_type else (value.name, value) + ) + files[key] = file_tuple + else: + data[key] = value + if files: + return request_method(endpoint_url, headers=headers, files=files, data=data) + return request_method(endpoint_url, headers=headers, data=data) diff --git a/src/plexsdk/net/query_serializer.py b/src/plexsdk/net/query_serializer.py new file mode 100644 index 0000000..232d18a --- /dev/null +++ b/src/plexsdk/net/query_serializer.py @@ -0,0 +1,74 @@ +from typing import Any, Dict, List +from enum import Enum + +explode = bool + + +def simple(value: Any, explode: bool) -> str: + if isinstance(value, Enum): + return str(value.value) + + # Check if the value is a list + if isinstance(value, list): + return ",".join(value) if explode else "".join(value) + + if isinstance(value, dict): + if explode: + # Serialize object with exploded format: "key=value,key2=value2" + return ",".join([f"{k}={v}" for k, v in value.items()]) + else: + # Serialize object with non-exploded format: "key,value,key2,value2" + return ",".join( + [str(item) for sublist in value.items() for item in sublist] + ) + + return str(value) + + +def form(parameter_name: str, parameter_value: Any, explode: bool) -> str: + if isinstance(parameter_value, Enum): + return f"{parameter_name}=" + str(parameter_value.value) + + if isinstance(parameter_value, list): + return ( + "&".join([f"{parameter_name}={v}" for v in parameter_value]) + if explode + else f"{parameter_name}=" + ",".join([str(v) for v in parameter_value]) + ) + + if isinstance(parameter_value, dict): + if explode: + # Serialize object with exploded format: "key1=value1&key2=value2" + return "&".join([f"{k}={v}" for k, v in parameter_value.items()]) + else: + # Serialize object with non-exploded format: "key=key1,value1,key2,value2" + return f"{parameter_name}=" + ",".join( + [str(item) for sublist in parameter_value.items() for item in sublist] + ) + + return f"{parameter_name}=" + str(parameter_value) + + +style_methods = { + "simple": simple, + "form": form, +} + + +def serialize_query(parameter_style, explode, key: str, parameter_value: Any) -> str: + method = style_methods.get(parameter_style) + return method(key, parameter_value, explode) if method else "" + + +def serialize_path( + parameter_style, explode: bool, parameter_value: Any, parameter_key=None +): + method = style_methods.get(parameter_style) + if not method: + return "" + + # The `simple` and `label` styles do not require a `parameter_key` + if not parameter_key: + return method(parameter_value, explode) + else: + return method(parameter_key, parameter_value, explode) diff --git a/src/plexsdk/net/response.py b/src/plexsdk/net/response.py new file mode 100644 index 0000000..27e802f --- /dev/null +++ b/src/plexsdk/net/response.py @@ -0,0 +1,4 @@ +class ResponseWithHeaders: + def __init__(self, data, headers): + self.data = data + self.headers = headers diff --git a/src/plexsdk/net/utils.py b/src/plexsdk/net/utils.py new file mode 100644 index 0000000..0994d60 --- /dev/null +++ b/src/plexsdk/net/utils.py @@ -0,0 +1,65 @@ +""" +Helper functions for http calls. + +Functions: + to_serialize +""" + +import io +from enum import Enum + + +def to_serialize(obj): + """ + Recursively converts objects into dictionaries. + + Parameters: + ---------- + obj: + The object to transform into a dictionary. + """ + result = {} + if not hasattr(obj, "__dict__") or isinstance( + obj, (io.TextIOWrapper, io.BufferedIOBase) + ): + return obj + iter_obj = obj.__dict__.items() if hasattr(obj, "__dict__") else obj.items() + for key, value in iter_obj: + if isinstance(value, (io.TextIOWrapper, io.BufferedIOBase)): + result[key] = value + elif isinstance(value, Enum): + result[key] = value.value + elif isinstance(value, (list, set, tuple)): + for i in range(len(value)): + value[i] = to_serialize(value[i]) + result[key] = value + elif hasattr(value, "__dict__"): + result[key] = to_serialize(value) + else: + result[key] = value + return result + + +response_mapper = {"type": "type_"} +request_mapper = {"type_": "type"} + + +def rename_keys(data, mapper): + if isinstance(data, dict): + new_data = {} + for key, value in data.items(): + new_key = mapper[key] if key in mapper else key + new_data[new_key] = rename_keys(value, mapper) + return new_data + elif isinstance(data, list): + return [rename_keys(item, mapper) for item in data] + else: + return data + + +def rename_reserved_keys(data): + return rename_keys(data, response_mapper) + + +def rename_to_reserved_keys(data): + return rename_keys(data, request_mapper) diff --git a/src/plexsdk/sdk.py b/src/plexsdk/sdk.py new file mode 100644 index 0000000..785bacf --- /dev/null +++ b/src/plexsdk/sdk.py @@ -0,0 +1,129 @@ +""" +Creates a PlexSDK class. +Generates the main SDK with all available queries as attributes. + +Class: + PlexSDK +""" +from .net.environment import Environment + +from .services.activities import Activities +from .services.butler import Butler +from .services.hubs import Hubs +from .services.library import Library +from .services.log import Log +from .services.media import Media +from .services.playlists import Playlists +from .services.search import Search +from .services.security import Security +from .services.server import Server +from .services.sessions import Sessions +from .services.updater import Updater +from .services.video import Video + + +class PlexSDK: + """ + A class representing the full PlexSDK SDK + + Attributes + ---------- + activities : Activities + butler : Butler + hubs : Hubs + library : Library + log : Log + media : Media + playlists : Playlists + search : Search + security : Security + server : Server + sessions : Sessions + updater : Updater + video : Video + + Methods + ------- + set_base_url(url: str) + Sets the end URL + set_api_key(api_key, api_key_header)) + Set the api key + """ + + def __init__( + self, api_key="", api_key_header="X-Plex-Token", environment=Environment.DEFAULT + ) -> None: + """ + Initializes the PlexSDK SDK class. + Parameters + ---------- + environment: str + The environment that the SDK is accessing + api_key : str + The api key + api_key_header : str + The API key header + """ + self.activities = Activities(api_key, api_key_header) + self.butler = Butler(api_key, api_key_header) + self.hubs = Hubs(api_key, api_key_header) + self.library = Library(api_key, api_key_header) + self.log = Log(api_key, api_key_header) + self.media = Media(api_key, api_key_header) + self.playlists = Playlists(api_key, api_key_header) + self.search = Search(api_key, api_key_header) + self.security = Security(api_key, api_key_header) + self.server = Server(api_key, api_key_header) + self.sessions = Sessions(api_key, api_key_header) + self.updater = Updater(api_key, api_key_header) + self.video = Video(api_key, api_key_header) + + self.set_base_url(environment.value) + + def set_base_url(self, url: str) -> None: + """ + Sets the end URL + + Parameters + ---------- + url: + The end URL + """ + self.activities.set_base_url(url) + self.butler.set_base_url(url) + self.hubs.set_base_url(url) + self.library.set_base_url(url) + self.log.set_base_url(url) + self.media.set_base_url(url) + self.playlists.set_base_url(url) + self.search.set_base_url(url) + self.security.set_base_url(url) + self.server.set_base_url(url) + self.sessions.set_base_url(url) + self.updater.set_base_url(url) + self.video.set_base_url(url) + + def set_api_key(self, api_key: str, api_key_header: str = None) -> None: + """ + Sets api key and api key header name + + Parameters + ---------- + api_key: string + API Key value + api_key_header: string + Name of API Key + """ + self.activities.set_api_key(api_key, api_key_header) + self.butler.set_api_key(api_key, api_key_header) + self.hubs.set_api_key(api_key, api_key_header) + self.library.set_api_key(api_key, api_key_header) + self.log.set_api_key(api_key, api_key_header) + self.media.set_api_key(api_key, api_key_header) + self.playlists.set_api_key(api_key, api_key_header) + self.search.set_api_key(api_key, api_key_header) + self.security.set_api_key(api_key, api_key_header) + self.server.set_api_key(api_key, api_key_header) + self.sessions.set_api_key(api_key, api_key_header) + self.updater.set_api_key(api_key, api_key_header) + self.video.set_api_key(api_key, api_key_header) diff --git a/src/plexsdk/services/README.md b/src/plexsdk/services/README.md new file mode 100644 index 0000000..d51d73a --- /dev/null +++ b/src/plexsdk/services/README.md @@ -0,0 +1,1896 @@ +# PlexSDK Services +A list of all services and services methods. +- Services + + - [Server](#server) + + - [Media](#media) + + - [Activities](#activities) + + - [Butler](#butler) + + - [Hubs](#hubs) + + - [Search](#search) + + - [Library](#library) + + - [Log](#log) + + - [Playlists](#playlists) + + - [Security](#security) + + - [Sessions](#sessions) + + - [Updater](#updater) + + - [Video](#video) +- [All Methods](#all-methods) + + +## Server + +| Method | Description| +| :-------- | :----------| +| [get_server_capabilities](#get_server_capabilities) | Server Capabilities | +| [get_server_preferences](#get_server_preferences) | Get Server Preferences | +| [get_available_clients](#get_available_clients) | Get Available Clients | +| [get_devices](#get_devices) | Get Devices | +| [get_server_identity](#get_server_identity) | Get Server Identity | +| [get_my_plex_account](#get_my_plex_account) | Get MyPlex Account | +| [get_resized_photo](#get_resized_photo) | Get a Resized Photo | +| [get_server_list](#get_server_list) | Get Server List | + + +## Media + +| Method | Description| +| :-------- | :----------| +| [mark_played](#mark_played) | Mark Media Played | +| [mark_unplayed](#mark_unplayed) | Mark Media Unplayed | +| [update_play_progress](#update_play_progress) | Update Media Play Progress | + + +## Activities + +| Method | Description| +| :-------- | :----------| +| [get_server_activities](#get_server_activities) | Get Server Activities | +| [cancel_server_activities](#cancel_server_activities) | Cancel Server Activities | + + +## Butler + +| Method | Description| +| :-------- | :----------| +| [start_all_tasks](#start_all_tasks) | Start all Butler tasks | +| [get_butler_tasks](#get_butler_tasks) | Get Butler tasks | +| [stop_all_tasks](#stop_all_tasks) | Stop all Butler tasks | +| [start_task](#start_task) | Start a single Butler task | +| [stop_task](#stop_task) | Stop a single Butler task | + + +## Hubs + +| Method | Description| +| :-------- | :----------| +| [get_global_hubs](#get_global_hubs) | Get Global Hubs | +| [get_library_hubs](#get_library_hubs) | Get library specific hubs | + + +## Search + +| Method | Description| +| :-------- | :----------| +| [perform_search](#perform_search) | Perform a search | +| [perform_voice_search](#perform_voice_search) | Perform a voice search | +| [get_search_results](#get_search_results) | Get Search Results | + + +## Library + +| Method | Description| +| :-------- | :----------| +| [get_file_hash](#get_file_hash) | Get Hash Value | +| [get_recently_added](#get_recently_added) | Get Recently Added | +| [get_libraries](#get_libraries) | Get All Libraries | +| [get_library](#get_library) | Get Library Details | +| [delete_library](#delete_library) | Delete Library Section | +| [get_library_items](#get_library_items) | Get Library Items | +| [refresh_library](#refresh_library) | Refresh Library | +| [get_latest_library_items](#get_latest_library_items) | Get Latest Library Items | +| [get_common_library_items](#get_common_library_items) | Get Common Library Items | +| [get_metadata](#get_metadata) | Get Items Metadata | +| [get_metadata_children](#get_metadata_children) | Get Items Children | +| [get_on_deck](#get_on_deck) | Get On Deck | + + +## Log + +| Method | Description| +| :-------- | :----------| +| [log_multi_line](#log_multi_line) | Logging a multi-line message | +| [log_line](#log_line) | Logging a single line message. | +| [enable_paper_trail](#enable_paper_trail) | Enabling Papertrail | + + +## Playlists + +| Method | Description| +| :-------- | :----------| +| [create_playlist](#create_playlist) | Create a Playlist | +| [get_playlists](#get_playlists) | Get All Playlists | +| [get_playlist](#get_playlist) | Retrieve Playlist | +| [delete_playlist](#delete_playlist) | Deletes a Playlist | +| [update_playlist](#update_playlist) | Update a Playlist | +| [get_playlist_contents](#get_playlist_contents) | Retrieve Playlist Contents | +| [clear_playlist_contents](#clear_playlist_contents) | Delete Playlist Contents | +| [add_playlist_contents](#add_playlist_contents) | Adding to a Playlist | +| [upload_playlist](#upload_playlist) | Upload Playlist | + + +## Security + +| Method | Description| +| :-------- | :----------| +| [get_transient_token](#get_transient_token) | Get a Transient Token. | +| [get_source_connection_information](#get_source_connection_information) | Get Source Connection Information | + + +## Sessions + +| Method | Description| +| :-------- | :----------| +| [get_sessions](#get_sessions) | Get Active Sessions | +| [get_session_history](#get_session_history) | Get Session History | +| [get_transcode_sessions](#get_transcode_sessions) | Get Transcode Sessions | +| [stop_transcode_session](#stop_transcode_session) | Stop a Transcode Session | + + +## Updater + +| Method | Description| +| :-------- | :----------| +| [get_update_status](#get_update_status) | Querying status of updates | +| [check_for_updates](#check_for_updates) | Checking for updates | +| [apply_updates](#apply_updates) | Apply Updates | + + +## Video + +| Method | Description| +| :-------- | :----------| +| [start_universal_transcode](#start_universal_transcode) | Start Universal Transcode | +| [get_timeline](#get_timeline) | Get the timeline for a media item | + + + + +## All Methods + + +### **get_server_capabilities** +Server Capabilities +- HTTP Method: GET +- Endpoint: / + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerCapabilitiesResponse](/src/plexsdk/models/README.md#getservercapabilitiesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_capabilities() + +pprint(vars(results)) + +``` + +### **get_server_preferences** +Get Server Preferences +- HTTP Method: GET +- Endpoint: /:/prefs + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_preferences() + +pprint(vars(results)) + +``` + +### **get_available_clients** +Get Available Clients +- HTTP Method: GET +- Endpoint: /clients + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetAvailableClientsResponse](/src/plexsdk/models/README.md#getavailableclientsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_available_clients() + +pprint(vars(results)) + +``` + +### **get_devices** +Get Devices +- HTTP Method: GET +- Endpoint: /devices + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetDevicesResponse](/src/plexsdk/models/README.md#getdevicesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_devices() + +pprint(vars(results)) + +``` + +### **get_server_identity** +Get Server Identity +- HTTP Method: GET +- Endpoint: /identity + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerIdentityResponse](/src/plexsdk/models/README.md#getserveridentityresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_identity() + +pprint(vars(results)) + +``` + +### **get_my_plex_account** +Get MyPlex Account +- HTTP Method: GET +- Endpoint: /myplex/account + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetMyPlexAccountResponse](/src/plexsdk/models/README.md#getmyplexaccountresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_my_plex_account() + +pprint(vars(results)) + +``` + +### **get_resized_photo** +Get a Resized Photo +- HTTP Method: GET +- Endpoint: /photo/:/transcode + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| width | float | Required | The width for the resized photo | +| height | float | Required | The height for the resized photo | +| opacity | int | Required | The opacity for the resized photo | +| blur | float | Required | The width for the resized photo | +| min_size | [MinSize](/src/plexsdk/models/README.md#minsize) | Required | images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. | +| upscale | [Upscale](/src/plexsdk/models/README.md#upscale) | Required | allow images to be resized beyond native dimensions. | +| url | str | Required | path to image within Plex | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_resized_photo( + width = 110, + height = 165, + opacity = 100, + blur = 4000, + min_size = 1, + upscale = 1, + url = '/library/metadata/49564/thumb/1654258204' +) + +pprint(vars(results)) + +``` + +### **get_server_list** +Get Server List +- HTTP Method: GET +- Endpoint: /servers + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerListResponse](/src/plexsdk/models/README.md#getserverlistresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.server.get_server_list() + +pprint(vars(results)) + +``` + + +### **mark_played** +Mark Media Played +- HTTP Method: GET +- Endpoint: /:/scrobble + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | float | Required | The media key to mark as played | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.mark_played(key = 59398) + +pprint(vars(results)) + +``` + +### **mark_unplayed** +Mark Media Unplayed +- HTTP Method: GET +- Endpoint: /:/unscrobble + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | float | Required | The media key to mark as Unplayed | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.mark_unplayed(key = 59398) + +pprint(vars(results)) + +``` + +### **update_play_progress** +Update Media Play Progress +- HTTP Method: POST +- Endpoint: /:/progress + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| key | str | Required | the media key | +| time | float | Required | The time, in milliseconds, used to set the media playback progress. | +| state | str | Required | The playback state of the media item. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.media.update_play_progress( + key = 'key', + time = 8535962.551714689, + state = 'state' +) + +pprint(vars(results)) + +``` + + +### **get_server_activities** +Get Server Activities +- HTTP Method: GET +- Endpoint: /activities + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetServerActivitiesResponse](/src/plexsdk/models/README.md#getserveractivitiesresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.activities.get_server_activities() + +pprint(vars(results)) + +``` + +### **cancel_server_activities** +Cancel Server Activities +- HTTP Method: DELETE +- Endpoint: /activities/{activityUUID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| activity_uuid | str | Required | The UUID of the activity to cancel. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.activities.cancel_server_activities(activity_uuid = 'activityUUID') + +pprint(vars(results)) + +``` + + +### **start_all_tasks** +Start all Butler tasks +- HTTP Method: POST +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.start_all_tasks() + +pprint(vars(results)) + +``` + +### **get_butler_tasks** +Get Butler tasks +- HTTP Method: GET +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetButlerTasksResponse](/src/plexsdk/models/README.md#getbutlertasksresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.get_butler_tasks() + +pprint(vars(results)) + +``` + +### **stop_all_tasks** +Stop all Butler tasks +- HTTP Method: DELETE +- Endpoint: /butler + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.stop_all_tasks() + +pprint(vars(results)) + +``` + +### **start_task** +Start a single Butler task +- HTTP Method: POST +- Endpoint: /butler/{taskName} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | the name of the task to be started. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.start_task(task_name = 'CleanOldBundles') + +pprint(vars(results)) + +``` + +### **stop_task** +Stop a single Butler task +- HTTP Method: DELETE +- Endpoint: /butler/{taskName} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| task_name | [TaskName](/src/plexsdk/models/README.md#taskname) | Required | The name of the task to be started. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.butler.stop_task(task_name = 'BuildGracenoteCollections') + +pprint(vars(results)) + +``` + + +### **get_global_hubs** +Get Global Hubs +- HTTP Method: GET +- Endpoint: /hubs + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| count | float | Optional | The number of items to return with each hub. | +| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.hubs.get_global_hubs( + count = -21278281.03559582, + only_transient = 42.1 +) + +pprint(vars(results)) + +``` + +### **get_library_hubs** +Get library specific hubs +- HTTP Method: GET +- Endpoint: /hubs/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| count | float | Optional | The number of items to return with each hub. | +| only_transient | [OnlyTransient](/src/plexsdk/models/README.md#onlytransient) | Optional | Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.hubs.get_library_hubs( + section_id = -58449853.97546232, + count = 19599615.466092095, + only_transient = 42.1 +) + +pprint(vars(results)) + +``` + + +### **perform_search** +Perform a search +- HTTP Method: GET +- Endpoint: /hubs/search + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The query term | +| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | float | Optional | The number of items to return per hub | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.perform_search( + query = 'arnold', + section_id = -56180545.95318425, + limit = 5 +) + +pprint(vars(results)) + +``` + +### **perform_voice_search** +Perform a voice search +- HTTP Method: GET +- Endpoint: /hubs/search/voice + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The query term | +| section_id | float | Optional | This gives context to the search, and can result in re-ordering of search result hubs | +| limit | float | Optional | The number of items to return per hub | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.perform_voice_search( + query = 'dead+poop', + section_id = 69248804.72578311, + limit = 5 +) + +pprint(vars(results)) + +``` + +### **get_search_results** +Get Search Results +- HTTP Method: GET +- Endpoint: /search + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| query | str | Required | The search query string to use | + +**Return Type** + +[GetSearchResultsResponse](/src/plexsdk/models/README.md#getsearchresultsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.search.get_search_results(query = '110') + +pprint(vars(results)) + +``` + + +### **get_file_hash** +Get Hash Value +- HTTP Method: GET +- Endpoint: /library/hashes + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| url | str | Required | This is the path to the local file, must be prefixed by `file://` | +| type | float | Optional | Item type | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_file_hash( + url = 'file://C:\Image.png&type=13', + type = -87442821.49664992 +) + +pprint(vars(results)) + +``` + +### **get_recently_added** +Get Recently Added +- HTTP Method: GET +- Endpoint: /library/recentlyAdded + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetRecentlyAddedResponse](/src/plexsdk/models/README.md#getrecentlyaddedresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_recently_added() + +pprint(vars(results)) + +``` + +### **get_libraries** +Get All Libraries +- HTTP Method: GET +- Endpoint: /library/sections + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_libraries() + +pprint(vars(results)) + +``` + +### **get_library** +Get Library Details +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| include_details | [IncludeDetails](/src/plexsdk/models/README.md#includedetails) | Optional | Whether or not to include details for a section (types, filters, and sorts).
Only exists for backwards compatibility, media providers other than the server libraries have it on always.
| + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_library( + section_id = 1000, + include_details = 42.1 +) + +pprint(vars(results)) + +``` + +### **delete_library** +Delete Library Section +- HTTP Method: DELETE +- Endpoint: /library/sections/{sectionId} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.delete_library(section_id = 1000) + +pprint(vars(results)) + +``` + +### **get_library_items** +Get Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/all + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Optional | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_library_items( + section_id = 36759422.79452938, + type = 85955460.47229531, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **refresh_library** +Refresh Library +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/refresh + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to refresh | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.refresh_library(section_id = -11046452.742861003) + +pprint(vars(results)) + +``` + +### **get_latest_library_items** +Get Latest Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/latest + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Required | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_latest_library_items( + section_id = -72167450.51781249, + type = 84030430.945622, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **get_common_library_items** +Get Common Library Items +- HTTP Method: GET +- Endpoint: /library/sections/{sectionId}/common + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| section_id | float | Required | the Id of the library to query | +| type | float | Required | item type | +| filter | str | Optional | the filter parameter | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_common_library_items( + section_id = 84909442.48641255, + type = 81418870.30483484, + filter = 'filter' +) + +pprint(vars(results)) + +``` + +### **get_metadata** +Get Items Metadata +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | the id of the library item to return the children of. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_metadata(rating_key = -79180953.0704827) + +pprint(vars(results)) + +``` + +### **get_metadata_children** +Get Items Children +- HTTP Method: GET +- Endpoint: /library/metadata/{ratingKey}/children + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | the id of the library item to return the children of. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_metadata_children(rating_key = 22112069.91905561) + +pprint(vars(results)) + +``` + +### **get_on_deck** +Get On Deck +- HTTP Method: GET +- Endpoint: /library/onDeck + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetOnDeckResponse](/src/plexsdk/models/README.md#getondeckresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.library.get_on_deck() + +pprint(vars(results)) + +``` + + +### **log_multi_line** +Logging a multi-line message +- HTTP Method: POST +- Endpoint: /log + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.log_multi_line() + +pprint(vars(results)) + +``` + +### **log_line** +Logging a single line message. +- HTTP Method: GET +- Endpoint: /log + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| level | [Level](/src/plexsdk/models/README.md#level) | Required | An integer log level to write to the PMS log with.
0: Error
1: Warning
2: Info
3: Debug
4: Verbose
| +| message | str | Required | The text of the message to write to the log. | +| source | str | Required | a string indicating the source of the message. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.log_line( + level = 1, + message = 'message', + source = 'source' +) + +pprint(vars(results)) + +``` + +### **enable_paper_trail** +Enabling Papertrail +- HTTP Method: GET +- Endpoint: /log/networked + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.log.enable_paper_trail() + +pprint(vars(results)) + +``` + + +### **create_playlist** +Create a Playlist +- HTTP Method: POST +- Endpoint: /playlists + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| title | str | Required | name of the playlist | +| type | [Type](/src/plexsdk/models/README.md#type) | Required | type of playlist to create | +| smart | [Smart](/src/plexsdk/models/README.md#smart) | Required | whether the playlist is smart or not | +| uri | str | Optional | the content URI for the playlist | +| play_queue_id | float | Optional | the play queue to copy to a playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.create_playlist( + title = 'title', + type = 'audio', + smart = 1, + uri = 'uri', + play_queue_id = -92047882.95265284 +) + +pprint(vars(results)) + +``` + +### **get_playlists** +Get All Playlists +- HTTP Method: GET +- Endpoint: /playlists/all + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_type | [PlaylistType](/src/plexsdk/models/README.md#playlisttype) | Optional | limit to a type of playlist. | +| smart | [Smart](/src/plexsdk/models/README.md#smart) | Optional | type of playlists to return (default is all). | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlists( + playlist_type = 'photo', + smart = 1 +) + +pprint(vars(results)) + +``` + +### **get_playlist** +Retrieve Playlist +- HTTP Method: GET +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlist(playlist_id = 83585352.9850379) + +pprint(vars(results)) + +``` + +### **delete_playlist** +Deletes a Playlist +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.delete_playlist(playlist_id = 97044833.44888172) + +pprint(vars(results)) + +``` + +### **update_playlist** +Update a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.update_playlist(playlist_id = -48390592.04133318) + +pprint(vars(results)) + +``` + +### **get_playlist_contents** +Retrieve Playlist Contents +- HTTP Method: GET +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | +| type | float | Required | the metadata type of the item to return | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.get_playlist_contents( + playlist_id = 86594016.93713528, + type = 92063710.76575199 +) + +pprint(vars(results)) + +``` + +### **clear_playlist_contents** +Delete Playlist Contents +- HTTP Method: DELETE +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.clear_playlist_contents(playlist_id = -5395985.032092914) + +pprint(vars(results)) + +``` + +### **add_playlist_contents** +Adding to a Playlist +- HTTP Method: PUT +- Endpoint: /playlists/{playlistID}/items + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| playlist_id | float | Required | the ID of the playlist | +| uri | str | Required | the content URI for the playlist | +| play_queue_id | float | Required | the play queue to add to a playlist | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.add_playlist_contents( + playlist_id = -6920661.3914530575, + uri = 'library://..', + play_queue_id = 123 +) + +pprint(vars(results)) + +``` + +### **upload_playlist** +Upload Playlist +- HTTP Method: POST +- Endpoint: /playlists/upload + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| path | str | Required | absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server.
If the `path` argument is a directory, that path will be scanned for playlist files to be processed.
Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it.
The GUID of each playlist is based on the filename.
| +| force | [Force](/src/plexsdk/models/README.md#force) | Required | force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist.
The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded.
| + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.playlists.upload_playlist( + path = '/home/barkley/playlist.m3u', + force = 1 +) + +pprint(vars(results)) + +``` + + +### **get_transient_token** +Get a Transient Token. +- HTTP Method: GET +- Endpoint: /security/token + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| type | [SecurityType](/src/plexsdk/models/README.md#securitytype) | Required | `delegation` - This is the only supported `type` parameter. | +| scope | [Scope](/src/plexsdk/models/README.md#scope) | Required | `all` - This is the only supported `scope` parameter. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.security.get_transient_token( + type = 'delegation', + scope = 'all' +) + +pprint(vars(results)) + +``` + +### **get_source_connection_information** +Get Source Connection Information +- HTTP Method: GET +- Endpoint: /security/resources + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| source | str | Required | The source identifier with an included prefix. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.security.get_source_connection_information(source = 'provider://provider-identifier') + +pprint(vars(results)) + +``` + + +### **get_sessions** +Get Active Sessions +- HTTP Method: GET +- Endpoint: /status/sessions + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_sessions() + +pprint(vars(results)) + +``` + +### **get_session_history** +Get Session History +- HTTP Method: GET +- Endpoint: /status/sessions/history/all + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_session_history() + +pprint(vars(results)) + +``` + +### **get_transcode_sessions** +Get Transcode Sessions +- HTTP Method: GET +- Endpoint: /transcode/sessions + +**Parameters** + +This method has no parameters. + +**Return Type** + +[GetTranscodeSessionsResponse](/src/plexsdk/models/README.md#gettranscodesessionsresponse) + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.get_transcode_sessions() + +pprint(vars(results)) + +``` + +### **stop_transcode_session** +Stop a Transcode Session +- HTTP Method: DELETE +- Endpoint: /transcode/sessions/{sessionKey} + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| session_key | str | Required | the Key of the transcode session to stop | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.sessions.stop_transcode_session(session_key = 'zz7llzqlx8w9vnrsbnwhbmep') + +pprint(vars(results)) + +``` + + +### **get_update_status** +Querying status of updates +- HTTP Method: GET +- Endpoint: /updater/status + +**Parameters** + +This method has no parameters. + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.get_update_status() + +pprint(vars(results)) + +``` + +### **check_for_updates** +Checking for updates +- HTTP Method: PUT +- Endpoint: /updater/check + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| download | [Download](/src/plexsdk/models/README.md#download) | Optional | Indicate that you want to start download any updates found. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.check_for_updates(download = 1) + +pprint(vars(results)) + +``` + +### **apply_updates** +Apply Updates +- HTTP Method: PUT +- Endpoint: /updater/apply + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| tonight | [Tonight](/src/plexsdk/models/README.md#tonight) | Optional | Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install | +| skip | [Skip](/src/plexsdk/models/README.md#skip) | Optional | Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.updater.apply_updates( + tonight = 'foo', + skip = 1 +) + +pprint(vars(results)) + +``` + + +### **start_universal_transcode** +Start Universal Transcode +- HTTP Method: GET +- Endpoint: /video/:/transcode/universal/start.mpd + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| has_mde | float | Required | Whether the media item has MDE | +| path | str | Required | The path to the media item to transcode | +| media_index | float | Required | The index of the media item to transcode | +| part_index | float | Required | The index of the part to transcode | +| protocol | str | Required | The protocol to use for the transcode session | +| fast_seek | float | Optional | Whether to use fast seek or not | +| direct_play | float | Optional | Whether to use direct play or not | +| direct_stream | float | Optional | Whether to use direct stream or not | +| subtitle_size | float | Optional | The size of the subtitles | +| subtites | str | Optional | The subtitles | +| audio_boost | float | Optional | The audio boost | +| location | str | Optional | The location of the transcode session | +| media_buffer_size | float | Optional | The size of the media buffer | +| session | str | Optional | The session ID | +| add_debug_overlay | float | Optional | Whether to add a debug overlay or not | +| auto_adjust_quality | float | Optional | Whether to auto adjust quality or not | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.video.start_universal_transcode( + has_mde = 68681526.6739457, + path = 'path', + media_index = 38635292.15502611, + part_index = 56377101.56072605, + protocol = 'protocol', + fast_seek = -48546578.06404572, + direct_play = -4004169.7057704926, + direct_stream = -63607905.2844202, + subtitle_size = -95264880.14792101, + subtites = 'subtites', + audio_boost = 92032906.1650356, + location = 'location', + media_buffer_size = 43422490.76220566, + session = 'session', + add_debug_overlay = -40848683.38562142, + auto_adjust_quality = 63926343.42811155 +) + +pprint(vars(results)) + +``` + +### **get_timeline** +Get the timeline for a media item +- HTTP Method: GET +- Endpoint: /:/timeline + +**Parameters** +| Name | Type| Required | Description | +| :-------- | :----------| :----------| :----------| +| rating_key | float | Required | The rating key of the media item | +| key | str | Required | The key of the media item to get the timeline for | +| state | [State](/src/plexsdk/models/README.md#state) | Required | The state of the media item | +| has_mde | float | Required | Whether the media item has MDE | +| time | float | Required | The time of the media item | +| duration | float | Required | The duration of the media item | +| context | str | Required | The context of the media item | +| play_queue_item_id | float | Required | The play queue item ID of the media item | +| play_back_time | float | Required | The playback time of the media item | +| row | float | Required | The row of the media item | + +**Return Type** + +Returns a dict object. + +**Example Usage Code Snippet** +```Python +from os import getenv +from pprint import pprint +from plexsdk import PlexSDK +sdk = PlexSDK() +sdk.set_api_key(getenv("PLEXSDK_API_KEY")) +results = sdk.video.get_timeline( + rating_key = 45215776.89077535, + key = 'key', + state = 'playing', + has_mde = -17905072.874770716, + time = -7347989.028095856, + duration = 82330750.57461244, + context = 'context', + play_queue_item_id = -69611222.19233666, + play_back_time = -80252961.1853132, + row = -54653572.50923404 +) + +pprint(vars(results)) + +``` + + + + diff --git a/src/plexsdk/services/__init__.py b/src/plexsdk/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/plexsdk/services/activities.py b/src/plexsdk/services/activities.py new file mode 100644 index 0000000..370954e --- /dev/null +++ b/src/plexsdk/services/activities.py @@ -0,0 +1,53 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.GetServerActivitiesResponse import ( + GetServerActivitiesResponse as GetServerActivitiesResponseModel, +) + + +class Activities(BaseService): + def get_server_activities(self) -> GetServerActivitiesResponseModel: + """ + Get Server Activities + """ + + url_endpoint = "/activities" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetServerActivitiesResponseModel(**res) + return res + + def cancel_server_activities(self, activity_uuid: str): + """ + Cancel Server Activities + Parameters: + ---------- + activity_uuid: str + The UUID of the activity to cancel. + """ + + url_endpoint = "/activities/{activity_uuid}" + headers = {} + self._add_required_headers(headers) + if not activity_uuid: + raise ValueError( + "Parameter activity_uuid is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{activity_uuid}", + quote( + str( + query_serializer.serialize_path( + "simple", False, activity_uuid, None + ) + ) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res diff --git a/src/plexsdk/services/base.py b/src/plexsdk/services/base.py new file mode 100644 index 0000000..f8b0d0e --- /dev/null +++ b/src/plexsdk/services/base.py @@ -0,0 +1,106 @@ +""" +Creates a BaseService class. +Performs API calls,sets authentication tokens and handles http exceptions. + +Class: + BaseService +""" +from typing import List, Union +from enum import Enum +import re +from ..net.http_client import HTTPClient + + +class BaseService: + """ + A class to represent a base serivce + + Attributes + ---------- + _url_prefix : str + The base URL + + Methods + ------- + + set_api_key(api_key: str, api_key_header: str = None) -> None: + Sets api key and api key header name + def _add_required_headers(headers: dict): + Request authorization headers + def set_base_url(url: str): + Sets the base url + """ + + _url_prefix = "{protocol}://{ip}:{port}" + + _http = HTTPClient(None) + + def __init__(self, api_key: str = "", api_key_header: str = "X-Plex-Token") -> None: + """ + Initialize client + + Parameters: + ---------- + api_key : str + The API Key access token + api_key_header : str + The API Key header name + """ + self._api_key = api_key + self._api_key_header = api_key_header + + def _pattern_matching(cls, value: str, pattern: str, variable_name: str): + if re.match(r"{}".format(pattern), value): + return value + else: + raise ValueError(f"Invalid value for {variable_name}: must match {pattern}") + + def _enum_matching( + cls, value: Union[str, Enum], enum_values: List[str], variable_name: str + ): + str_value = value.value if isinstance(value, Enum) else value + if str_value in enum_values: + return value + else: + raise ValueError( + f"Invalid value for {variable_name}: must match one of {enum_values}" + ) + + def set_base_url(self, url: str) -> None: + """ + Sets the base URL + + Parameters: + ---------- + url: + The base URL + """ + self._url_prefix = url + + def set_api_key(self, api_key: str, api_key_header: str = None) -> None: + """ + Sets api key and api key header name + + Parameters + ---------- + api_key: string + API Key value + api_key_header: string + Name of API Key + """ + self._api_key = api_key + if api_key_header is not None: + self._api_key_header = api_key_header + + def _add_required_headers(self, headers: dict): + """ + Request authorization headers + + Parameters + ---------- + headers: dict + Headers dict to add auth headers to + """ + headers["User-Agent"] = "liblab/0.1.25 PlexSDK/0.0.1 python/2.7" + headers[f"{self._api_key_header}"] = f"{self._api_key}" + return headers diff --git a/src/plexsdk/services/butler.py b/src/plexsdk/services/butler.py new file mode 100644 index 0000000..d733a68 --- /dev/null +++ b/src/plexsdk/services/butler.py @@ -0,0 +1,116 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.GetButlerTasksResponse import ( + GetButlerTasksResponse as GetButlerTasksResponseModel, +) +from ..models.TaskName import TaskName as TaskNameModel + + +class Butler(BaseService): + def get_butler_tasks(self) -> GetButlerTasksResponseModel: + """ + Get Butler tasks + """ + + url_endpoint = "/butler" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetButlerTasksResponseModel(**res) + return res + + def start_all_tasks(self): + """ + Start all Butler tasks + """ + + url_endpoint = "/butler" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.post(final_url, headers, {}, True) + return res + + def stop_all_tasks(self): + """ + Stop all Butler tasks + """ + + url_endpoint = "/butler" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res + + def start_task(self, task_name: TaskNameModel): + """ + Start a single Butler task + Parameters: + ---------- + task_name: TaskName + the name of the task to be started. + """ + + url_endpoint = "/butler/{task_name}" + headers = {} + self._add_required_headers(headers) + if not task_name: + raise ValueError( + "Parameter task_name is required, cannot be empty or blank." + ) + validated_task_name = self._enum_matching( + task_name, TaskNameModel.list(), "task_name" + ) + url_endpoint = url_endpoint.replace( + "{task_name}", + quote( + str( + query_serializer.serialize_path( + "simple", False, validated_task_name, None + ) + ) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.post(final_url, headers, {}, True) + return res + + def stop_task(self, task_name: TaskNameModel): + """ + Stop a single Butler task + Parameters: + ---------- + task_name: TaskName + The name of the task to be started. + """ + + url_endpoint = "/butler/{task_name}" + headers = {} + self._add_required_headers(headers) + if not task_name: + raise ValueError( + "Parameter task_name is required, cannot be empty or blank." + ) + validated_task_name = self._enum_matching( + task_name, TaskNameModel.list(), "task_name" + ) + url_endpoint = url_endpoint.replace( + "{task_name}", + quote( + str( + query_serializer.serialize_path( + "simple", False, validated_task_name, None + ) + ) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res diff --git a/src/plexsdk/services/hubs.py b/src/plexsdk/services/hubs.py new file mode 100644 index 0000000..4987dd0 --- /dev/null +++ b/src/plexsdk/services/hubs.py @@ -0,0 +1,93 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.OnlyTransient import OnlyTransient as OnlyTransientModel + + +class Hubs(BaseService): + def get_global_hubs( + self, count: float = None, only_transient: OnlyTransientModel = None + ): + """ + Get Global Hubs + Parameters: + ---------- + count: float + The number of items to return with each hub. + only_transient: OnlyTransient + Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). + """ + + url_endpoint = "/hubs" + headers = {} + query_params = [] + self._add_required_headers(headers) + if count: + query_params.append( + query_serializer.serialize_query("form", False, "count", count) + ) + if only_transient: + validated_only_transient = self._enum_matching( + only_transient, OnlyTransientModel.list(), "only_transient" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "onlyTransient", validated_only_transient + ) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_library_hubs( + self, + section_id: float, + count: float = None, + only_transient: OnlyTransientModel = None, + ): + """ + Get library specific hubs + Parameters: + ---------- + section_id: float + the Id of the library to query + count: float + The number of items to return with each hub. + only_transient: OnlyTransient + Only return hubs which are "transient", meaning those which are prone to changing after media playback or addition (e.g. On Deck, or Recently Added). + """ + + url_endpoint = "/hubs/sections/{section_id}" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + if count: + query_params.append( + query_serializer.serialize_query("form", False, "count", count) + ) + if only_transient: + validated_only_transient = self._enum_matching( + only_transient, OnlyTransientModel.list(), "only_transient" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "onlyTransient", validated_only_transient + ) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res diff --git a/src/plexsdk/services/library.py b/src/plexsdk/services/library.py new file mode 100644 index 0000000..61aad04 --- /dev/null +++ b/src/plexsdk/services/library.py @@ -0,0 +1,354 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.GetRecentlyAddedResponse import ( + GetRecentlyAddedResponse as GetRecentlyAddedResponseModel, +) +from ..models.IncludeDetails import IncludeDetails as IncludeDetailsModel +from ..models.GetOnDeckResponse import GetOnDeckResponse as GetOnDeckResponseModel + + +class Library(BaseService): + def get_file_hash(self, url: str, type_: float = None): + """ + Get Hash Value + Parameters: + ---------- + url: str + This is the path to the local file, must be prefixed by `file://` + type: float + Item type + """ + + url_endpoint = "/library/hashes" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not url: + raise ValueError("Parameter url is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "url", url)) + if type_: + query_params.append( + query_serializer.serialize_query("form", False, "type_", type_) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_recently_added(self) -> GetRecentlyAddedResponseModel: + """ + Get Recently Added + """ + + url_endpoint = "/library/recentlyAdded" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetRecentlyAddedResponseModel(**res) + return res + + def get_libraries(self): + """ + Get All Libraries + """ + + url_endpoint = "/library/sections" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_library( + self, section_id: float, include_details: IncludeDetailsModel = None + ): + """ + Get Library Details + Parameters: + ---------- + section_id: float + the Id of the library to query + include_details: IncludeDetails + Whether or not to include details for a section (types, filters, and sorts). + Only exists for backwards compatibility, media providers other than the server libraries have it on always. + + """ + + url_endpoint = "/library/sections/{section_id}" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + if include_details: + validated_include_details = self._enum_matching( + include_details, IncludeDetailsModel.list(), "include_details" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "includeDetails", validated_include_details + ) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def delete_library(self, section_id: float): + """ + Delete Library Section + Parameters: + ---------- + section_id: float + the Id of the library to query + """ + + url_endpoint = "/library/sections/{section_id}" + headers = {} + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res + + def get_library_items( + self, section_id: float, type_: float = None, filter: str = None + ): + """ + Get Library Items + Parameters: + ---------- + section_id: float + the Id of the library to query + type: float + item type + filter: str + the filter parameter + """ + + url_endpoint = "/library/sections/{section_id}/all" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + if type_: + query_params.append( + query_serializer.serialize_query("form", False, "type_", type_) + ) + if filter: + query_params.append( + query_serializer.serialize_query("form", False, "filter", filter) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def refresh_library(self, section_id: float): + """ + Refresh Library + Parameters: + ---------- + section_id: float + the Id of the library to refresh + """ + + url_endpoint = "/library/sections/{section_id}/refresh" + headers = {} + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_latest_library_items( + self, type_: float, section_id: float, filter: str = None + ): + """ + Get Latest Library Items + Parameters: + ---------- + section_id: float + the Id of the library to query + type: float + item type + filter: str + the filter parameter + """ + + url_endpoint = "/library/sections/{section_id}/latest" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + if not type_: + raise ValueError("Parameter type_ is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "type_", type_) + ) + if filter: + query_params.append( + query_serializer.serialize_query("form", False, "filter", filter) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_common_library_items( + self, type_: float, section_id: float, filter: str = None + ): + """ + Get Common Library Items + Parameters: + ---------- + section_id: float + the Id of the library to query + type: float + item type + filter: str + the filter parameter + """ + + url_endpoint = "/library/sections/{section_id}/common" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not section_id: + raise ValueError( + "Parameter section_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{section_id}", + quote( + str(query_serializer.serialize_path("simple", False, section_id, None)) + ), + ) + if not type_: + raise ValueError("Parameter type_ is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "type_", type_) + ) + if filter: + query_params.append( + query_serializer.serialize_query("form", False, "filter", filter) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_metadata(self, rating_key: float): + """ + Get Items Metadata + Parameters: + ---------- + rating_key: float + the id of the library item to return the children of. + """ + + url_endpoint = "/library/metadata/{rating_key}" + headers = {} + self._add_required_headers(headers) + if not rating_key: + raise ValueError( + "Parameter rating_key is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{rating_key}", + quote( + str(query_serializer.serialize_path("simple", False, rating_key, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_metadata_children(self, rating_key: float): + """ + Get Items Children + Parameters: + ---------- + rating_key: float + the id of the library item to return the children of. + """ + + url_endpoint = "/library/metadata/{rating_key}/children" + headers = {} + self._add_required_headers(headers) + if not rating_key: + raise ValueError( + "Parameter rating_key is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{rating_key}", + quote( + str(query_serializer.serialize_path("simple", False, rating_key, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_on_deck(self) -> GetOnDeckResponseModel: + """ + Get On Deck + """ + + url_endpoint = "/library/onDeck" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetOnDeckResponseModel(**res) + return res diff --git a/src/plexsdk/services/log.py b/src/plexsdk/services/log.py new file mode 100644 index 0000000..1443abc --- /dev/null +++ b/src/plexsdk/services/log.py @@ -0,0 +1,75 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.Level import Level as LevelModel + + +class Log(BaseService): + def log_line(self, source: str, message: str, level: LevelModel): + """ + Logging a single line message. + Parameters: + ---------- + level: Level + An integer log level to write to the PMS log with. + 0: Error + 1: Warning + 2: Info + 3: Debug + 4: Verbose + + message: str + The text of the message to write to the log. + source: str + a string indicating the source of the message. + """ + + url_endpoint = "/log" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not level: + raise ValueError("Parameter level is required, cannot be empty or blank.") + validated_level = self._enum_matching(level, LevelModel.list(), "level") + query_params.append( + query_serializer.serialize_query("form", False, "level", validated_level) + ) + if not message: + raise ValueError("Parameter message is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "message", message) + ) + if not source: + raise ValueError("Parameter source is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "source", source) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def log_multi_line(self): + """ + Logging a multi-line message + """ + + url_endpoint = "/log" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.post(final_url, headers, {}, True) + return res + + def enable_paper_trail(self): + """ + Enabling Papertrail + """ + + url_endpoint = "/log/networked" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res diff --git a/src/plexsdk/services/media.py b/src/plexsdk/services/media.py new file mode 100644 index 0000000..0b73faf --- /dev/null +++ b/src/plexsdk/services/media.py @@ -0,0 +1,79 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService + + +class Media(BaseService): + def mark_played(self, key: float): + """ + Mark Media Played + Parameters: + ---------- + key: float + The media key to mark as played + """ + + url_endpoint = "/:/scrobble" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not key: + raise ValueError("Parameter key is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "key", key)) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def mark_unplayed(self, key: float): + """ + Mark Media Unplayed + Parameters: + ---------- + key: float + The media key to mark as Unplayed + """ + + url_endpoint = "/:/unscrobble" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not key: + raise ValueError("Parameter key is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "key", key)) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def update_play_progress(self, state: str, time: float, key: str): + """ + Update Media Play Progress + Parameters: + ---------- + key: str + the media key + time: float + The time, in milliseconds, used to set the media playback progress. + state: str + The playback state of the media item. + """ + + url_endpoint = "/:/progress" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not key: + raise ValueError("Parameter key is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "key", key)) + if not time: + raise ValueError("Parameter time is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "time", time) + ) + if not state: + raise ValueError("Parameter state is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "state", state) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.post(final_url, headers, {}, True) + return res diff --git a/src/plexsdk/services/playlists.py b/src/plexsdk/services/playlists.py new file mode 100644 index 0000000..a8b1a80 --- /dev/null +++ b/src/plexsdk/services/playlists.py @@ -0,0 +1,326 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.Type import Type as TypeModel +from ..models.Smart import Smart as SmartModel +from ..models.PlaylistType import PlaylistType as PlaylistTypeModel +from ..models.Force import Force as ForceModel + + +class Playlists(BaseService): + def create_playlist( + self, + smart: SmartModel, + type_: TypeModel, + title: str, + uri: str = None, + play_queue_id: float = None, + ): + """ + Create a Playlist + Parameters: + ---------- + title: str + name of the playlist + type: Type + type of playlist to create + smart: Smart + whether the playlist is smart or not + uri: str + the content URI for the playlist + play_queue_id: float + the play queue to copy to a playlist + """ + + url_endpoint = "/playlists" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not title: + raise ValueError("Parameter title is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "title", title) + ) + if not type_: + raise ValueError("Parameter type_ is required, cannot be empty or blank.") + validated_type_ = self._enum_matching(type_, TypeModel.list(), "type_") + query_params.append( + query_serializer.serialize_query("form", False, "type_", validated_type_) + ) + if not smart: + raise ValueError("Parameter smart is required, cannot be empty or blank.") + validated_smart = self._enum_matching(smart, SmartModel.list(), "smart") + query_params.append( + query_serializer.serialize_query("form", False, "smart", validated_smart) + ) + if uri: + query_params.append( + query_serializer.serialize_query("form", False, "uri", uri) + ) + if play_queue_id: + query_params.append( + query_serializer.serialize_query( + "form", False, "playQueueID", play_queue_id + ) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.post(final_url, headers, {}, True) + return res + + def get_playlists( + self, playlist_type: PlaylistTypeModel = None, smart: SmartModel = None + ): + """ + Get All Playlists + Parameters: + ---------- + playlist_type: PlaylistType + limit to a type of playlist. + smart: Smart + type of playlists to return (default is all). + """ + + url_endpoint = "/playlists/all" + headers = {} + query_params = [] + self._add_required_headers(headers) + if playlist_type: + validated_playlist_type = self._enum_matching( + playlist_type, PlaylistTypeModel.list(), "playlist_type" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "playlistType", validated_playlist_type + ) + ) + if smart: + validated_smart = self._enum_matching(smart, SmartModel.list(), "smart") + query_params.append( + query_serializer.serialize_query( + "form", False, "smart", validated_smart + ) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_playlist(self, playlist_id: float): + """ + Retrieve Playlist + Parameters: + ---------- + playlist_id: float + the ID of the playlist + """ + + url_endpoint = "/playlists/{playlist_id}" + headers = {} + self._add_required_headers(headers) + if not playlist_id: + raise ValueError( + "Parameter playlist_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{playlist_id}", + quote( + str(query_serializer.serialize_path("simple", False, playlist_id, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def update_playlist(self, playlist_id: float): + """ + Update a Playlist + Parameters: + ---------- + playlist_id: float + the ID of the playlist + """ + + url_endpoint = "/playlists/{playlist_id}" + headers = {} + self._add_required_headers(headers) + if not playlist_id: + raise ValueError( + "Parameter playlist_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{playlist_id}", + quote( + str(query_serializer.serialize_path("simple", False, playlist_id, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.put(final_url, headers, {}, True) + return res + + def delete_playlist(self, playlist_id: float): + """ + Deletes a Playlist + Parameters: + ---------- + playlist_id: float + the ID of the playlist + """ + + url_endpoint = "/playlists/{playlist_id}" + headers = {} + self._add_required_headers(headers) + if not playlist_id: + raise ValueError( + "Parameter playlist_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{playlist_id}", + quote( + str(query_serializer.serialize_path("simple", False, playlist_id, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res + + def get_playlist_contents(self, type_: float, playlist_id: float): + """ + Retrieve Playlist Contents + Parameters: + ---------- + playlist_id: float + the ID of the playlist + type: float + the metadata type of the item to return + """ + + url_endpoint = "/playlists/{playlist_id}/items" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not playlist_id: + raise ValueError( + "Parameter playlist_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{playlist_id}", + quote( + str(query_serializer.serialize_path("simple", False, playlist_id, None)) + ), + ) + if not type_: + raise ValueError("Parameter type_ is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "type_", type_) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def add_playlist_contents(self, play_queue_id: float, uri: str, playlist_id: float): + """ + Adding to a Playlist + Parameters: + ---------- + playlist_id: float + the ID of the playlist + uri: str + the content URI for the playlist + play_queue_id: float + the play queue to add to a playlist + """ + + url_endpoint = "/playlists/{playlist_id}/items" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not playlist_id: + raise ValueError( + "Parameter playlist_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{playlist_id}", + quote( + str(query_serializer.serialize_path("simple", False, playlist_id, None)) + ), + ) + if not uri: + raise ValueError("Parameter uri is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "uri", uri)) + if not play_queue_id: + raise ValueError( + "Parameter play_queue_id is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "playQueueID", play_queue_id + ) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.put(final_url, headers, {}, True) + return res + + def clear_playlist_contents(self, playlist_id: float): + """ + Delete Playlist Contents + Parameters: + ---------- + playlist_id: float + the ID of the playlist + """ + + url_endpoint = "/playlists/{playlist_id}/items" + headers = {} + self._add_required_headers(headers) + if not playlist_id: + raise ValueError( + "Parameter playlist_id is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{playlist_id}", + quote( + str(query_serializer.serialize_path("simple", False, playlist_id, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res + + def upload_playlist(self, force: ForceModel, path: str): + """ + Upload Playlist + Parameters: + ---------- + path: str + absolute path to a directory on the server where m3u files are stored, or the absolute path to a playlist file on the server. + If the `path` argument is a directory, that path will be scanned for playlist files to be processed. + Each file in that directory creates a separate playlist, with a name based on the filename of the file that created it. + The GUID of each playlist is based on the filename. + If the `path` argument is a file, that file will be used to create a new playlist, with the name based on the filename of the file that created it. + The GUID of each playlist is based on the filename. + + force: Force + force overwriting of duplicate playlists. By default, a playlist file uploaded with the same path will overwrite the existing playlist. + The `force` argument is used to disable overwriting. If the `force` argument is set to 0, a new playlist will be created suffixed with the date and time that the duplicate was uploaded. + + """ + + url_endpoint = "/playlists/upload" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not path: + raise ValueError("Parameter path is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "path", path) + ) + if not force: + raise ValueError("Parameter force is required, cannot be empty or blank.") + validated_force = self._enum_matching(force, ForceModel.list(), "force") + query_params.append( + query_serializer.serialize_query("form", False, "force", validated_force) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.post(final_url, headers, {}, True) + return res diff --git a/src/plexsdk/services/search.py b/src/plexsdk/services/search.py new file mode 100644 index 0000000..5800b66 --- /dev/null +++ b/src/plexsdk/services/search.py @@ -0,0 +1,102 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.GetSearchResultsResponse import ( + GetSearchResultsResponse as GetSearchResultsResponseModel, +) + + +class Search(BaseService): + def perform_search(self, query: str, section_id: float = None, limit: float = None): + """ + Perform a search + Parameters: + ---------- + query: str + The query term + section_id: float + This gives context to the search, and can result in re-ordering of search result hubs + limit: float + The number of items to return per hub + """ + + url_endpoint = "/hubs/search" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not query: + raise ValueError("Parameter query is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "query", query) + ) + if section_id: + query_params.append( + query_serializer.serialize_query("form", False, "sectionId", section_id) + ) + if limit: + query_params.append( + query_serializer.serialize_query("form", False, "limit", limit) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def perform_voice_search( + self, query: str, section_id: float = None, limit: float = None + ): + """ + Perform a voice search + Parameters: + ---------- + query: str + The query term + section_id: float + This gives context to the search, and can result in re-ordering of search result hubs + limit: float + The number of items to return per hub + """ + + url_endpoint = "/hubs/search/voice" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not query: + raise ValueError("Parameter query is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "query", query) + ) + if section_id: + query_params.append( + query_serializer.serialize_query("form", False, "sectionId", section_id) + ) + if limit: + query_params.append( + query_serializer.serialize_query("form", False, "limit", limit) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_search_results(self, query: str) -> GetSearchResultsResponseModel: + """ + Get Search Results + Parameters: + ---------- + query: str + The search query string to use + """ + + url_endpoint = "/search" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not query: + raise ValueError("Parameter query is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "query", query) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetSearchResultsResponseModel(**res) + return res diff --git a/src/plexsdk/services/security.py b/src/plexsdk/services/security.py new file mode 100644 index 0000000..cc88811 --- /dev/null +++ b/src/plexsdk/services/security.py @@ -0,0 +1,60 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.SecurityType import SecurityType as SecurityTypeModel +from ..models.Scope import Scope as ScopeModel + + +class Security(BaseService): + def get_transient_token(self, scope: ScopeModel, type_: SecurityTypeModel): + """ + Get a Transient Token. + Parameters: + ---------- + type: SecurityType + `delegation` - This is the only supported `type` parameter. + scope: Scope + `all` - This is the only supported `scope` parameter. + """ + + url_endpoint = "/security/token" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not type_: + raise ValueError("Parameter type_ is required, cannot be empty or blank.") + validated_type_ = self._enum_matching(type_, SecurityTypeModel.list(), "type_") + query_params.append( + query_serializer.serialize_query("form", False, "type_", validated_type_) + ) + if not scope: + raise ValueError("Parameter scope is required, cannot be empty or blank.") + validated_scope = self._enum_matching(scope, ScopeModel.list(), "scope") + query_params.append( + query_serializer.serialize_query("form", False, "scope", validated_scope) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_source_connection_information(self, source: str): + """ + Get Source Connection Information + Parameters: + ---------- + source: str + The source identifier with an included prefix. + """ + + url_endpoint = "/security/resources" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not source: + raise ValueError("Parameter source is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "source", source) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res diff --git a/src/plexsdk/services/server.py b/src/plexsdk/services/server.py new file mode 100644 index 0000000..a883cbc --- /dev/null +++ b/src/plexsdk/services/server.py @@ -0,0 +1,210 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.GetServerCapabilitiesResponse import ( + GetServerCapabilitiesResponse as GetServerCapabilitiesResponseModel, +) +from ..models.GetAvailableClientsResponse import ( + GetAvailableClientsResponse as GetAvailableClientsResponseModel, +) +from ..models.GetAvailableClientsResponse import ( + GetAvailableClientsResponseItem as GetAvailableClientsResponseItemModel, +) +from ..models.GetDevicesResponse import GetDevicesResponse as GetDevicesResponseModel +from ..models.GetServerIdentityResponse import ( + GetServerIdentityResponse as GetServerIdentityResponseModel, +) +from ..models.GetMyPlexAccountResponse import ( + GetMyPlexAccountResponse as GetMyPlexAccountResponseModel, +) +from ..models.MinSize import MinSize as MinSizeModel +from ..models.Upscale import Upscale as UpscaleModel +from ..models.GetServerListResponse import ( + GetServerListResponse as GetServerListResponseModel, +) + + +class Server(BaseService): + def get_server_capabilities(self) -> GetServerCapabilitiesResponseModel: + """ + Server Capabilities + """ + + url_endpoint = "/" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetServerCapabilitiesResponseModel(**res) + return res + + def get_server_preferences(self): + """ + Get Server Preferences + """ + + url_endpoint = "/:/prefs" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_available_clients(self) -> GetAvailableClientsResponseModel: + """ + Get Available Clients + """ + + url_endpoint = "/clients" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, list): + return [GetAvailableClientsResponseItemModel(**model) for model in res] + return res + + def get_devices(self) -> GetDevicesResponseModel: + """ + Get Devices + """ + + url_endpoint = "/devices" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetDevicesResponseModel(**res) + return res + + def get_server_identity(self) -> GetServerIdentityResponseModel: + """ + Get Server Identity + """ + + url_endpoint = "/identity" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetServerIdentityResponseModel(**res) + return res + + def get_my_plex_account(self) -> GetMyPlexAccountResponseModel: + """ + Get MyPlex Account + """ + + url_endpoint = "/myplex/account" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetMyPlexAccountResponseModel(**res) + return res + + def get_resized_photo( + self, + url: str, + upscale: UpscaleModel, + min_size: MinSizeModel, + blur: float, + opacity: int, + height: float, + width: float, + ): + """ + Get a Resized Photo + Parameters: + ---------- + width: float + The width for the resized photo + height: float + The height for the resized photo + opacity: int + The opacity for the resized photo + blur: float + The width for the resized photo + min_size: MinSize + images are always scaled proportionally. A value of '1' in minSize will make the smaller native dimension the dimension resized against. + upscale: Upscale + allow images to be resized beyond native dimensions. + url: str + path to image within Plex + """ + + url_endpoint = "/photo/:/transcode" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not width: + raise ValueError("Parameter width is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "width", width) + ) + if not height: + raise ValueError("Parameter height is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "height", height) + ) + if not opacity: + raise ValueError("Parameter opacity is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "opacity", opacity) + ) + if not blur: + raise ValueError("Parameter blur is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "blur", blur) + ) + if not min_size: + raise ValueError( + "Parameter min_size is required, cannot be empty or blank." + ) + validated_min_size = self._enum_matching( + min_size, MinSizeModel.list(), "min_size" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "minSize", validated_min_size + ) + ) + if not upscale: + raise ValueError("Parameter upscale is required, cannot be empty or blank.") + validated_upscale = self._enum_matching(upscale, UpscaleModel.list(), "upscale") + query_params.append( + query_serializer.serialize_query( + "form", False, "upscale", validated_upscale + ) + ) + if not url: + raise ValueError("Parameter url is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "url", url)) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_server_list(self) -> GetServerListResponseModel: + """ + Get Server List + """ + + url_endpoint = "/servers" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetServerListResponseModel(**res) + return res diff --git a/src/plexsdk/services/sessions.py b/src/plexsdk/services/sessions.py new file mode 100644 index 0000000..51debed --- /dev/null +++ b/src/plexsdk/services/sessions.py @@ -0,0 +1,75 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.GetTranscodeSessionsResponse import ( + GetTranscodeSessionsResponse as GetTranscodeSessionsResponseModel, +) + + +class Sessions(BaseService): + def get_sessions(self): + """ + Get Active Sessions + """ + + url_endpoint = "/status/sessions" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_session_history(self): + """ + Get Session History + """ + + url_endpoint = "/status/sessions/history/all" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def get_transcode_sessions(self) -> GetTranscodeSessionsResponseModel: + """ + Get Transcode Sessions + """ + + url_endpoint = "/transcode/sessions" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + if res and isinstance(res, dict): + return GetTranscodeSessionsResponseModel(**res) + return res + + def stop_transcode_session(self, session_key: str): + """ + Stop a Transcode Session + Parameters: + ---------- + session_key: str + the Key of the transcode session to stop + """ + + url_endpoint = "/transcode/sessions/{session_key}" + headers = {} + self._add_required_headers(headers) + if not session_key: + raise ValueError( + "Parameter session_key is required, cannot be empty or blank." + ) + url_endpoint = url_endpoint.replace( + "{session_key}", + quote( + str(query_serializer.serialize_path("simple", False, session_key, None)) + ), + ) + final_url = self._url_prefix + url_endpoint + res = self._http.delete(final_url, headers, True) + return res diff --git a/src/plexsdk/services/updater.py b/src/plexsdk/services/updater.py new file mode 100644 index 0000000..9a7c8cd --- /dev/null +++ b/src/plexsdk/services/updater.py @@ -0,0 +1,84 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.Download import Download as DownloadModel +from ..models.Tonight import Tonight as TonightModel +from ..models.Skip import Skip as SkipModel + + +class Updater(BaseService): + def get_update_status(self): + """ + Querying status of updates + """ + + url_endpoint = "/updater/status" + headers = {} + self._add_required_headers(headers) + + final_url = self._url_prefix + url_endpoint + res = self._http.get(final_url, headers, True) + return res + + def check_for_updates(self, download: DownloadModel = None): + """ + Checking for updates + Parameters: + ---------- + download: Download + Indicate that you want to start download any updates found. + """ + + url_endpoint = "/updater/check" + headers = {} + query_params = [] + self._add_required_headers(headers) + if download: + validated_download = self._enum_matching( + download, DownloadModel.list(), "download" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "download", validated_download + ) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.put(final_url, headers, {}, True) + return res + + def apply_updates(self, tonight: TonightModel = None, skip: SkipModel = None): + """ + Apply Updates + Parameters: + ---------- + tonight: Tonight + Indicate that you want the update to run during the next Butler execution. Omitting this or setting it to false indicates that the update should install + skip: Skip + Indicate that the latest version should be marked as skipped. The entry for this version will have the `state` set to `skipped`. + """ + + url_endpoint = "/updater/apply" + headers = {} + query_params = [] + self._add_required_headers(headers) + if tonight: + validated_tonight = self._enum_matching( + tonight, TonightModel.list(), "tonight" + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "tonight", validated_tonight + ) + ) + if skip: + validated_skip = self._enum_matching(skip, SkipModel.list(), "skip") + query_params.append( + query_serializer.serialize_query("form", False, "skip", validated_skip) + ) + final_url = self._url_prefix + url_endpoint + if len(query_params) > 0: + final_url += "?" + "&".join(query_params) + res = self._http.put(final_url, headers, {}, True) + return res diff --git a/src/plexsdk/services/video.py b/src/plexsdk/services/video.py new file mode 100644 index 0000000..f99b9f3 --- /dev/null +++ b/src/plexsdk/services/video.py @@ -0,0 +1,266 @@ +from urllib.parse import quote +from ..net import query_serializer +from .base import BaseService +from ..models.State import State as StateModel + + +class Video(BaseService): + def start_universal_transcode( + self, + protocol: str, + part_index: float, + media_index: float, + path: str, + has_mde: float, + fast_seek: float = None, + direct_play: float = None, + direct_stream: float = None, + subtitle_size: float = None, + subtites: str = None, + audio_boost: float = None, + location: str = None, + media_buffer_size: float = None, + session: str = None, + add_debug_overlay: float = None, + auto_adjust_quality: float = None, + ): + """ + Start Universal Transcode + Parameters: + ---------- + has_mde: float + Whether the media item has MDE + path: str + The path to the media item to transcode + media_index: float + The index of the media item to transcode + part_index: float + The index of the part to transcode + protocol: str + The protocol to use for the transcode session + fast_seek: float + Whether to use fast seek or not + direct_play: float + Whether to use direct play or not + direct_stream: float + Whether to use direct stream or not + subtitle_size: float + The size of the subtitles + subtites: str + The subtitles + audio_boost: float + The audio boost + location: str + The location of the transcode session + media_buffer_size: float + The size of the media buffer + session: str + The session ID + add_debug_overlay: float + Whether to add a debug overlay or not + auto_adjust_quality: float + Whether to auto adjust quality or not + """ + + url_endpoint = "/video/:/transcode/universal/start.mpd" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not has_mde: + raise ValueError("Parameter has_mde is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "hasMDE", has_mde) + ) + if not path: + raise ValueError("Parameter path is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "path", path) + ) + if not media_index: + raise ValueError( + "Parameter media_index is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query("form", False, "mediaIndex", media_index) + ) + if not part_index: + raise ValueError( + "Parameter part_index is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query("form", False, "partIndex", part_index) + ) + if not protocol: + raise ValueError( + "Parameter protocol is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query("form", False, "protocol", protocol) + ) + if fast_seek: + query_params.append( + query_serializer.serialize_query("form", False, "fastSeek", fast_seek) + ) + if direct_play: + query_params.append( + query_serializer.serialize_query( + "form", False, "directPlay", direct_play + ) + ) + if direct_stream: + query_params.append( + query_serializer.serialize_query( + "form", False, "directStream", direct_stream + ) + ) + if subtitle_size: + query_params.append( + query_serializer.serialize_query( + "form", False, "subtitleSize", subtitle_size + ) + ) + if subtites: + query_params.append( + query_serializer.serialize_query("form", False, "subtites", subtites) + ) + if audio_boost: + query_params.append( + query_serializer.serialize_query( + "form", False, "audioBoost", audio_boost + ) + ) + if location: + query_params.append( + query_serializer.serialize_query("form", False, "location", location) + ) + if media_buffer_size: + query_params.append( + query_serializer.serialize_query( + "form", False, "mediaBufferSize", media_buffer_size + ) + ) + if session: + query_params.append( + query_serializer.serialize_query("form", False, "session", session) + ) + if add_debug_overlay: + query_params.append( + query_serializer.serialize_query( + "form", False, "addDebugOverlay", add_debug_overlay + ) + ) + if auto_adjust_quality: + query_params.append( + query_serializer.serialize_query( + "form", False, "autoAdjustQuality", auto_adjust_quality + ) + ) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res + + def get_timeline( + self, + row: float, + play_back_time: float, + play_queue_item_id: float, + context: str, + duration: float, + time: float, + has_mde: float, + state: StateModel, + key: str, + rating_key: float, + ): + """ + Get the timeline for a media item + Parameters: + ---------- + rating_key: float + The rating key of the media item + key: str + The key of the media item to get the timeline for + state: State + The state of the media item + has_mde: float + Whether the media item has MDE + time: float + The time of the media item + duration: float + The duration of the media item + context: str + The context of the media item + play_queue_item_id: float + The play queue item ID of the media item + play_back_time: float + The playback time of the media item + row: float + The row of the media item + """ + + url_endpoint = "/:/timeline" + headers = {} + query_params = [] + self._add_required_headers(headers) + if not rating_key: + raise ValueError( + "Parameter rating_key is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query("form", False, "ratingKey", rating_key) + ) + if not key: + raise ValueError("Parameter key is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "key", key)) + if not state: + raise ValueError("Parameter state is required, cannot be empty or blank.") + validated_state = self._enum_matching(state, StateModel.list(), "state") + query_params.append( + query_serializer.serialize_query("form", False, "state", validated_state) + ) + if not has_mde: + raise ValueError("Parameter has_mde is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "hasMDE", has_mde) + ) + if not time: + raise ValueError("Parameter time is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "time", time) + ) + if not duration: + raise ValueError( + "Parameter duration is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query("form", False, "duration", duration) + ) + if not context: + raise ValueError("Parameter context is required, cannot be empty or blank.") + query_params.append( + query_serializer.serialize_query("form", False, "context", context) + ) + if not play_queue_item_id: + raise ValueError( + "Parameter play_queue_item_id is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "playQueueItemID", play_queue_item_id + ) + ) + if not play_back_time: + raise ValueError( + "Parameter play_back_time is required, cannot be empty or blank." + ) + query_params.append( + query_serializer.serialize_query( + "form", False, "playBackTime", play_back_time + ) + ) + if not row: + raise ValueError("Parameter row is required, cannot be empty or blank.") + query_params.append(query_serializer.serialize_query("form", False, "row", row)) + final_url = self._url_prefix + url_endpoint + "?" + "&".join(query_params) + res = self._http.get(final_url, headers, True) + return res diff --git a/src/plexsdk/setup.py b/src/plexsdk/setup.py new file mode 100644 index 0000000..435cdf0 --- /dev/null +++ b/src/plexsdk/setup.py @@ -0,0 +1,9 @@ +import setuptools + +setuptools.setup( + name="PlexSDK", + version="0.0.1", + description="""An Open API Spec for interacting with Plex.tv and Plex Servers""", + license="MIT", + packages=setuptools.find_packages(), +) diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/models/__init__.py b/test/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/models/test_base.py b/test/models/test_base.py new file mode 100644 index 0000000..2da5600 --- /dev/null +++ b/test/models/test_base.py @@ -0,0 +1,14 @@ +import unittest +import responses +from http import HTTPStatus +from src.plexsdk.models.base import BaseModel +from http_exceptions import ClientException + + +class TestBaseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_butler_tasks_response.py b/test/models/test_get_butler_tasks_response.py new file mode 100644 index 0000000..f8b5f78 --- /dev/null +++ b/test/models/test_get_butler_tasks_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetButlerTasksResponse import GetButlerTasksResponse + + +class TestGetButlerTasksResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_butler_tasks_response(self): + # Create GetButlerTasksResponse class instance + test_model = GetButlerTasksResponse(ButlerTasks={"accusantium": 1}) + self.assertEqual(test_model.ButlerTasks, {"accusantium": 1}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_devices_response.py b/test/models/test_get_devices_response.py new file mode 100644 index 0000000..2944bc4 --- /dev/null +++ b/test/models/test_get_devices_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetDevicesResponse import GetDevicesResponse + + +class TestGetDevicesResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_devices_response(self): + # Create GetDevicesResponse class instance + test_model = GetDevicesResponse(MediaContainer={"velit": 1}) + self.assertEqual(test_model.MediaContainer, {"velit": 1}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_my_plex_account_response.py b/test/models/test_get_my_plex_account_response.py new file mode 100644 index 0000000..13c1108 --- /dev/null +++ b/test/models/test_get_my_plex_account_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetMyPlexAccountResponse import GetMyPlexAccountResponse + + +class TestGetMyPlexAccountResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_my_plex_account_response(self): + # Create GetMyPlexAccountResponse class instance + test_model = GetMyPlexAccountResponse(MyPlex={"repellat": 8}) + self.assertEqual(test_model.MyPlex, {"repellat": 8}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_on_deck_response.py b/test/models/test_get_on_deck_response.py new file mode 100644 index 0000000..5df6afb --- /dev/null +++ b/test/models/test_get_on_deck_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetOnDeckResponse import GetOnDeckResponse + + +class TestGetOnDeckResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_on_deck_response(self): + # Create GetOnDeckResponse class instance + test_model = GetOnDeckResponse(MediaContainer={"quisquam": 1}) + self.assertEqual(test_model.MediaContainer, {"quisquam": 1}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_recently_added_response.py b/test/models/test_get_recently_added_response.py new file mode 100644 index 0000000..9c531c2 --- /dev/null +++ b/test/models/test_get_recently_added_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetRecentlyAddedResponse import GetRecentlyAddedResponse + + +class TestGetRecentlyAddedResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_recently_added_response(self): + # Create GetRecentlyAddedResponse class instance + test_model = GetRecentlyAddedResponse(MediaContainer={"numquam": 1}) + self.assertEqual(test_model.MediaContainer, {"numquam": 1}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_search_results_response.py b/test/models/test_get_search_results_response.py new file mode 100644 index 0000000..b85f45e --- /dev/null +++ b/test/models/test_get_search_results_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetSearchResultsResponse import GetSearchResultsResponse + + +class TestGetSearchResultsResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_search_results_response(self): + # Create GetSearchResultsResponse class instance + test_model = GetSearchResultsResponse(MediaContainer={"eius": 4}) + self.assertEqual(test_model.MediaContainer, {"eius": 4}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_server_activities_response.py b/test/models/test_get_server_activities_response.py new file mode 100644 index 0000000..81d6a8c --- /dev/null +++ b/test/models/test_get_server_activities_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetServerActivitiesResponse import GetServerActivitiesResponse + + +class TestGetServerActivitiesResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_server_activities_response(self): + # Create GetServerActivitiesResponse class instance + test_model = GetServerActivitiesResponse(MediaContainer={"quisquam": 5}) + self.assertEqual(test_model.MediaContainer, {"quisquam": 5}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_server_capabilities_response.py b/test/models/test_get_server_capabilities_response.py new file mode 100644 index 0000000..608fd48 --- /dev/null +++ b/test/models/test_get_server_capabilities_response.py @@ -0,0 +1,18 @@ +import unittest +from src.plexsdk.models.GetServerCapabilitiesResponse import ( + GetServerCapabilitiesResponse, +) + + +class TestGetServerCapabilitiesResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_server_capabilities_response(self): + # Create GetServerCapabilitiesResponse class instance + test_model = GetServerCapabilitiesResponse(MediaContainer={"maiores": 3}) + self.assertEqual(test_model.MediaContainer, {"maiores": 3}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_server_identity_response.py b/test/models/test_get_server_identity_response.py new file mode 100644 index 0000000..ba551bb --- /dev/null +++ b/test/models/test_get_server_identity_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetServerIdentityResponse import GetServerIdentityResponse + + +class TestGetServerIdentityResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_server_identity_response(self): + # Create GetServerIdentityResponse class instance + test_model = GetServerIdentityResponse(MediaContainer={"eum": 1}) + self.assertEqual(test_model.MediaContainer, {"eum": 1}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_server_list_response.py b/test/models/test_get_server_list_response.py new file mode 100644 index 0000000..7a86879 --- /dev/null +++ b/test/models/test_get_server_list_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetServerListResponse import GetServerListResponse + + +class TestGetServerListResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_server_list_response(self): + # Create GetServerListResponse class instance + test_model = GetServerListResponse(MediaContainer={"asperiores": 6}) + self.assertEqual(test_model.MediaContainer, {"asperiores": 6}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/models/test_get_transcode_sessions_response.py b/test/models/test_get_transcode_sessions_response.py new file mode 100644 index 0000000..bbeb5ce --- /dev/null +++ b/test/models/test_get_transcode_sessions_response.py @@ -0,0 +1,16 @@ +import unittest +from src.plexsdk.models.GetTranscodeSessionsResponse import GetTranscodeSessionsResponse + + +class TestGetTranscodeSessionsResponseModel(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + def test_get_transcode_sessions_response(self): + # Create GetTranscodeSessionsResponse class instance + test_model = GetTranscodeSessionsResponse(MediaContainer={"eaque": 9}) + self.assertEqual(test_model.MediaContainer, {"eaque": 9}) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/__init__.py b/test/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/services/test_activities.py b/test/services/test_activities.py new file mode 100644 index 0000000..bcf0d82 --- /dev/null +++ b/test/services/test_activities.py @@ -0,0 +1,67 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.activities import Activities + + +class TestActivities_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_server_activities(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/activities", json={}, status=200) + # call the method to test + test_service = Activities("testkey") + response = test_service.get_server_activities() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_server_activities_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/activities", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Activities("testkey") + test_service.get_server_activities() + responses.reset() + + @responses.activate + def test_cancel_server_activities(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/activities/3963273161", json={}, status=200 + ) + # call the method to test + test_service = Activities("testkey") + response = test_service.cancel_server_activities("3963273161") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_cancel_server_activities_required_fields_missing(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/activities/8877676477", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Activities("testkey") + test_service.cancel_server_activities() + responses.reset(), + + @responses.activate + def test_cancel_server_activities_error_on_non_200(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/activities/6744354026", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Activities("testkey") + test_service.cancel_server_activities("6744354026") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_base.py b/test/services/test_base.py new file mode 100644 index 0000000..331da0a --- /dev/null +++ b/test/services/test_base.py @@ -0,0 +1,14 @@ +import unittest +import responses +from http import HTTPStatus +from src.plexsdk.services.base import BaseService +from http_exceptions import ClientException + + +class TestBaseService(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_butler.py b/test/services/test_butler.py new file mode 100644 index 0000000..21afdac --- /dev/null +++ b/test/services/test_butler.py @@ -0,0 +1,139 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.butler import Butler + + +class TestButler_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_butler_tasks(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/butler", json={}, status=200) + # call the method to test + test_service = Butler("testkey") + response = test_service.get_butler_tasks() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_butler_tasks_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/butler", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Butler("testkey") + test_service.get_butler_tasks() + responses.reset() + + @responses.activate + def test_start_all_tasks(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/butler", json={}, status=200) + # call the method to test + test_service = Butler("testkey") + response = test_service.start_all_tasks() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_start_all_tasks_error_on_non_200(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/butler", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Butler("testkey") + test_service.start_all_tasks() + responses.reset() + + @responses.activate + def test_stop_all_tasks(self): + # Mock the API response + responses.delete("{protocol}://{ip}:{port}/butler", json={}, status=200) + # call the method to test + test_service = Butler("testkey") + response = test_service.stop_all_tasks() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_stop_all_tasks_error_on_non_200(self): + # Mock the API response + responses.delete("{protocol}://{ip}:{port}/butler", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Butler("testkey") + test_service.stop_all_tasks() + responses.reset() + + @responses.activate + def test_start_task(self): + # Mock the API response + responses.post( + "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=200 + ) + # call the method to test + test_service = Butler("testkey") + response = test_service.start_task("BackupDatabase") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_start_task_required_fields_missing(self): + # Mock the API response + responses.post( + "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Butler("testkey") + test_service.start_task() + responses.reset(), + + @responses.activate + def test_start_task_error_on_non_200(self): + # Mock the API response + responses.post( + "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Butler("testkey") + test_service.start_task("BackupDatabase") + responses.reset() + + @responses.activate + def test_stop_task(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=200 + ) + # call the method to test + test_service = Butler("testkey") + response = test_service.stop_task("BackupDatabase") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_stop_task_required_fields_missing(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Butler("testkey") + test_service.stop_task() + responses.reset(), + + @responses.activate + def test_stop_task_error_on_non_200(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/butler/BackupDatabase", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Butler("testkey") + test_service.stop_task("BackupDatabase") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_hubs.py b/test/services/test_hubs.py new file mode 100644 index 0000000..74e05e5 --- /dev/null +++ b/test/services/test_hubs.py @@ -0,0 +1,67 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.hubs import Hubs + + +class TestHubs_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_global_hubs(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs", json={}, status=200) + # call the method to test + test_service = Hubs("testkey") + response = test_service.get_global_hubs(8, 4) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_global_hubs_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Hubs("testkey") + test_service.get_global_hubs(8, 3) + responses.reset() + + @responses.activate + def test_get_library_hubs(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/hubs/sections/3684333305", json={}, status=200 + ) + # call the method to test + test_service = Hubs("testkey") + response = test_service.get_library_hubs(3684333305, 2, 4) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_library_hubs_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/hubs/sections/2635728470", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Hubs("testkey") + test_service.get_library_hubs() + responses.reset(), + + @responses.activate + def test_get_library_hubs_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/hubs/sections/5770327453", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Hubs("testkey") + test_service.get_library_hubs(5770327453, 9, 3) + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_library.py b/test/services/test_library.py new file mode 100644 index 0000000..1ead67f --- /dev/null +++ b/test/services/test_library.py @@ -0,0 +1,399 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.library import Library + + +class TestLibrary_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_file_hash(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/hashes", json={}, status=200) + # call the method to test + test_service = Library("testkey") + response = test_service.get_file_hash("asperiores", 7) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_file_hash_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/hashes", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_file_hash() + responses.reset(), + + @responses.activate + def test_get_file_hash_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/hashes", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_file_hash("cum", 4) + responses.reset() + + @responses.activate + def test_get_recently_added(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/recentlyAdded", json={}, status=200 + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_recently_added() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_recently_added_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/recentlyAdded", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_recently_added() + responses.reset() + + @responses.activate + def test_get_libraries(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/sections", json={}, status=200) + # call the method to test + test_service = Library("testkey") + response = test_service.get_libraries() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_libraries_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/sections", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_libraries() + responses.reset() + + @responses.activate + def test_get_library(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/9871766595", json={}, status=200 + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_library(9871766595, 8) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_library_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/5100915384", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_library() + responses.reset(), + + @responses.activate + def test_get_library_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/6801308709", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_library(6801308709, 3) + responses.reset() + + @responses.activate + def test_delete_library(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/library/sections/3061349599", json={}, status=200 + ) + # call the method to test + test_service = Library("testkey") + response = test_service.delete_library(3061349599) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_delete_library_required_fields_missing(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/library/sections/6921817889", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.delete_library() + responses.reset(), + + @responses.activate + def test_delete_library_error_on_non_200(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/library/sections/9683751588", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.delete_library(9683751588) + responses.reset() + + @responses.activate + def test_get_library_items(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/6582958821/all", + json={}, + status=200, + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_library_items(6582958821, 9, "quibusdam") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_library_items_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/9837157577/all", + json={}, + status=202, + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_library_items() + responses.reset(), + + @responses.activate + def test_get_library_items_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/2390106765/all", + json={}, + status=404, + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_library_items(2390106765, 1, "voluptas") + responses.reset() + + @responses.activate + def test_refresh_library(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/6321566139/refresh", + json={}, + status=200, + ) + # call the method to test + test_service = Library("testkey") + response = test_service.refresh_library(6321566139) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_refresh_library_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/1404494475/refresh", + json={}, + status=202, + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.refresh_library() + responses.reset(), + + @responses.activate + def test_refresh_library_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/5664562376/refresh", + json={}, + status=404, + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.refresh_library(5664562376) + responses.reset() + + @responses.activate + def test_get_latest_library_items(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/7193296778/latest", + json={}, + status=200, + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_latest_library_items(3, 7193296778, "optio") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_latest_library_items_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/6458970563/latest", + json={}, + status=202, + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_latest_library_items() + responses.reset(), + + @responses.activate + def test_get_latest_library_items_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/1032189612/latest", + json={}, + status=404, + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_latest_library_items(9, 1032189612, "enim") + responses.reset() + + @responses.activate + def test_get_common_library_items(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/8358490584/common", + json={}, + status=200, + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_common_library_items(7, 8358490584, "sed") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_common_library_items_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/6763505882/common", + json={}, + status=202, + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_common_library_items() + responses.reset(), + + @responses.activate + def test_get_common_library_items_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/sections/8191586203/common", + json={}, + status=404, + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_common_library_items(2, 8191586203, "nobis") + responses.reset() + + @responses.activate + def test_get_metadata(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/metadata/2", json={}, status=200 + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_metadata(2) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_metadata_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/metadata/3", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_metadata() + responses.reset(), + + @responses.activate + def test_get_metadata_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/metadata/5", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_metadata(5) + responses.reset() + + @responses.activate + def test_get_metadata_children(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/metadata/4/children", json={}, status=200 + ) + # call the method to test + test_service = Library("testkey") + response = test_service.get_metadata_children(4) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_metadata_children_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/metadata/2/children", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Library("testkey") + test_service.get_metadata_children() + responses.reset(), + + @responses.activate + def test_get_metadata_children_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/library/metadata/9/children", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_metadata_children(9) + responses.reset() + + @responses.activate + def test_get_on_deck(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/onDeck", json={}, status=200) + # call the method to test + test_service = Library("testkey") + response = test_service.get_on_deck() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_on_deck_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/library/onDeck", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Library("testkey") + test_service.get_on_deck() + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_log.py b/test/services/test_log.py new file mode 100644 index 0000000..50e3733 --- /dev/null +++ b/test/services/test_log.py @@ -0,0 +1,80 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.log import Log + + +class TestLog_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_log_line(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/log", json={}, status=200) + # call the method to test + test_service = Log("testkey") + response = test_service.log_line("adipisci", "reprehenderit", 1) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_log_line_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/log", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Log("testkey") + test_service.log_line() + responses.reset(), + + @responses.activate + def test_log_line_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/log", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Log("testkey") + test_service.log_line("eaque", "ea", 4) + responses.reset() + + @responses.activate + def test_log_multi_line(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/log", json={}, status=200) + # call the method to test + test_service = Log("testkey") + response = test_service.log_multi_line() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_log_multi_line_error_on_non_200(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/log", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Log("testkey") + test_service.log_multi_line() + responses.reset() + + @responses.activate + def test_enable_paper_trail(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/log/networked", json={}, status=200) + # call the method to test + test_service = Log("testkey") + response = test_service.enable_paper_trail() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_enable_paper_trail_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/log/networked", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Log("testkey") + test_service.enable_paper_trail() + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_media.py b/test/services/test_media.py new file mode 100644 index 0000000..cd0b8b4 --- /dev/null +++ b/test/services/test_media.py @@ -0,0 +1,98 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.media import Media + + +class TestMedia_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_mark_played(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/scrobble", json={}, status=200) + # call the method to test + test_service = Media("testkey") + response = test_service.mark_played(1) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_mark_played_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/scrobble", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Media("testkey") + test_service.mark_played() + responses.reset(), + + @responses.activate + def test_mark_played_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/scrobble", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Media("testkey") + test_service.mark_played(9) + responses.reset() + + @responses.activate + def test_mark_unplayed(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/unscrobble", json={}, status=200) + # call the method to test + test_service = Media("testkey") + response = test_service.mark_unplayed(9) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_mark_unplayed_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/unscrobble", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Media("testkey") + test_service.mark_unplayed() + responses.reset(), + + @responses.activate + def test_mark_unplayed_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/unscrobble", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Media("testkey") + test_service.mark_unplayed(7) + responses.reset() + + @responses.activate + def test_update_play_progress(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/:/progress", json={}, status=200) + # call the method to test + test_service = Media("testkey") + response = test_service.update_play_progress("esse", 7, "nesciunt") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_update_play_progress_required_fields_missing(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/:/progress", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Media("testkey") + test_service.update_play_progress() + responses.reset(), + + @responses.activate + def test_update_play_progress_error_on_non_200(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/:/progress", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Media("testkey") + test_service.update_play_progress("quo", 7, "dolorum") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_playlists.py b/test/services/test_playlists.py new file mode 100644 index 0000000..346718e --- /dev/null +++ b/test/services/test_playlists.py @@ -0,0 +1,297 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.playlists import Playlists + + +class TestPlaylists_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_create_playlist(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/playlists", json={}, status=200) + # call the method to test + test_service = Playlists("testkey") + response = test_service.create_playlist( + 8, "audio", "quasi", "alias", 5922730203 + ) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_create_playlist_required_fields_missing(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/playlists", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.create_playlist() + responses.reset(), + + @responses.activate + def test_create_playlist_error_on_non_200(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/playlists", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.create_playlist(4, "audio", "nihil", "cumque", 5164878131) + responses.reset() + + @responses.activate + def test_get_playlists(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/playlists/all", json={}, status=200) + # call the method to test + test_service = Playlists("testkey") + response = test_service.get_playlists("audio", 5) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_playlists_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/playlists/all", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.get_playlists("audio", 4) + responses.reset() + + @responses.activate + def test_get_playlist(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/playlists/8961898762", json={}, status=200 + ) + # call the method to test + test_service = Playlists("testkey") + response = test_service.get_playlist(8961898762) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_playlist_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/playlists/2350925795", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.get_playlist() + responses.reset(), + + @responses.activate + def test_get_playlist_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/playlists/7803831874", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.get_playlist(7803831874) + responses.reset() + + @responses.activate + def test_update_playlist(self): + # Mock the API response + responses.put( + "{protocol}://{ip}:{port}/playlists/4846174885", json={}, status=200 + ) + # call the method to test + test_service = Playlists("testkey") + response = test_service.update_playlist(4846174885) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_update_playlist_required_fields_missing(self): + # Mock the API response + responses.put( + "{protocol}://{ip}:{port}/playlists/3217640966", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.update_playlist() + responses.reset(), + + @responses.activate + def test_update_playlist_error_on_non_200(self): + # Mock the API response + responses.put( + "{protocol}://{ip}:{port}/playlists/2969411689", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.update_playlist(2969411689) + responses.reset() + + @responses.activate + def test_delete_playlist(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/playlists/9157110662", json={}, status=200 + ) + # call the method to test + test_service = Playlists("testkey") + response = test_service.delete_playlist(9157110662) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_delete_playlist_required_fields_missing(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/playlists/9590435699", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.delete_playlist() + responses.reset(), + + @responses.activate + def test_delete_playlist_error_on_non_200(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/playlists/7937977423", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.delete_playlist(7937977423) + responses.reset() + + @responses.activate + def test_get_playlist_contents(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/playlists/5686140716/items", json={}, status=200 + ) + # call the method to test + test_service = Playlists("testkey") + response = test_service.get_playlist_contents(8, 5686140716) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_playlist_contents_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/playlists/3666405994/items", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.get_playlist_contents() + responses.reset(), + + @responses.activate + def test_get_playlist_contents_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/playlists/9239392917/items", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.get_playlist_contents(4, 9239392917) + responses.reset() + + @responses.activate + def test_add_playlist_contents(self): + # Mock the API response + responses.put( + "{protocol}://{ip}:{port}/playlists/2224463633/items", json={}, status=200 + ) + # call the method to test + test_service = Playlists("testkey") + response = test_service.add_playlist_contents( + 7330486290, "provident", 2224463633 + ) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_add_playlist_contents_required_fields_missing(self): + # Mock the API response + responses.put( + "{protocol}://{ip}:{port}/playlists/8938618789/items", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.add_playlist_contents() + responses.reset(), + + @responses.activate + def test_add_playlist_contents_error_on_non_200(self): + # Mock the API response + responses.put( + "{protocol}://{ip}:{port}/playlists/7136237365/items", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.add_playlist_contents(3430294919, "aspernatur", 7136237365) + responses.reset() + + @responses.activate + def test_clear_playlist_contents(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/playlists/6699998436/items", json={}, status=200 + ) + # call the method to test + test_service = Playlists("testkey") + response = test_service.clear_playlist_contents(6699998436) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_clear_playlist_contents_required_fields_missing(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/playlists/1772875063/items", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.clear_playlist_contents() + responses.reset(), + + @responses.activate + def test_clear_playlist_contents_error_on_non_200(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/playlists/3406600816/items", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.clear_playlist_contents(3406600816) + responses.reset() + + @responses.activate + def test_upload_playlist(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/playlists/upload", json={}, status=200) + # call the method to test + test_service = Playlists("testkey") + response = test_service.upload_playlist(2, "dignissimos") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_upload_playlist_required_fields_missing(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/playlists/upload", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Playlists("testkey") + test_service.upload_playlist() + responses.reset(), + + @responses.activate + def test_upload_playlist_error_on_non_200(self): + # Mock the API response + responses.post("{protocol}://{ip}:{port}/playlists/upload", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Playlists("testkey") + test_service.upload_playlist(5, "fugit") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_search.py b/test/services/test_search.py new file mode 100644 index 0000000..af8038d --- /dev/null +++ b/test/services/test_search.py @@ -0,0 +1,98 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.search import Search + + +class TestSearch_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_perform_search(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs/search", json={}, status=200) + # call the method to test + test_service = Search("testkey") + response = test_service.perform_search("incidunt", 2233364198, 9) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_perform_search_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs/search", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Search("testkey") + test_service.perform_search() + responses.reset(), + + @responses.activate + def test_perform_search_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs/search", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Search("testkey") + test_service.perform_search("molestias", 8612735357, 3) + responses.reset() + + @responses.activate + def test_perform_voice_search(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs/search/voice", json={}, status=200) + # call the method to test + test_service = Search("testkey") + response = test_service.perform_voice_search("numquam", 7139321773, 2) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_perform_voice_search_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs/search/voice", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Search("testkey") + test_service.perform_voice_search() + responses.reset(), + + @responses.activate + def test_perform_voice_search_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/hubs/search/voice", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Search("testkey") + test_service.perform_voice_search("maxime", 5682941219, 6) + responses.reset() + + @responses.activate + def test_get_search_results(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/search", json={}, status=200) + # call the method to test + test_service = Search("testkey") + response = test_service.get_search_results("veritatis") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_search_results_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/search", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Search("testkey") + test_service.get_search_results() + responses.reset(), + + @responses.activate + def test_get_search_results_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/search", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Search("testkey") + test_service.get_search_results("eaque") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_security.py b/test/services/test_security.py new file mode 100644 index 0000000..7988389 --- /dev/null +++ b/test/services/test_security.py @@ -0,0 +1,76 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.security import Security + + +class TestSecurity_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_transient_token(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/security/token", json={}, status=200) + # call the method to test + test_service = Security("testkey") + response = test_service.get_transient_token("all", "delegation") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_transient_token_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/security/token", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Security("testkey") + test_service.get_transient_token() + responses.reset(), + + @responses.activate + def test_get_transient_token_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/security/token", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Security("testkey") + test_service.get_transient_token("all", "delegation") + responses.reset() + + @responses.activate + def test_get_source_connection_information(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/security/resources", json={}, status=200 + ) + # call the method to test + test_service = Security("testkey") + response = test_service.get_source_connection_information("provident") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_source_connection_information_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/security/resources", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Security("testkey") + test_service.get_source_connection_information() + responses.reset(), + + @responses.activate + def test_get_source_connection_information_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/security/resources", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Security("testkey") + test_service.get_source_connection_information("molestiae") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_server.py b/test/services/test_server.py new file mode 100644 index 0000000..0f468c2 --- /dev/null +++ b/test/services/test_server.py @@ -0,0 +1,175 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.server import Server + + +class TestServer_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_server_capabilities(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_server_capabilities() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_server_capabilities_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_server_capabilities() + responses.reset() + + @responses.activate + def test_get_server_preferences(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/prefs", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_server_preferences() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_server_preferences_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/prefs", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_server_preferences() + responses.reset() + + @responses.activate + def test_get_available_clients(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/clients", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_available_clients() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_available_clients_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/clients", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_available_clients() + responses.reset() + + @responses.activate + def test_get_devices(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/devices", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_devices() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_devices_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/devices", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_devices() + responses.reset() + + @responses.activate + def test_get_server_identity(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/identity", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_server_identity() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_server_identity_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/identity", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_server_identity() + responses.reset() + + @responses.activate + def test_get_my_plex_account(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/myplex/account", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_my_plex_account() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_my_plex_account_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/myplex/account", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_my_plex_account() + responses.reset() + + @responses.activate + def test_get_resized_photo(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/photo/:/transcode", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_resized_photo("saepe", 6, 6, 3, 4, 6, 6805299528) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_resized_photo_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/photo/:/transcode", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Server("testkey") + test_service.get_resized_photo() + responses.reset(), + + @responses.activate + def test_get_resized_photo_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/photo/:/transcode", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_resized_photo("asperiores", 3, 2, 3, 5, 4, 8443664162) + responses.reset() + + @responses.activate + def test_get_server_list(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/servers", json={}, status=200) + # call the method to test + test_service = Server("testkey") + response = test_service.get_server_list() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_server_list_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/servers", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Server("testkey") + test_service.get_server_list() + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_sessions.py b/test/services/test_sessions.py new file mode 100644 index 0000000..02bedcd --- /dev/null +++ b/test/services/test_sessions.py @@ -0,0 +1,115 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.sessions import Sessions + + +class TestSessions_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_sessions(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/status/sessions", json={}, status=200) + # call the method to test + test_service = Sessions("testkey") + response = test_service.get_sessions() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_sessions_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/status/sessions", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Sessions("testkey") + test_service.get_sessions() + responses.reset() + + @responses.activate + def test_get_session_history(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/status/sessions/history/all", json={}, status=200 + ) + # call the method to test + test_service = Sessions("testkey") + response = test_service.get_session_history() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_session_history_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/status/sessions/history/all", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Sessions("testkey") + test_service.get_session_history() + responses.reset() + + @responses.activate + def test_get_transcode_sessions(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/transcode/sessions", json={}, status=200 + ) + # call the method to test + test_service = Sessions("testkey") + response = test_service.get_transcode_sessions() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_transcode_sessions_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/transcode/sessions", json={}, status=404 + ) + with self.assertRaises(ClientException): + test_service = Sessions("testkey") + test_service.get_transcode_sessions() + responses.reset() + + @responses.activate + def test_stop_transcode_session(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/transcode/sessions/natus", json={}, status=200 + ) + # call the method to test + test_service = Sessions("testkey") + response = test_service.stop_transcode_session("natus") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_stop_transcode_session_required_fields_missing(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/transcode/sessions/iure", json={}, status=202 + ) + with self.assertRaises(TypeError): + test_service = Sessions("testkey") + test_service.stop_transcode_session() + responses.reset(), + + @responses.activate + def test_stop_transcode_session_error_on_non_200(self): + # Mock the API response + responses.delete( + "{protocol}://{ip}:{port}/transcode/sessions/praesentium", + json={}, + status=404, + ) + with self.assertRaises(ClientException): + test_service = Sessions("testkey") + test_service.stop_transcode_session("praesentium") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_updater.py b/test/services/test_updater.py new file mode 100644 index 0000000..745cff9 --- /dev/null +++ b/test/services/test_updater.py @@ -0,0 +1,71 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.updater import Updater + + +class TestUpdater_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_get_update_status(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/updater/status", json={}, status=200) + # call the method to test + test_service = Updater("testkey") + response = test_service.get_update_status() + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_update_status_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/updater/status", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Updater("testkey") + test_service.get_update_status() + responses.reset() + + @responses.activate + def test_check_for_updates(self): + # Mock the API response + responses.put("{protocol}://{ip}:{port}/updater/check", json={}, status=200) + # call the method to test + test_service = Updater("testkey") + response = test_service.check_for_updates("foo") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_check_for_updates_error_on_non_200(self): + # Mock the API response + responses.put("{protocol}://{ip}:{port}/updater/check", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Updater("testkey") + test_service.check_for_updates("foo") + responses.reset() + + @responses.activate + def test_apply_updates(self): + # Mock the API response + responses.put("{protocol}://{ip}:{port}/updater/apply", json={}, status=200) + # call the method to test + test_service = Updater("testkey") + response = test_service.apply_updates("foo", "foo") + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_apply_updates_error_on_non_200(self): + # Mock the API response + responses.put("{protocol}://{ip}:{port}/updater/apply", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Updater("testkey") + test_service.apply_updates("foo", "foo") + responses.reset() + + +if __name__ == "__main__": + unittest.main() diff --git a/test/services/test_video.py b/test/services/test_video.py new file mode 100644 index 0000000..ea245a3 --- /dev/null +++ b/test/services/test_video.py @@ -0,0 +1,129 @@ +import unittest +import responses +from src.plexsdk.net.http_client import HTTPClient +from http_exceptions import ClientException +from src.plexsdk.services.video import Video + + +class TestVideo_(unittest.TestCase): + def test_true(self): + self.assertTrue(True) + + @responses.activate + def test_start_universal_transcode(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/video/:/transcode/universal/start.mpd", + json={}, + status=200, + ) + # call the method to test + test_service = Video("testkey") + response = test_service.start_universal_transcode( + "debitis", + 5, + 6, + "quos", + 1, + 2, + 2, + 5, + 7, + "maiores", + 4, + "fugit", + 9, + "veritatis", + 5, + 3, + ) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_start_universal_transcode_required_fields_missing(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/video/:/transcode/universal/start.mpd", + json={}, + status=202, + ) + with self.assertRaises(TypeError): + test_service = Video("testkey") + test_service.start_universal_transcode() + responses.reset(), + + @responses.activate + def test_start_universal_transcode_error_on_non_200(self): + # Mock the API response + responses.get( + "{protocol}://{ip}:{port}/video/:/transcode/universal/start.mpd", + json={}, + status=404, + ) + with self.assertRaises(ClientException): + test_service = Video("testkey") + test_service.start_universal_transcode( + "quos", + 2, + 9, + "cupiditate", + 4, + 5, + 7, + 6, + 5, + "eligendi", + 9, + "eum", + 9, + "veritatis", + 8, + 2, + ) + responses.reset() + + @responses.activate + def test_get_timeline(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/timeline", json={}, status=200) + # call the method to test + test_service = Video("testkey") + response = test_service.get_timeline( + 1, 3, 4519717282, "neque optio ipsa aut est", 2, 6, 3, "playing", "aut", 6 + ) + self.assertEqual(response.data, {}) + responses.reset(), + + @responses.activate + def test_get_timeline_required_fields_missing(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/timeline", json={}, status=202) + with self.assertRaises(TypeError): + test_service = Video("testkey") + test_service.get_timeline() + responses.reset(), + + @responses.activate + def test_get_timeline_error_on_non_200(self): + # Mock the API response + responses.get("{protocol}://{ip}:{port}/:/timeline", json={}, status=404) + with self.assertRaises(ClientException): + test_service = Video("testkey") + test_service.get_timeline( + 9, + 1, + 6697392847, + "repellendus odio inventore dolore excepturi", + 6, + 4, + 4, + "playing", + "recusandae", + 4, + ) + responses.reset() + + +if __name__ == "__main__": + unittest.main()