Repository navigation
Releases: TIGamingTV/JellyWatchParty
Release list
v4.0.0.0
✨ Highlights
Discord bot. People on third-party Jellyfin clients (Android TV, Fladder, Swiftfin, …) can now run and join watch parties from Discord, without an admin placing their devices. Admins link a Discord user to a Jellyfin user with a one-time code, and the linked user controls their own devices from a live room panel in the channel.
🆕 What's new
Discord bot
- New optional sidecar (
src/integrations/discord-bot) that relays Discord interactions to the session server. It holds no state and decides nothing itself. /jwpslash commands with private replies, plus one live panel message per room: join, add or remove my device, leave, pick host, close.- Passwords and link codes are only typed into modals. User-supplied names are escaped, and the bot never pings anyone.
- Runs from the new
discordcompose profile, with its own image on GHCR (ghcr.io/<owner>/<bot-image>). - Built on
twilightinstead ofserenity, because serenity 0.12 pulls in arustls-webpkiwith open advisories (RUSTSEC-2026-0049/0098/0099/0104).
Linking chat accounts to Jellyfin users
- In the admin panel, an admin assigns a 4-digit code to a Jellyfin user. The user enters their Jellyfin name and the code in chat to link.
- Codes are stored as an HMAC under a per-install key (
secret.keyinDATA_DIR). They are never logged and shown only once. - Attempt limits (fixed windows that start at the first wrong code):
| Limit | Effect |
|---|---|
| 10 wrong tries on one code, from anyone | That user's code locks |
| 5 wrong tries on one account | Account waits 15 min |
| 50 wrong tries across all users in 10 min | Linking pauses for everyone |
Rooms for linked chat users
- Rooms created through the bot belong to the Jellyfin user who made them.
- Participants join with the room password (same throttle as the Watch Party panel, per user per room) and can only add or remove their own Jellyfin devices.
- The owner or an admin can pick the host, remove people, change the name and password, hand the room over, and close it. The owner cannot leave.
- Device ownership is checked against the same fresh
/Sessionsresult the device is bridged from, so a session ID can't be used to drive someone else's TV. - A chat room stays open (hostless) when its last device leaves, and closes after the platform's idle timeout.
Admin panel additions
- New bot settings section, a users and link codes table, and an activity log.
- Sidecars talk to the server over a token-protected integration API (config, heartbeat, link, unlink, me) on its own port:
<port>.
New and changed settings
| Variable / setting | What it does |
|---|---|
<DISCORD_TOKEN> |
Bot token for the discord profile <confirm name> |
<INTEGRATION_PORT> |
Port of the integration API <confirm name and default> |
JWP_TAG |
Image tag the prod compose file pulls (dev, beta, X.Y.Z, X.Y, latest) |
DATA_DIR |
Now backed by the jwp-data volume, holds integration data and secret.key |
ADMIN_HOST |
Now passed through by both compose files |
ADMIN_PASSWORD_FILE |
Now passed through by both compose files |
JELLYFIN_API_KEY_FILE |
Now passed through by both compose files |
The bot settings in the admin panel have defaults and ranges, listed in the Discord bot docs.
🐳 Docker and CI
- The Discord bot image is now published to GHCR next to the session server image. Both get the same tags:
dev(fromdevelop),beta(frommain), andX.Y.Z,X.Yandlateston release. Tags have novprefix. - The prod compose file now pulls the published images instead of building them. Choose the tag with
JWP_TAG. - Security checks now run on
develop, and the bot image is scanned. - A failed bot build does not hold back the release assets.
🔒 Security fixes
- Integration data file permissions. A leftover
integrations.json.tmpkept its own permissions when reopened, so the data file could end up0644. It is now always written as a fresh0600file. - Integration tokens are kept out of debug output.
- Redirects. The bot no longer follows redirects from the integration API.
🐛 Bug fixes
- A web host who became host of a Discord room could close it for everyone by starting a new room. Only the owner or an admin can close a chat room now, and the host just leaves it.
- Escaped member names could push a panel field past 1024 characters, after which every panel edit was refused. Long room lists and
whoamireplies over 2000 characters left the user on "thinking...". - The bot missed settings saved just before a server restart, because the settings version restarts at 1 with the server. It now reloads them.
- Panel edits lost to network or Discord errors are now retried.
📚 Documentation
- README and installation quick start now cover the admin panel and the optional Discord bot.
- New Discord Bot troubleshooting section, setting defaults and ranges, and backup guidance for
secret.key. - Corrected the lockout and throttle descriptions, the host promotion notes and the integration token notes.
- Documented how to test
developbuilds withJWP_TAG=dev.
🔄 Upgrading
- Update the session server. The plugin is unchanged in this release
<confirm>. - Production compose users: the compose file now pulls images from GHCR. Set
JWP_TAGif you don't wantlatest. - To use the bot, start it with the
discordcompose profile and set<DISCORD_TOKEN><confirm>. - In the admin panel, open the bot settings, then assign link codes to your users.
- Make sure the
jwp-datavolume is persisted and back upsecret.key. Without it, issued link codes can no longer be verified<confirm>.
What's Changed
- feat(server): link chat accounts to Jellyfin users with admin-issued codes in #92
- feat(server): let linked chat users run rooms with their own devices in #93
- feat: add a Discord bot for third-party-client watch parties in #94
- ci: publish the Discord bot image to GHCR in #95
- fix: pre-release hardening in #96
- docs: fix Discord bot docs and add troubleshooting in #97
Full Changelog: v3.0.0.0...v4.0.0.0
v3.0.0.0
✨ Highlights
Admin panel. The session server now has a web UI and JSON API for managing every room, including rooms users created. It is also the new way to put non-browser Jellyfin clients (Android TV, Fladder, Swiftfin, …) into a watch party.
⚠️ Breaking change: panel bridging is now off by default
Plugin 3.0.0.0 adds a new master switch, EnablePanelBridging, which is off by default, including on upgraded installs. If you used the Host or Receiver bridges, they stop working after the update until an admin ticks the new switch on the plugin config page. The per-role flags do nothing until then.
For most servers, use the admin panel's device support instead (see below).
🆕 What's new
Admin panel (#85)
- Second listener on
ADMIN_PORT(default3001) with a web UI and JSON API. - See every room with its members, host, sync status and connection, plus connected clients that are not in a room.
- Create groups: named rooms with an optional password (with a built-in generator) that users join from the Watch Party panel.
- Add or move signed-in clients into any room without its password, make another member host, remove members, rename rooms, set or clear passwords, and close rooms.
- Empty admin groups are removed after
ADMIN_EMPTY_GROUP_TTL_SECS(default 10 minutes). - Clients moved or removed by an admin get a toast in the web UI.
- Opt-out config: the panel starts only when
ADMIN_PASSWORDorADMIN_PASSWORD_FILEis set. Without it, the server logs a warning and runs normally.
| Variable | Default |
|---|---|
ADMIN_ENABLED |
true |
ADMIN_HOST |
0.0.0.0 |
ADMIN_PORT |
3001 |
ADMIN_USERNAME |
admin |
ADMIN_PASSWORD / _FILE |
required |
ADMIN_SESSION_TTL_SECS |
43200 |
ADMIN_COOKIE_SECURE |
false |
ADMIN_TRUST_X_FORWARDED_FOR |
false |
ADMIN_EMPTY_GROUP_TTL_SECS |
600 |
Both compose files, .env.example, the Dockerfile EXPOSE and the Windows README are updated.
Panel security: constant-time password check, HttpOnly; SameSite=Strict session cookie, login throttling (5 per IP and 30 overall per minute), required x-jwp-admin header and matching Origin on every change, strict CSP, X-Frame-Options: DENY, no-store, and an admin: log line for every action.
Jellyfin device control (#86)
Set JELLYFIN_URL and JELLYFIN_API_KEY and the admin panel lists active Jellyfin clients that can't show the Watch Party panel. Add any of them to any room as host or receiver. The session server drives them over the Jellyfin REST API, so per-user plugin bridges are no longer needed.
- Receivers start on the room's item, follow pause, play, seek and media changes, and stay within 2 s of the room.
- Hosts report play, pause, seeks and item switches to the room.
- Positions are extrapolated between progress reports to avoid repeated seeks.
- A device missing for 90 s leaves its room. When a host leaves, a person is promoted before any device.
| Variable | Default |
|---|---|
JELLYFIN_URL |
none (as reachable from the session server) |
JELLYFIN_API_KEY / _FILE |
none (Dashboard → API Keys) |
BRIDGE_POLL_INTERVAL_MS |
1000 (minimum 250) |
Plugin 3.0.0.0 (#87)
- New
EnablePanelBridgingmaster switch (see above). Turning it or a role off now stops the matching running bridges immediately. - The plugin config section is now "Watch Party Panel Bridging (trusted servers only)". It explains the change and points to the admin panel.
- Plugin bridges now send
bridge_device_id. The admin panel labels these connections Plugin bridge, and a device can't be bridged twice.
🔒 Security fixes
- Session takeover via
client_id(#85). Reconnecting with a known client id took over that session, including its room and host role. Each client now gets a resume secret that is sent only to its owner and required to reattach. Clients from before this change still connect, but lose their room on reconnect while the old entry is held. - Missing ownership checks in the plugin (#87). Any user could bridge or stop anyone's session and attach any session to any room, bypassing Jellyfin's
EnableRemoteControlOfOtherUsers.Bridge/SessionsandBridge/Statusnow list only your own sessions, andStart,FollowandStopreturn 403 for someone else's. Jellyfin administrators can still bridge any session.
🐛 Bug fixes
- Fixed a possible deadlock: leave and disconnect took locks in the opposite order from every other handler (#85).
- Fixed stale membership:
join_roomandcreate_roomleft a client listed in the room it was already in (#85).
📚 Documentation
Updated configuration, host-bridge, features, user-guide, troubleshooting, plugin, ARCHITECTURE and protocol docs. Also corrected plugin.md: the JWT secret is sent back to the config page through the admin-only plugin configuration API.
🔄 Upgrading
- Update the session server and plugin together.
- Set
ADMIN_PASSWORD(or_FILE) to enable the admin panel, and open port3001. - To control TV apps from the panel, set
JELLYFIN_URLandJELLYFIN_API_KEY. - If you still want the in-player Host and Receiver bridges, tick Enable panel bridging on the plugin config page.
What's Changed
- feat(server): put Jellyfin devices into rooms from the admin panel by @TIGamingTV in #86
- feat(plugin): make panel bridging opt-in and limit it to own sessions by @TIGamingTV in #87
- fix(plugin): never drive one device from two places, and leave rooms cleanly by @TIGamingTV in #89
- Feat/admin jellyfin bridge by @TIGamingTV in #90
- Feat/admin panel core by @TIGamingTV in #91
Full Changelog: v2.0.8.0...v3.0.0.0
v2.0.8.0
What's Changed
- feat(protocol,web,server): room media follows the host (set_media) by @TIGamingTV in #73
- fix(web): stale item id from the hidden player OSD, and guest ready after media_changed (#71 follow-up) by @francotosqui in #77
- feat(web): close the panel from an X button, Escape, or a click outside by @francotosqui in #78
- feat: show each participant's name and playback status by @francotosqui in #79
- fix(server): stop logging room passwords and throttle failed joins by @TIGamingTV in #83
- feat: start the room's first play together after a countdown by @francotosqui in #80
- Develop by @TIGamingTV in #84
New Contributors
- @francotosqui made their first contribution in #77
Full Changelog: v2.0.7.0...v2.0.8.0
v2.0.7.0
What's Changed
- fix(web): resolve item id and start playback without global playbackManager by @TIGamingTV in #68
- fix(web): send Authorization header from apiFetch, not just X-Emby-Token by @TIGamingTV in #72
- fix(web): send Authorization header from apiFetch, not just X-Emby-Token by @TIGamingTV in #74
- fix(server): correct set_media tests for the room_list broadcast to host by @TIGamingTV in #75
Full Changelog: v2.0.6.0...v2.0.7.0
v2.0.6.0
v2.0.5.0
What's Changed
- Update develop plugin manifest (build 127) by @TIGamingTV in #69
- Merge remote-tracking branch 'origin/develop' into fix/jf-12.1-playback-manager by @TIGamingTV in #70
Full Changelog: v2.0.4.0...v2.0.5.0
v2.0.4.0
What's Changed
- refactor: migrate session-server from warp to axum, resolving RUSTSEC-2026-0258 at the source by @TIGamingTV in #66
Full Changelog: v2.0.3.0...v2.0.4.0
v2.0.3.0
What's Changed
- fix: ignore RUSTSEC-2026-0258 in cargo-audit config by @TIGamingTV in #64
- fix: inject the Jellyfin 12 header button into the MUI toolbar instead of floating it by @TIGamingTV in #65
Full Changelog: v2.0.2.0...v2.0.3.0
v2.0.2.0
What's Changed
- fix: v12 floating button overlaps other plugins; replace SyncPlay when configured by @TIGamingTV in #63
Full Changelog: v2.0.1.0...v2.0.2.0
V2.0.1.0
What's Changed
- Drop Jellyfin 10.11.x support, target Jellyfin 12.x only by @TIGamingTV in #61
- fix: Jellyfin 12's default layout hides the header button by @TIGamingTV in #62
Full Changelog: v2.0.0.0...v2.0.1.0