diff --git a/README.md b/README.md index 962b06b..78c14b3 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A Python script that displays your [Plex](https://www.plex.tv) status on [Discord](https://discord.com) using [Rich Presence](https://discord.com/developers/docs/rich-presence/how-to). -Current Version: 2.2.5 +Current Version: 2.3.0 ## Getting Started @@ -30,9 +30,12 @@ The script must be running on the same machine as your Discord client. * `useRemainingTime` (boolean, default: `false`) - Displays your media's remaining time instead of elapsed time in your Rich Presence if enabled. * `posters` * `enabled` (boolean, default: `false`) - Displays media posters in Rich Presence if enabled. Requires `imgurClientID`. - * `imgurClientID` (string, default: `""`) - [Instructions](#obtaining-an-imgur-client-id) + * `imgurClientID` (string, default: `""`) - [Obtention Instructions](#obtaining-an-imgur-client-id) + * `buttons` (list) - [Information](#buttons) + * `label` (string) - The label to be displayed on the button. + * `url` (string) - A web address or a [dynamic URL placeholder](#dynamic-button-urls). * `users` (list) - * `token` (string) - An access token associated with your Plex account. ([X-Plex-Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token), [Authenticating with Plex](https://forums.plex.tv/t/authenticating-with-plex/609370)) + * `token` (string) - An access token associated with your Plex account. ([X-Plex-Token](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/), [Authenticating with Plex](https://forums.plex.tv/t/authenticating-with-plex/609370)) * `servers` (list) * `name` (string) - Name of the Plex Media Server you wish to connect to. * `listenForUser` (string, optional) - The script will respond to alerts originating only from this username. Defaults to the parent user's username if not set. @@ -45,6 +48,18 @@ The script must be running on the same machine as your Discord client. 2. Enter any name for the application and pick OAuth2 without a callback URL as the authorisation type 3. Submit the form to obtain your application's client ID +### Buttons + +A maximum of 2 buttons can be displayed in your Rich Presence. + +Due to a strange Discord bug, buttons displayed in your Rich Presence are unresponsive to your own clicks, but other users are able to click on them to open their corresponding URLs. + +#### Dynamic Button URLs + +During runtime, the following dynamic URL placeholders will get replaced with real URLs based on the media being played: +* `dynamic:imdb` +* `dynamic:tmdb` + ### Example ```json @@ -58,7 +73,17 @@ The script must be running on the same machine as your Discord client. "posters": { "enabled": true, "imgurClientID": "9e9sf637S8bRp4z" - } + }, + "buttons": [ + { + "label": "IMDb Link", + "url": "dynamic:imdb" + }, + { + "label": "My YouTube Channel", + "url": "https://www.youtube.com/" + } + ] }, "users": [ { diff --git a/models/config.py b/models/config.py index 78e9953..cfa9464 100644 --- a/models/config.py +++ b/models/config.py @@ -8,9 +8,14 @@ class Posters(TypedDict): enabled: bool imgurClientID: str +class Button(TypedDict): + label: str + url: str + class Display(TypedDict): useRemainingTime: bool posters: Posters + buttons: list[Button] class Server(TypedDict, total = False): name: str diff --git a/models/discord.py b/models/discord.py index fad2e84..e146e3d 100644 --- a/models/discord.py +++ b/models/discord.py @@ -10,8 +10,13 @@ class ActivityTimestamps(TypedDict, total = False): start: int end: int +class ActivityButton(TypedDict): + label: str + url: str + class Activity(TypedDict, total = False): details: str state: str assets: ActivityAssets timestamps: ActivityTimestamps + buttons: list[ActivityButton] diff --git a/services/PlexAlertListener.py b/services/PlexAlertListener.py index 2a33826..3be542f 100644 --- a/services/PlexAlertListener.py +++ b/services/PlexAlertListener.py @@ -6,6 +6,7 @@ from .config import config from .imgur import uploadImage from plexapi.alert import AlertListener from plexapi.base import Playable, PlexPartialObject +from plexapi.media import Genre, GuidTag from plexapi.myplex import MyPlexAccount, PlexServer from typing import Optional from utils.logging import LoggerWithPrefix @@ -183,7 +184,8 @@ class PlexAlertListener(threading.Thread): stateStrings: list[str] = [formatSeconds(item.duration / 1000)] if mediaType == "movie": title = f"{item.title} ({item.year})" - stateStrings.append(f"{', '.join(genre.tag for genre in item.genres[:3])}") + genres: list[Genre] = item.genres[:3] + stateStrings.append(f"{', '.join(genre.tag for genre in genres)}") largeText = "Watching a movie" thumb = item.thumb else: @@ -220,6 +222,35 @@ class PlexAlertListener(threading.Thread): "small_image": state, }, } + if config["display"]["buttons"]: + guidTags: list[GuidTag] = [] + if mediaType == "movie": + guidTags = item.guids + elif mediaType == "episode": + guidTags = self.server.fetchItem(item.grandparentRatingKey).guids + guids: list[str] = [guid.id for guid in guidTags] + buttons: list[models.discord.ActivityButton] = [] + for button in config["display"]["buttons"]: + if button["url"].startswith("dynamic:"): + if guids: + newUrl = button["url"] + if button["url"] == "dynamic:imdb": + for guid in guids: + if guid.startswith("imdb://"): + newUrl = guid.replace("imdb://", "https://www.imdb.com/title/") + break + elif button["url"] == "dynamic:tmdb": + for guid in guids: + if guid.startswith("tmdb://"): + tmdbPathSegment = "movie" if mediaType == "movie" else "tv" + newUrl = guid.replace("tmdb://", f"https://www.themoviedb.org/{tmdbPathSegment}/") + break + if newUrl: + buttons.append({ "label": button["label"], "url": newUrl }) + else: + buttons.append(button) + if buttons: + activity["buttons"] = buttons[:2] if state == "playing": currentTimestamp = int(time.time()) if config["display"]["useRemainingTime"]: diff --git a/services/config.py b/services/config.py index ef766d5..7eeddc7 100644 --- a/services/config.py +++ b/services/config.py @@ -17,6 +17,7 @@ config: models.config.Config = { "enabled": False, "imgurClientID": "", }, + "buttons": [], }, "users": [], } diff --git a/store/constants.py b/store/constants.py index 5363489..6ebd121 100644 --- a/store/constants.py +++ b/store/constants.py @@ -2,7 +2,7 @@ import os import sys name = "Discord Rich Presence for Plex" -version = "2.2.5" +version = "2.3.0" plexClientID = "discord-rich-presence-plex" discordClientID = "413407336082833418"