mirror of
https://github.com/phin05/discord-rich-presence-plex
synced 2025-02-17 14:18:25 +00:00
Added support for YAML config file
This commit is contained in:
parent
a6fdb596db
commit
5e4a56ada4
7 changed files with 128 additions and 21 deletions
26
NOTICE
26
NOTICE
|
@ -437,3 +437,29 @@ limitations under the License.
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
PyYAML
|
||||||
|
https://github.com/yaml/pyyaml
|
||||||
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
|
Copyright (c) 2017-2021 Ingy döt Net
|
||||||
|
Copyright (c) 2006-2016 Kirill Simonov
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
72
README.md
72
README.md
|
@ -11,18 +11,26 @@ Discord Rich Presence for Plex is a Python script which displays your [Plex](htt
|
||||||
|
|
||||||
If you're using a Linux-based operating system, you can [run this script with Docker](#run-with-docker). Otherwise, follow these instructions:
|
If you're using a Linux-based operating system, you can [run this script with Docker](#run-with-docker). Otherwise, follow these instructions:
|
||||||
|
|
||||||
1. Install [Python 3.10](https://www.python.org/downloads/) - Make sure to tick "Add Python 3.10 to PATH" during the installation.
|
1. Install [Python](https://www.python.org/downloads/) (version 3.10 or newer) - Make sure to tick "Add Python to PATH" during the installation.
|
||||||
2. Download the [latest release](https://github.com/phin05/discord-rich-presence-plex/releases/latest) of this script.
|
2. Download the [latest release](https://github.com/phin05/discord-rich-presence-plex/releases/latest) of this script.
|
||||||
3. Extract the directory contained in the above ZIP file.
|
3. Extract the directory contained in the above ZIP file.
|
||||||
4. Navigate a command-line interface (cmd.exe, PowerShell, bash, etc.) into the above-extracted directory.
|
4. Navigate a command-line interface (cmd, PowerShell, bash, etc.) into the above-extracted directory.
|
||||||
5. Install the required Python modules by running `python -m pip install -U -r requirements.txt`.
|
5. Start the script by running `python main.py`.
|
||||||
6. Start the script by running `python main.py`.
|
|
||||||
|
|
||||||
When the script runs for the first time, a `data` directory will be created in the current working directory along with a `config.json` file inside of it. You will be prompted to complete the authentication flow to allow the script to retrieve an access token for your Plex account.
|
When the script runs for the first time, a directory named `data` will be created in the current working directory along with a `config.yaml` file inside of it. You will be prompted to complete authentication to allow the script to retrieve an access token for your Plex account.
|
||||||
|
|
||||||
The script must be running on the same machine as your Discord client.
|
The script must be running on the same machine as your Discord client.
|
||||||
|
|
||||||
## Configuration - `config.json`
|
## Configuration
|
||||||
|
|
||||||
|
A directory named `data` is used for storing the configuration file.
|
||||||
|
|
||||||
|
### Supported Formats
|
||||||
|
|
||||||
|
* YAML - `config.yaml` / `config.yml`
|
||||||
|
* JSON - `config.json`
|
||||||
|
|
||||||
|
### Reference
|
||||||
|
|
||||||
* `logging`
|
* `logging`
|
||||||
* `debug` (boolean, default: `true`) - Outputs additional debug-helpful information to the console if enabled.
|
* `debug` (boolean, default: `true`) - Outputs additional debug-helpful information to the console if enabled.
|
||||||
|
@ -64,6 +72,44 @@ During runtime, the following dynamic URL placeholders will get replaced with re
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>YAML</summary>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
logging:
|
||||||
|
debug: true
|
||||||
|
writeToFile: false
|
||||||
|
display:
|
||||||
|
hideTotalTime: false
|
||||||
|
useRemainingTime: false
|
||||||
|
posters:
|
||||||
|
enabled: true
|
||||||
|
imgurClientID: 9e9sf637S8bRp4z
|
||||||
|
buttons:
|
||||||
|
- label: IMDb Link
|
||||||
|
url: dynamic:imdb
|
||||||
|
- label: My YouTube Channel
|
||||||
|
url: https://www.youtube.com/channel/me
|
||||||
|
users:
|
||||||
|
- token: HPbrz2NhfLRjU888Rrdt
|
||||||
|
servers:
|
||||||
|
- name: Bob's Home Media Server
|
||||||
|
- name: A Friend's Server
|
||||||
|
whitelistedLibraries:
|
||||||
|
- Movies
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
|
||||||
|
<summary>JSON</summary>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"logging": {
|
"logging": {
|
||||||
|
@ -98,7 +144,9 @@ During runtime, the following dynamic URL placeholders will get replaced with re
|
||||||
{
|
{
|
||||||
"name": "A Friend's Server",
|
"name": "A Friend's Server",
|
||||||
"listenForUser": "xyz",
|
"listenForUser": "xyz",
|
||||||
"whitelistedLibraries": ["Movies"]
|
"whitelistedLibraries": [
|
||||||
|
"Movies"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -106,9 +154,7 @@ During runtime, the following dynamic URL placeholders will get replaced with re
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration - Environment Variables
|
</details>
|
||||||
|
|
||||||
* `PLEX_SERVER_NAME` - Name of the Plex Media Server you wish to connect to. Used only during the initial setup (when there are no users in the config) for adding a server to the config after authentication. If this isn't set, in interactive environments, the user is prompted for an input, and in non-interactive environments, "ServerName" is used as a placeholder, which can later be changed by editing the config file and restarting the script.
|
|
||||||
|
|
||||||
## Configuration - Discord
|
## Configuration - Discord
|
||||||
|
|
||||||
|
@ -116,6 +162,10 @@ The "Display current activity as a status message" setting must be enabled in Di
|
||||||
|
|
||||||
![Discord Settings](https://user-images.githubusercontent.com/59180111/186830889-35af3895-ece0-4a7d-9efb-f68640116884.png)
|
![Discord Settings](https://user-images.githubusercontent.com/59180111/186830889-35af3895-ece0-4a7d-9efb-f68640116884.png)
|
||||||
|
|
||||||
|
## Configuration - Environment Variables
|
||||||
|
|
||||||
|
* `PLEX_SERVER_NAME` - Name of the Plex Media Server you wish to connect to. Used only during the initial setup (when there are no users in the config) for adding a server to the config after authentication. If this isn't set, in interactive environments, the user is prompted for an input, and in non-interactive environments, "ServerName" is used as a placeholder, which can later be changed by editing the configuration file and restarting the script.
|
||||||
|
|
||||||
## Run with Docker
|
## Run with Docker
|
||||||
|
|
||||||
### Image
|
### Image
|
||||||
|
@ -124,7 +174,7 @@ The "Display current activity as a status message" setting must be enabled in Di
|
||||||
|
|
||||||
### Volumes
|
### Volumes
|
||||||
|
|
||||||
Mount a directory for persistent data (config file, cache file and log file) at `/app/data`.
|
Mount a directory for persistent data (configuration file, cache file and log file) at `/app/data`.
|
||||||
|
|
||||||
The directory where Discord stores its inter-process communication Unix socket file needs to be mounted into the container at `/run/app`. The path for this would be the first non-null value from the values of the following environment variables: ([source](https://github.com/discord/discord-rpc/blob/963aa9f3e5ce81a4682c6ca3d136cddda614db33/src/connection_unix.cpp#L29C33-L29C33))
|
The directory where Discord stores its inter-process communication Unix socket file needs to be mounted into the container at `/run/app`. The path for this would be the first non-null value from the values of the following environment variables: ([source](https://github.com/discord/discord-rpc/blob/963aa9f3e5ce81a4682c6ca3d136cddda614db33/src/connection_unix.cpp#L29C33-L29C33))
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,13 @@ import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
name = "Discord Rich Presence for Plex"
|
name = "Discord Rich Presence for Plex"
|
||||||
version = "2.3.5"
|
version = "2.4.0"
|
||||||
|
|
||||||
plexClientID = "discord-rich-presence-plex"
|
plexClientID = "discord-rich-presence-plex"
|
||||||
discordClientID = "413407336082833418"
|
discordClientID = "413407336082833418"
|
||||||
|
|
||||||
dataDirectoryPath = "data"
|
dataDirectoryPath = "data"
|
||||||
configFilePath = os.path.join(dataDirectoryPath, "config.json")
|
configFilePathRoot = os.path.join(dataDirectoryPath, "config")
|
||||||
cacheFilePath = os.path.join(dataDirectoryPath, "cache.json")
|
cacheFilePath = os.path.join(dataDirectoryPath, "cache.json")
|
||||||
logFilePath = os.path.join(dataDirectoryPath, "console.log")
|
logFilePath = os.path.join(dataDirectoryPath, "console.log")
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
from config.constants import configFilePath
|
from config.constants import configFilePathRoot
|
||||||
from utils.dict import copyDict
|
from utils.dict import copyDict
|
||||||
from utils.logging import logger
|
from utils.logging import logger
|
||||||
import json
|
import json
|
||||||
import models.config
|
import models.config
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
import yaml
|
||||||
|
|
||||||
config: models.config.Config = {
|
config: models.config.Config = {
|
||||||
"logging": {
|
"logging": {
|
||||||
|
@ -22,23 +23,52 @@ config: models.config.Config = {
|
||||||
},
|
},
|
||||||
"users": [],
|
"users": [],
|
||||||
}
|
}
|
||||||
|
supportedConfigFileExtensions = {
|
||||||
|
"yaml": "yaml",
|
||||||
|
"yml": "yaml",
|
||||||
|
"json": "json",
|
||||||
|
}
|
||||||
|
configFileExtension = ""
|
||||||
|
configFileType = ""
|
||||||
|
configFilePath = ""
|
||||||
|
|
||||||
def loadConfig() -> None:
|
def loadConfig() -> None:
|
||||||
|
global configFileType, configFileExtension, configFilePath
|
||||||
|
for fileExtension, fileType in supportedConfigFileExtensions.items():
|
||||||
|
filePath = f"{configFilePathRoot}.{fileExtension}"
|
||||||
|
if os.path.isfile(filePath):
|
||||||
|
configFileExtension = fileExtension
|
||||||
|
configFileType = fileType
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
configFileExtension, configFileType = list(supportedConfigFileExtensions.items())[0]
|
||||||
|
filePath = f"{configFilePathRoot}.{configFileExtension}"
|
||||||
|
configFilePath = filePath
|
||||||
if os.path.isfile(configFilePath):
|
if os.path.isfile(configFilePath):
|
||||||
try:
|
try:
|
||||||
with open(configFilePath, "r", encoding = "UTF-8") as configFile:
|
with open(configFilePath, "r", encoding = "UTF-8") as configFile:
|
||||||
loadedConfig = json.load(configFile)
|
if configFileType == "yaml":
|
||||||
|
loadedConfig = yaml.safe_load(configFile) or {}
|
||||||
|
else:
|
||||||
|
loadedConfig = json.load(configFile) or {}
|
||||||
except:
|
except:
|
||||||
os.rename(configFilePath, configFilePath.replace(".json", f"-{time.time():.0f}.json"))
|
os.rename(configFilePath, f"{configFilePathRoot}-{time.time():.0f}.{configFileExtension}")
|
||||||
logger.exception("Failed to parse the application's config file. A new one will be created.")
|
logger.exception("Failed to parse the application's config file. A new one will be created.")
|
||||||
else:
|
else:
|
||||||
copyDict(loadedConfig, config)
|
copyDict(loadedConfig, config)
|
||||||
saveConfig()
|
saveConfig()
|
||||||
|
|
||||||
|
class YamlSafeDumper(yaml.SafeDumper):
|
||||||
|
def increase_indent(self, flow: bool = False, indentless: bool = False) -> None:
|
||||||
|
return super().increase_indent(flow, False)
|
||||||
|
|
||||||
def saveConfig() -> None:
|
def saveConfig() -> None:
|
||||||
try:
|
try:
|
||||||
with open(configFilePath, "w", encoding = "UTF-8") as configFile:
|
with open(configFilePath, "w", encoding = "UTF-8") as configFile:
|
||||||
json.dump(config, configFile, indent = "\t")
|
if configFileType == "yaml":
|
||||||
configFile.write("\n")
|
yaml.dump(config, configFile, sort_keys = False, Dumper = YamlSafeDumper, allow_unicode = True)
|
||||||
|
else:
|
||||||
|
json.dump(config, configFile, indent = "\t")
|
||||||
|
configFile.write("\n")
|
||||||
except:
|
except:
|
||||||
logger.exception("Failed to write to the application's config file.")
|
logger.exception("Failed to write to the application's config file.")
|
||||||
|
|
|
@ -59,7 +59,7 @@ class PlexAlertListener(threading.Thread):
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
self.logger.info("Connected to %s \"%s\"", self.productName, resource.name)
|
self.logger.info("Connected to %s \"%s\"", self.productName, resource.name)
|
||||||
self.alertListener = AlertListener(self.server, self.handlePlexAlert, self.reconnect)
|
self.alertListener = AlertListener(self.server, self.handleAlert, self.reconnect)
|
||||||
self.alertListener.start()
|
self.alertListener.start()
|
||||||
self.logger.info("Listening for alerts from user \"%s\"", self.listenForUser)
|
self.logger.info("Listening for alerts from user \"%s\"", self.listenForUser)
|
||||||
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
|
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
|
||||||
|
@ -116,7 +116,7 @@ class PlexAlertListener(threading.Thread):
|
||||||
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
|
self.connectionTimeoutTimer = threading.Timer(self.connectionTimeoutTimerInterval, self.connectionTimeout)
|
||||||
self.connectionTimeoutTimer.start()
|
self.connectionTimeoutTimer.start()
|
||||||
|
|
||||||
def handlePlexAlert(self, alert: models.plex.Alert) -> None:
|
def handleAlert(self, alert: models.plex.Alert) -> None:
|
||||||
try:
|
try:
|
||||||
if alert["type"] == "playing" and "PlaySessionStateNotification" in alert:
|
if alert["type"] == "playing" and "PlaySessionStateNotification" in alert:
|
||||||
stateNotification = alert["PlaySessionStateNotification"][0]
|
stateNotification = alert["PlaySessionStateNotification"][0]
|
||||||
|
|
2
main.py
2
main.py
|
@ -49,7 +49,7 @@ def main() -> None:
|
||||||
logger.info("%s - v%s", name, version)
|
logger.info("%s - v%s", name, version)
|
||||||
loadCache()
|
loadCache()
|
||||||
if not config["users"]:
|
if not config["users"]:
|
||||||
logger.info("No users found in the config file. Initiating authentication flow.")
|
logger.info("No users found in the config file")
|
||||||
user = authUser()
|
user = authUser()
|
||||||
if not user:
|
if not user:
|
||||||
exit()
|
exit()
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
PlexAPI==4.10.1
|
PlexAPI==4.10.1
|
||||||
requests==2.26.0
|
requests==2.26.0
|
||||||
websocket-client==1.3.2
|
websocket-client==1.3.2
|
||||||
|
PyYAML==6.0.1
|
||||||
|
|
Loading…
Add table
Reference in a new issue