[
  {
    "guid": "0f2fd0fd-09ff-4f49-9f1c-4a8f421a4b7d",
    "name": "JellyWatchParty",
    "description": "Watch movies together in sync with friends on Jellyfin",
    "overview": "Synchronized watch parties for Jellyfin. Create or join rooms to watch content together with friends, perfectly in sync.",
    "owner": "TIGamingTV",
    "category": "General",
    "versions": [
      {
        "version": "4.0.0.0",
        "changelog": "## ✨ Highlights\r\n\r\n**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.\r\n\r\n## 🆕 What's new\r\n\r\n### Discord bot\r\n\r\n- New optional sidecar (`src/integrations/discord-bot`) that relays Discord interactions to the session server. It holds no state and decides nothing itself.\r\n- `/jwp` slash commands with private replies, plus **one live panel message per room**: join, add or remove my device, leave, pick host, close.\r\n- Passwords and link codes are only typed into modals. User-supplied names are escaped, and the bot never pings anyone.\r\n- Runs from the new `discord` compose profile, with its own image on GHCR (`ghcr.io/<owner>/<bot-image>`).\r\n- Built on `twilight` instead of `serenity`, because serenity 0.12 pulls in a `rustls-webpki` with open advisories (RUSTSEC-2026-0049/0098/0099/0104).\r\n\r\n### Linking chat accounts to Jellyfin users\r\n\r\n- 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.\r\n- Codes are stored as an HMAC under a per-install key (`secret.key` in `DATA_DIR`). They are never logged and shown only once.\r\n- Attempt limits (fixed windows that start at the first wrong code):\r\n\r\n| Limit                                         | Effect                           |\r\n| --------------------------------------------- | -------------------------------- |\r\n| 10 wrong tries on one code, from anyone       | That user's code locks           |\r\n| 5 wrong tries on one account                  | Account waits 15 min             |\r\n| 50 wrong tries across all users in 10 min     | Linking pauses for everyone      |\r\n\r\n### Rooms for linked chat users\r\n\r\n- Rooms created through the bot belong to the Jellyfin user who made them.\r\n- 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.\r\n- 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.\r\n- Device ownership is checked against the same fresh `/Sessions` result the device is bridged from, so a session ID can't be used to drive someone else's TV.\r\n- A chat room stays open (hostless) when its last device leaves, and closes after the platform's idle timeout.\r\n\r\n### Admin panel additions\r\n\r\n- New **bot settings** section, a **users and link codes** table, and an **activity log**.\r\n- Sidecars talk to the server over a token-protected integration API (config, heartbeat, link, unlink, me) on its own port: `<port>`.\r\n\r\n### New and changed settings\r\n\r\n| Variable / setting        | What it does                                                                 |\r\n| ------------------------- | ---------------------------------------------------------------------------- |\r\n| `<DISCORD_TOKEN>`         | Bot token for the `discord` profile `<confirm name>`                         |\r\n| `<INTEGRATION_PORT>`      | Port of the integration API `<confirm name and default>`                     |\r\n| `JWP_TAG`                 | Image tag the prod compose file pulls (`dev`, `beta`, `X.Y.Z`, `X.Y`, `latest`) |\r\n| `DATA_DIR`                | Now backed by the `jwp-data` volume, holds integration data and `secret.key` |\r\n| `ADMIN_HOST`              | Now passed through by both compose files                                     |\r\n| `ADMIN_PASSWORD_FILE`     | Now passed through by both compose files                                     |\r\n| `JELLYFIN_API_KEY_FILE`   | Now passed through by both compose files                                     |\r\n\r\nThe bot settings in the admin panel have defaults and ranges, listed in the Discord bot docs.\r\n\r\n## 🐳 Docker and CI\r\n\r\n- The Discord bot image is now published to GHCR next to the session server image. Both get the same tags: `dev` (from `develop`), `beta` (from `main`), and `X.Y.Z`, `X.Y` and `latest` on release. **Tags have no `v` prefix.**\r\n- The prod compose file now **pulls the published images** instead of building them. Choose the tag with `JWP_TAG`.\r\n- Security checks now run on `develop`, and the bot image is scanned.\r\n- A failed bot build does not hold back the release assets.\r\n\r\n## 🔒 Security fixes\r\n\r\n- **Integration data file permissions.** A leftover `integrations.json.tmp` kept its own permissions when reopened, so the data file could end up `0644`. It is now always written as a fresh `0600` file.\r\n- **Integration tokens** are kept out of debug output.\r\n- **Redirects.** The bot no longer follows redirects from the integration API.\r\n\r\n## 🐛 Bug fixes\r\n\r\n- 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.\r\n- Escaped member names could push a panel field past 1024 characters, after which every panel edit was refused. Long room lists and `whoami` replies over 2000 characters left the user on \"thinking...\".\r\n- The bot missed settings saved just before a server restart, because the settings version restarts at 1 with the server. It now reloads them.\r\n- Panel edits lost to network or Discord errors are now retried.\r\n\r\n## 📚 Documentation\r\n\r\n- README and installation quick start now cover the admin panel and the optional Discord bot.\r\n- New **Discord Bot troubleshooting** section, setting defaults and ranges, and backup guidance for `secret.key`.\r\n- Corrected the lockout and throttle descriptions, the host promotion notes and the integration token notes.\r\n- Documented how to test `develop` builds with `JWP_TAG=dev`.\r\n\r\n## 🔄 Upgrading\r\n\r\n1. Update the session server. The plugin is unchanged in this release `<confirm>`.\r\n2. **Production compose users:** the compose file now pulls images from GHCR. Set `JWP_TAG` if you don't want `latest`.\r\n3. To use the bot, start it with the `discord` compose profile and set `<DISCORD_TOKEN>` `<confirm>`.\r\n4. In the admin panel, open the bot settings, then assign link codes to your users.\r\n5. Make sure the `jwp-data` volume is persisted and **back up `secret.key`**. Without it, issued link codes can no longer be verified `<confirm>`.\r\n\r\n## What's Changed\r\n\r\n- feat(server): link chat accounts to Jellyfin users with admin-issued codes in #92\r\n- feat(server): let linked chat users run rooms with their own devices in #93\r\n- feat: add a Discord bot for third-party-client watch parties in #94\r\n- ci: publish the Discord bot image to GHCR in #95\r\n- fix: pre-release hardening in #96\r\n- docs: fix Discord bot docs and add troubleshooting in #97\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v3.0.0.0...v4.0.0.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v4.0.0.0/JellyWatchParty-v4.0.0.0.zip",
        "checksum": "61634aee547f4c9e5ac5574d8ff11a56",
        "timestamp": "2026-10-10T15:58:45Z"
      },
      {
        "version": "3.0.0.0",
        "changelog": "## ✨ Highlights\r\n\r\n**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.\r\n\r\n## ⚠️ Breaking change: panel bridging is now off by default\r\n\r\nPlugin **2.1.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.\r\n\r\nFor most servers, use the admin panel's device support instead (see below).\r\n\r\n## 🆕 What's new\r\n\r\n### Admin panel (#85)\r\n- Second listener on `ADMIN_PORT` (default `3001`) with a web UI and JSON API.\r\n- See every room with its members, host, sync status and connection, plus connected clients that are not in a room.\r\n- Create **groups**: named rooms with an optional password (with a built-in generator) that users join from the Watch Party panel.\r\n- 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.\r\n- Empty admin groups are removed after `ADMIN_EMPTY_GROUP_TTL_SECS` (default 10 minutes).\r\n- Clients moved or removed by an admin get a toast in the web UI.\r\n- **Opt-out config:** the panel starts only when `ADMIN_PASSWORD` or `ADMIN_PASSWORD_FILE` is set. Without it, the server logs a warning and runs normally.\r\n\r\n| Variable | Default |\r\n|---|---|\r\n| `ADMIN_ENABLED` | `true` |\r\n| `ADMIN_HOST` | `0.0.0.0` |\r\n| `ADMIN_PORT` | `3001` |\r\n| `ADMIN_USERNAME` | `admin` |\r\n| `ADMIN_PASSWORD` / `_FILE` | required |\r\n| `ADMIN_SESSION_TTL_SECS` | `43200` |\r\n| `ADMIN_COOKIE_SECURE` | `false` |\r\n| `ADMIN_TRUST_X_FORWARDED_FOR` | `false` |\r\n| `ADMIN_EMPTY_GROUP_TTL_SECS` | `600` |\r\n\r\nBoth compose files, `.env.example`, the Dockerfile `EXPOSE` and the Windows README are updated.\r\n\r\n**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.\r\n\r\n### Jellyfin device control (#86)\r\nSet `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.\r\n\r\n- **Receivers** start on the room's item, follow pause, play, seek and media changes, and stay within 2 s of the room.\r\n- **Hosts** report play, pause, seeks and item switches to the room.\r\n- Positions are extrapolated between progress reports to avoid repeated seeks.\r\n- A device missing for 90 s leaves its room. When a host leaves, a person is promoted before any device.\r\n\r\n| Variable | Default |\r\n|---|---|\r\n| `JELLYFIN_URL` | none (as reachable from the session server) |\r\n| `JELLYFIN_API_KEY` / `_FILE` | none (Dashboard → API Keys) |\r\n| `BRIDGE_POLL_INTERVAL_MS` | `1000` (minimum 250) |\r\n\r\n### Plugin 2.1.0.0 (#87)\r\n- New `EnablePanelBridging` master switch (see above). Turning it or a role off now stops the matching running bridges immediately.\r\n- The plugin config section is now \"Watch Party Panel Bridging (trusted servers only)\". It explains the change and points to the admin panel.\r\n- Plugin bridges now send `bridge_device_id`. The admin panel labels these connections *Plugin bridge*, and a device can't be bridged twice.\r\n\r\n## 🔒 Security fixes\r\n- **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.\r\n- **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/Sessions` and `Bridge/Status` now list only your own sessions, and `Start`, `Follow` and `Stop` return 403 for someone else's. Jellyfin administrators can still bridge any session.\r\n\r\n## 🐛 Bug fixes\r\n- Fixed a possible deadlock: leave and disconnect took locks in the opposite order from every other handler (#85).\r\n- Fixed stale membership: `join_room` and `create_room` left a client listed in the room it was already in (#85).\r\n\r\n## 📚 Documentation\r\nUpdated 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.\r\n\r\n## 🔄 Upgrading\r\n1. Update the session server and plugin together.\r\n2. Set `ADMIN_PASSWORD` (or `_FILE`) to enable the admin panel, and open port `3001`.\r\n3. To control TV apps from the panel, set `JELLYFIN_URL` and `JELLYFIN_API_KEY`.\r\n4. If you still want the in-player Host and Receiver bridges, tick **Enable panel bridging** on the plugin config page.\r\n\r\n## What's Changed\r\n* feat(server): put Jellyfin devices into rooms from the admin panel by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/86\r\n* feat(plugin): make panel bridging opt-in and limit it to own sessions by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/87\r\n* fix(plugin): never drive one device from two places, and leave rooms cleanly by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/89\r\n* Feat/admin jellyfin bridge by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/90\r\n* Feat/admin panel core by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/91\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.8.0...v3.0.0.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v3.0.0.0/JellyWatchParty-v3.0.0.0.zip",
        "checksum": "7f6893e4cc038c400256d669104ef304",
        "timestamp": "2026-10-04T14:50:55Z"
      },
      {
        "version": "2.0.8.0",
        "changelog": "## What's Changed\r\n* feat(protocol,web,server): room media follows the host (set_media) by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/73\r\n* fix(web): stale item id from the hidden player OSD, and guest ready after media_changed (#71 follow-up) by @francotosqui in https://github.com/TIGamingTV/JellyWatchParty/pull/77\r\n* feat(web): close the panel from an X button, Escape, or a click outside by @francotosqui in https://github.com/TIGamingTV/JellyWatchParty/pull/78\r\n* feat: show each participant's name and playback status by @francotosqui in https://github.com/TIGamingTV/JellyWatchParty/pull/79\r\n* fix(server): stop logging room passwords and throttle failed joins by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/83\r\n* feat: start the room's first play together after a countdown by @francotosqui in https://github.com/TIGamingTV/JellyWatchParty/pull/80\r\n* Develop by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/84\r\n\r\n## New Contributors\r\n* @francotosqui made their first contribution in https://github.com/TIGamingTV/JellyWatchParty/pull/77\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.7.0...v2.0.8.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.8.0/JellyWatchParty-v2.0.8.0.zip",
        "checksum": "c18d792c6b0445b98acf6fed0d8bcc4d",
        "timestamp": "2026-09-25T15:37:38Z"
      },
      {
        "version": "2.0.7.0",
        "changelog": "## What's Changed\r\n* fix(web): resolve item id and start playback without global playbackManager by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/68\r\n* fix(web): send Authorization header from apiFetch, not just X-Emby-Token by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/72\r\n* fix(web): send Authorization header from apiFetch, not just X-Emby-Token by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/74\r\n* fix(server): correct set_media tests for the room_list broadcast to host by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/75\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.6.0...v2.0.7.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.7.0/JellyWatchParty-v2.0.7.0.zip",
        "checksum": "99186b664c4961e490e377fc690e7d6a",
        "timestamp": "2026-09-24T09:12:54Z"
      },
      {
        "version": "2.0.6.0",
        "changelog": "**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.5.0...v2.0.6.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.6.0/JellyWatchParty-v2.0.6.0.zip",
        "checksum": "f36d248db3318d5113b8e23a40d64efe",
        "timestamp": "2026-09-24T08:58:28Z"
      },
      {
        "version": "2.0.6.0",
        "changelog": "**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.5.0...v2.0.6.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.6.0/JellyWatchParty-v2.0.6.0.zip",
        "checksum": "8a8ece6a34898094b73967cb27b939a3",
        "timestamp": "2026-09-24T08:56:08Z"
      },
      {
        "version": "2.0.6.0",
        "changelog": "**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.5.0...v2.0.6.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.6.0/JellyWatchParty-v2.0.6.0.zip",
        "checksum": "8571d8c20467ddd703af8e222eeb1ba0",
        "timestamp": "2026-09-24T08:49:28Z"
      },
      {
        "version": "2.0.5.0",
        "changelog": "## What's Changed\r\n* Update develop plugin manifest (build 127) by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/69\r\n* Merge remote-tracking branch 'origin/develop' into fix/jf-12.1-playback-manager by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/70\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.4.0...v2.0.5.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.5.0/JellyWatchParty-v2.0.5.0.zip",
        "checksum": "80c633b81aee6e81911eac1fc9aaae1d",
        "timestamp": "2026-09-23T22:03:10Z"
      },
      {
        "version": "2.0.4.0",
        "changelog": "## What's Changed\r\n* refactor: migrate session-server from warp to axum, resolving RUSTSEC-2026-0258 at the source by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/66\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.3.0...v2.0.4.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.4.0/JellyWatchParty-v2.0.4.0.zip",
        "checksum": "fc5e63a059c04e32eb5cbbe8540ea7a3",
        "timestamp": "2026-09-09T23:34:38Z"
      },
      {
        "version": "2.0.3.0",
        "changelog": "## What's Changed\r\n* fix: ignore RUSTSEC-2026-0258 in cargo-audit config by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/64\r\n* fix: inject the Jellyfin 12 header button into the MUI toolbar instead of floating it by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/65\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.2.0...v2.0.3.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.3.0/JellyWatchParty-v2.0.3.0.zip",
        "checksum": "65d101be6d6ed1c78c8937c5d72f9770",
        "timestamp": "2026-09-09T18:25:31Z"
      },
      {
        "version": "2.0.2.0",
        "changelog": "## What's Changed\r\n* fix: v12 floating button overlaps other plugins; replace SyncPlay when configured by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/63\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.1.0...v2.0.2.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.2.0/JellyWatchParty-v2.0.2.0.zip",
        "checksum": "1aa9f5cca54bcf5f4545fb0bc0c1d7e4",
        "timestamp": "2026-09-09T12:27:48Z"
      },
      {
        "version": "2.0.1.0",
        "changelog": "## What's Changed\r\n* Drop Jellyfin 10.11.x support, target Jellyfin 12.x only by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/61\r\n* fix: Jellyfin 12's default layout hides the header button by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/62\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v2.0.0.0...v2.0.1.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.1.0/JellyWatchParty-v2.0.1.0.zip",
        "checksum": "2adb97cbd0e5f9eb821e003ce5fcdbab",
        "timestamp": "2026-09-09T07:24:12Z"
      },
      {
        "version": "2.0.0.0",
        "changelog": "**This release brings JWP to jellyfin-rc7**\r\n\r\n## What's Changed\r\n* docs: fix broken tables and dead internal links by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/56\r\n* Implement dual-target build for Jellyfin 10.11 + 12.x support by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/57\r\n* Claude/jellyfin v12 rc3 testing sakmhg by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/59\r\n* Restore the dual-target Jellyfin 10.11 + 12.x build clobbered by the #59 merge by @TIGamingTV in https://github.com/TIGamingTV/JellyWatchParty/pull/60\r\n\r\n\r\n**Full Changelog**: https://github.com/TIGamingTV/JellyWatchParty/compare/v1.8.0.0...v2.0.0.0",
        "targetAbi": "12.0.0.0",
        "sourceUrl": "https://github.com/TIGamingTV/JellyWatchParty/releases/download/v2.0.0.0/JellyWatchParty-v2.0.0.0-jellyfin12.zip",
        "checksum": "14c79f31773f7b6917b1622fd1facd9d",
        "timestamp": "2026-09-07T16:23:42Z"
      }
    ]
  }
]
