From 2eef03c6e6d243e2211b3b66179aa447addf6846 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 9 Oct 2026 07:22:33 +0000 Subject: [PATCH] feat: filter by Plex player, and by several users A board mounted over a TV should show whatever is playing on that TV, whoever is watching. Plex User only matched one account, so a household with separate accounts had to run two instances and switch between them. - New Plex Player setting (plex_player): only show streams on these devices, matched case-insensitively against the device name Plex shows. - Plex User and Plex Player both take comma-separated lists. When both are set, a stream must match both. Bumps the plugin to 1.3.0. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01A7aYfsnp25o3X3x3AYhzPQ --- README.md | 5 +++-- __init__.py | 12 +++++++++--- docs/SETUP.md | 6 ++++-- manifest.json | 11 ++++++++--- tests/test_plugin.py | 38 ++++++++++++++++++++++++++++++++++++++ tests/test_sign_in.py | 2 +- 6 files changed, 63 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 9c4bd5f..f7417c2 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,8 @@ Center all three lines: | `server_url` | string | No | - | Plex server address, e.g. `http://192.168.1.100:32400`. Empty: found through your Plex account | | `token` | string | No | - | An X-Plex-Token to use instead of **Sign in with Plex**. When set, it wins | | `server_name` | string | No | - | Which server to use when your account has several and `server_url` is empty | -| `plex_user` | string | No | - | Only show this Plex user's streams | +| `plex_user` | string | No | - | Only show these Plex users' streams (comma-separated) | +| `plex_player` | string | No | - | Only show streams on these devices (comma-separated), e.g. `Living Room TV` | | `show_accents` | boolean | No | `true` | Frame the year or season/episode line with yellow tiles | | `hold_seconds` | integer | No | `60` | After playback stops, keep showing it this long before "Nothing playing" (0-600, 0 turns it off) | | `refresh_seconds` | integer | No | `30` | How often to ask Plex what's playing (10-600) | @@ -118,7 +119,7 @@ With no `server_url`, the plugin asks plex.tv for the servers on your account (` - Display lines that adapt to the board's width: Note, Flagship, and any Note Array - Long titles wrap across two lines; the rest of the layout moves to make room - Optional Plex yellow accent tiles -- Filter to a single Plex user +- Filter to some Plex users, some devices, or both (e.g. whatever is playing on the TV the board hangs over) - Every active stream available as `sessions`, plus a stream count - No blank board between episodes: the last stream stays up through Plex's Up Next countdown - Playback state color-coded: green playing, yellow paused, orange buffering diff --git a/__init__.py b/__init__.py index bd5e169..0cdf9e6 100644 --- a/__init__.py +++ b/__init__.py @@ -208,6 +208,11 @@ def _extra_lines( return extra +def name_set(value: Any) -> set: + """A comma-separated settings value as a set of lower-cased names; empty means no filter.""" + return {name.strip().lower() for name in str(value or "").split(",") if name.strip()} + + def pick_primary(sessions: List[Dict[str, Any]]) -> Dict[str, Any]: """The session to feature: playing beats buffering beats paused, then Plex's order.""" return min(sessions, key=lambda s: STATE_PRIORITY.index(s["state"])) @@ -414,9 +419,10 @@ def fetch_data(self) -> PluginResult: # A session with no title is one Plex has not loaded yet: nothing to show sessions = [parse_session(item) for item in items if item.get("title")] - plex_user = (self.config.get("plex_user") or "").strip().lower() - if plex_user: - sessions = [s for s in sessions if s["user"].lower() == plex_user] + for field, setting in (("user", "plex_user"), ("player", "plex_player")): + wanted = name_set(self.config.get(setting)) + if wanted: + sessions = [s for s in sessions if s[field].lower() in wanted] sessions = self._hold_through_gaps(sessions) primary = pick_primary(sessions) if sessions else None diff --git a/docs/SETUP.md b/docs/SETUP.md index ccadcae..6685022 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -18,7 +18,7 @@ Connect FiestaBoard to your Plex Media Server so your board shows what's playing 2. **Sign in** — In the plugin's settings, click **Sign in with Plex**. Plex's sign-in page opens in a new tab; sign in and approve **FiestaBoard**. The settings show you are connected once Plex confirms, usually within a few seconds. -3. **Server (usually nothing to do)** — Leave **Plex Server URL** empty and the plugin finds your server through your Plex account, trying its home-network address first. If your account can reach several servers, enter the one you want in **Plex Server Name**. If the board cannot reach the server that way, enter its address in **Plex Server URL** (for example `http://192.168.1.100:32400`). Optionally set a **Plex User** to only show that person's streams. +3. **Server (usually nothing to do)** — Leave **Plex Server URL** empty and the plugin finds your server through your Plex account, trying its home-network address first. If your account can reach several servers, enter the one you want in **Plex Server Name**. If the board cannot reach the server that way, enter its address in **Plex Server URL** (for example `http://192.168.1.100:32400`). Optionally set **Plex User** to only show some people's streams, or **Plex Player** to only show what's playing on some devices (for example the TV the board hangs over, whoever is watching). 4. **Template** — Create a page from the plugin's demo, or add the display lines to your own page: @@ -76,7 +76,8 @@ This is how the plugin worked before 1.1.0, and it still does. A pasted token is | Plex Server URL | No | - | Your server's address, including the port. Empty: found through your Plex account | | Plex Token | No | - | An X-Plex-Token, used instead of Sign in with Plex when set | | Plex Server Name | No | - | Which server to use when the URL is empty and your account has several | -| Plex User | No | - | Only show streams from this Plex user (case-insensitive) | +| Plex User | No | - | Only show streams from these Plex users, separated by commas (case-insensitive) | +| Plex Player | No | - | Only show streams on these devices, separated by commas (case-insensitive), e.g. `Living Room TV` | | Plex yellow accents | No | On | Frame the year or season/episode line with two yellow tiles on each side | | Hold Between Episodes | No | 60 seconds | After playback stops, keep showing it this long before "Nothing playing" (0-600 seconds, 0 turns it off) | | Refresh Interval | No | 30 seconds | How often to check Plex (10-600 seconds) | @@ -121,6 +122,7 @@ This is how the plugin worked before 1.1.0, and it still does. A pasted token is ### The board says "Nothing playing" while something is playing - If **Plex User** is set, it must match the name Plex shows for that user. +- If **Plex Player** is set, it must match the device name Plex shows. Put `{{plex.player}}` on a page with Plex Player empty to see it. - The board updates on the refresh interval, so wait up to 30 seconds. ### The board goes blank between episodes diff --git a/manifest.json b/manifest.json index 0d94f49..7b2e93e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "plex", "name": "Plex Now Playing", - "version": "1.2.0", + "version": "1.3.0", "description": "Show what's playing on your Plex Media Server: movies, TV episodes and music, laid out to fit any board", "author": "FiestaBoard Team", "repository": "https://github.com/Fiestaboard/fiestaboard-plugin--plex", @@ -36,7 +36,12 @@ "plex_user": { "type": "string", "title": "Plex User (optional)", - "description": "Only show streams from this Plex user. Leave empty to show anyone's." + "description": "Only show streams from these Plex users, separated by commas. Leave empty to show anyone's." + }, + "plex_player": { + "type": "string", + "title": "Plex Player (optional)", + "description": "Only show streams on these devices, separated by commas, using the names Plex shows (e.g. Living Room TV). Leave empty to show every device." }, "show_accents": { "type": "boolean", @@ -200,7 +205,7 @@ "example": "18" }, "stream_count": { - "description": "Number of active streams (after the Plex User filter)", + "description": "Number of active streams (after the Plex User and Plex Player filters)", "type": "number", "max_length": 2, "group": "playback", diff --git a/tests/test_plugin.py b/tests/test_plugin.py index 6324979..bb62797 100644 --- a/tests/test_plugin.py +++ b/tests/test_plugin.py @@ -454,6 +454,44 @@ def test_user_filter_without_match_is_idle(self): assert result.data["state"] == "Idle" assert result.data["stream_count"] == 0 + def test_user_filter_accepts_several_users(self): + kim = episode_item(User={"title": "Kim"}) + result, _ = fetch(make_plugin(plex_user="Sam, kim"), movie_item(), track_item(), kim) + assert sorted(s["user"] for s in result.data["sessions"]) == ["Kim", "Sam"] + + def test_user_filter_ignores_empty_entries(self): + result, _ = fetch(make_plugin(plex_user=" , "), movie_item()) + assert result.data["stream_count"] == 1 + + def test_player_filter_shows_only_that_device(self): + bedroom = episode_item(User={"title": "Kim"}, Player={"title": "Bedroom", "state": "playing"}) + result, _ = fetch(make_plugin(plex_player="living room tv"), bedroom, movie_item()) + assert result.data["title"] == "Interstellar" + assert result.data["stream_count"] == 1 + + def test_player_filter_follows_whoever_uses_the_device(self): + kim = episode_item(User={"title": "Kim"}) + result, _ = fetch(make_plugin(plex_player="Living Room TV"), kim) + assert result.data["user"] == "Kim" + + def test_player_filter_accepts_several_devices(self): + kitchen = track_item(Player={"title": "Kitchen", "state": "playing"}) + bedroom = episode_item(Player={"title": "Bedroom", "state": "playing"}) + result, _ = fetch(make_plugin(plex_player="Kitchen, Living Room TV"), kitchen, bedroom, movie_item()) + assert sorted(s["player"] for s in result.data["sessions"]) == ["Kitchen", "Living Room TV"] + + def test_player_filter_without_match_is_idle(self): + result, _ = fetch(make_plugin(plex_player="Bedroom"), movie_item()) + assert result.data["state"] == "Idle" + + def test_user_and_player_filters_both_apply(self): + kim_on_tv = episode_item(User={"title": "Kim"}) + alex_in_bedroom = track_item(User={"title": "Alex"}, Player={"title": "Bedroom", "state": "playing"}) + result, _ = fetch( + make_plugin(plex_user="Alex", plex_player="Living Room TV"), kim_on_tv, alex_in_bedroom, movie_item() + ) + assert [(s["user"], s["player"]) for s in result.data["sessions"]] == [("Alex", "Living Room TV")] + def test_show_accents_setting(self): result, _ = fetch(make_plugin(show_accents=False), episode_item(), board=NOTE) assert result.data["line_2"] == "S05 E14" diff --git a/tests/test_sign_in.py b/tests/test_sign_in.py index 2007f6f..5360fde 100644 --- a/tests/test_sign_in.py +++ b/tests/test_sign_in.py @@ -101,7 +101,7 @@ def test_requires_core_with_plex_pin(self): def test_existing_settings_keys_kept_and_optional(self): props = MANIFEST["settings_schema"]["properties"] - for key in ("server_url", "token", "plex_user", "show_accents", "refresh_seconds"): + for key in ("server_url", "token", "plex_user", "plex_player", "show_accents", "refresh_seconds"): assert key in props assert "token" not in MANIFEST["settings_schema"].get("required", []) assert "server_url" not in MANIFEST["settings_schema"].get("required", [])