Skip to content

Releases

v2.1.1 - Jellyfin 12 Fix

Choose a tag to compare

@github-actions github-actions released this 11 Nov 10:57
0de0d12

Jellyfin 12 support. Jellyfin 12 rejects the legacy X-Emby-Token header, so MediaWatch 2.1.0 and older logged Jellyfin rejected the configured JELLYFIN_API_KEY (HTTP 401) even with a valid key and showed the server offline. The bot now sends the key as Authorization: MediaBrowser Token="…", which Jellyfin 10.10, 10.11 and 12 all accept. Plex is not affected.

Upgrading

Change the tag to 2.1.1, then docker compose pull && docker compose up -d. No config change is needed, and your existing JELLYFIN_API_KEY keeps working.

2.1.1 (2026-11-11)

Fixed

  • jellyfin: authenticate with the Authorization header for Jellyfin 12 (#31) (96150fc), closes #30

v2.1.0 - Maintenance mode

Choose a tag to compare

@github-actions github-actions released this 11 Oct 16:07
89ef683

Maintenance mode. /maintenance start swaps the red "Offline" dashboard for a yellow "Server is under maintenance 🛠️" embed with your reason, the expected end and whether the server answers right now. The bot presence shows 🛠️ Maintenance. With a duration it ends on its own once the time is up and the server is back; it survives a restart of the bot. Works the same for Plex and Jellyfin, and only DISCORD_AUTHORIZED_USERS can use it.

See Commands → /maintenance and The Dashboard → During maintenance.

Upgrading

Change the tag to 2.1.0, then docker compose pull && docker compose up -d. No config change is needed. The new options are optional and default to the current behaviour:

Option Default
maintenance.thumbnail_url "" Dashboard thumbnail during maintenance; empty uses dashboard.icon_url. The example config uses https://mediawatch.nichtlegacy.com/logo-maintenance.png.
maintenance.auto_end true End maintenance on its own once the duration has passed and the server answers again.
presence.maintenance_text "🛠️ Maintenance" Presence text during maintenance.
presence.maintenance_status "idle" Presence status during maintenance.

The /maintenance commands appear after the restart; if Discord does not show them yet, reload the client (Ctrl+R).

2.1.0 (2026-10-11)

Added

  • dashboard: announce maintenance instead of showing the server offline (ec919b1)
  • maintenance: add /maintenance start, end and status (ec919b1)

Fixed

  • docker: keep the code read-only for the bot however it was checked out (53a3ed3)

Documentation

  • add the GitHub social preview to the brand assets (55c4e3c)

v2.0.0 - MediaWatch

Choose a tag to compare

@nichtlegacy nichtlegacy released this 28 Sep 22:41

The project was called PlexWatch and only spoke to Plex. It is now
MediaWatch and monitors Plex or Jellyfin from the same bot.

⚠️ Breaking changes

Read the upgrade guide
before upgrading. In short:

  • The Docker image moved. New path: ghcr.io/nichtlegacy/mediawatch.
    The old ghcr.io/nichtlegacy/plexwatch package keeps its 1.x tags and gets
    no further updates, so an existing install will not be auto-upgraded into
    2.0. You have to change the image path yourself.
  • :latest now follows the main branch, every build that passes CI, not
    only releases. Pin a version tag such as :2.0.0 to stay on releases. A new
    :dev tag follows the dev branch.
  • DISCORD_GUILD_ID is now required. 1.x registered slash commands
    globally; 2.0 registers them per guild. Without the variable the bot stops at
    startup. On the first start with it set, leftover global commands from 1.x
    are cleared so nothing appears twice.
  • DISCORD_AUTHORIZED_USERS is now required and has to contain at least
    one numeric user ID. In 1.x it was optional.
  • config.json is replaced by config.yaml. An existing
    data/config.json is converted automatically on first start. The original
    file is left untouched and ignored from then on; the conversion is logged in
    full. Only if the conversion fails does the bot stop, with manual
    instructions.
  • media_server.type selects the platform (plex or jellyfin) and is
    set to plex by the automatic conversion, which is what every 1.x install
    was.
  • presence.sections is replaced by presence.libraries, a list of
    library names. Display name and emoji now come from the library block. The
    conversion translates the old list, and a hand-written config.yaml that
    still carries it is translated on load with a warning.
  • data/user_mapping.json is rewritten in place into a platform-keyed
    format that 1.x cannot read. Back it up if you might roll back.
  • Startup fails fast on a broken environment. Missing or unparsable
    required variables abort the start with a message naming what is missing and
    where to set it, instead of booting into a permanently "offline" dashboard.
  • A broken config.yaml stops the bot instead of falling back to the
    defaults, as 1.x did with a broken config.json. With the new privacy
    options, a silent fallback would reopen restricted views. The message names
    the file, line and column. /reload_config rejects such a file and keeps the
    running configuration. An empty file still means defaults.
  • The dashboard turns offline after server.offline_threshold (default
    300 seconds) instead of on the first failed check. Set it to 0 for the
    1.x behaviour.
  • The container no longer runs the bot as root. It starts as root to adopt
    the volume, then drops to PUID/PGID (default 1000:1000). Hosts whose
    share user differs, Unraid uses 99:100, should set both, and recreate the
    container after a change: a plain docker restart keeps the old values.
    PUID or PGID 0 is refused, and the container exits before the bot
    starts.
  • A fresh Docker volume receives only config.yaml.example. Copy it to
    config.yaml; until then the bot runs on built-in defaults. 1.x copied a
    complete default config into an empty volume.
  • A local run needs Python 3.12 (1.x: 3.8 and later). Recreate the
    virtualenv and reinstall requirements.txt.
  • The log file of a local run is now logs/mediawatch_debug.log (was
    logs/plexwatch_debug.log), and log lines carry mediawatch_bot instead of
    plexwatch_bot. Update any log filter.
  • /cogs is limited to DISCORD_AUTHORIZED_USERS, like the other cog
    commands.
  • The single-module cogs/plex_core.py from 1.x is gone. The bot is now
    cogs/media_core/, a package per platform on top of a shared layer, and
    /load, /unload and /reload take media_core, resolved to the active
    platform, instead of plex_core.

Added

  • Jellyfin support: a full platform next to Plex, selected via
    media_server.type, with the same dashboard, stream details, Kill Stream and
    library statistics. Clients on the local network are recognised via
    ipaddress, which covers Docker's default networks and IPv6, audio and
    subtitle languages are shown as names (German, not deu), and music
    streams show bit depth and sample rate, as on Plex.
  • Multi-platform architecture: cogs/media_core/ with a shared layer
    (models, formatters, config, dashboard embed, presence) and one package per
    platform.
  • YAML configuration with a documented data/config.yaml.example, and an
    automatic conversion of a 1.x data/config.json. A block whose options are
    all commented out means the defaults, and an unknown media_server.type
    falls back to plex with a warning.
  • Environment validation at startup that checks the required Discord
    variables and the credentials for the selected platform, rejects
    non-numeric IDs, and reports the mismatch case explicitly
    (media_server.type is jellyfin but only PLEX_* is set). Partially
    configured optional integrations warn instead of aborting.
  • Buttons under the dashboard: one Stream N - User button per running
    stream, up to eight, and optionally 🌐 Global Stats. They keep working
    after a bot restart.
  • Stream details behind each stream button: device, player, quality,
    transcode decision and tracks. On Plex they come from Tautulli, Jellyfin
    serves them directly. TZ sets the timezone of the start and end clock in
    Plex stream details (default UTC).
  • Kill Stream for DISCORD_AUTHORIZED_USERS, with a reason message
    prefilled from stream_controls.kill_stream.default_reason. On Plex it works
    without Tautulli; on Jellyfin, which has no reason field, the reason is sent
    as an on-screen message first.
  • Tautulli integration for Plex: user statistics in up to three pages
    (watch behaviour, devices with a chart, top shows) and global server
    statistics in three pages (overview, activity with a peak-hour chart, top 10
    users by watch time). Global statistics are cached for five minutes and
    shared between concurrent clicks.
  • Statistics options: global_stats.button_location puts the Global Stats
    button in the stream details, on the dashboard, or both, and
    global_stats.page_2.time_range sets the window of the activity page.
    user_stats.enabled, user_stats.pages, user_stats.time_range and
    user_stats.top_tv_count shape the user statistics, and
    cache.user_stats_ttl caches them.
  • A link to the played item in Plex from the stream details, which need
    Tautulli: a Plex button, and the embed title links to the same place. It
    opens Plex Web, or the Plex app on a phone. Configure it under
    stream_details.links.plex (enabled, base_url, emoji); base_url
    accepts any Plex Web, for servers that are not reached through plex.tv.
  • Privacy options, all defaulting to false so every view stays open as
    before: stream_details.restrict_to_authorized,
    user_stats.restrict_to_authorized and global_stats.restrict_to_authorized
    limit those views to DISCORD_AUTHORIZED_USERS, and
    stream_details.show_ip_for_authorized_users shows client IPs to them only.
  • Configurable bot presence: presence.enabled, activity_type
    (custom, watching, listening), status and offline_status (online,
    idle, dnd) and auth_failed_text, all defaulting to the 1.x behaviour.
    presence.libraries takes library names, "all", and :episodes for the
    episode count.
  • display options: thousands_separator and episode_label, replacing
    the hard-coded English "Episodes".
  • /mapping commands to manage the username-to-display-name mapping from
    Discord, with an autocomplete that only authorized users get. A flat 1.x
    user_mapping.json is converted to the platform-keyed format.
  • /reload_config to apply config changes without restarting the bot,
    including a switch of media_server.type. If the new platform fails to load,
    the previous one is restored.
  • !sync to republish the slash commands by hand: guild, copy,
    global, clear and clearglobal. A bare !sync means guild.
  • Server uptime that survives a bot restart, kept in
    data/runtime_state.json. It is refreshed once a minute while the server is
    reachable, so a crash or docker kill keeps it too, and it starts over when
    the bot was away longer than server.offline_threshold.
  • A clean shutdown on SIGTERM. The bot runs as PID 1 and 1.x installed no
    handler, so docker stop waited out its grace period and killed it.
  • SABnzbd: a status icon per download (paused, checking, propagating,
    fetching, downloading, can be switched off with show_status_icons), disk
    space in the unit that fits instead of fixed GB and TB,
    sabnzbd.show_when_empty, and a choice of which disk SABnzbd reports
    (diskspace_free_key, diskspace_total_key).
  • A test suite and CI: lint, tests and a Docker build on every push and
    pull request to main and dev, ruff as the single lint and format tool,
    Dependabot for pip and github-actions, and releases cut by release-please.
    Every published image is started first and checked for its user and file
    permissions.
  • A docker-compose.yml in the repository, and CONTRIBUTING.md,
    SECURITY.md, this changelog, issue templates and a pull request template.

Changed

  • Renamed from PlexWatch to MediaWatch across code, docs, image path and
    repository URL.
  • The Docker image is based on python:3.12-slim instead of
    python:3-alpine.
  • Blocking plexapi calls run off the event loop, so a slow Plex server no
    longer stalls the bot.
  • Runtime dependencies are constrained to compatible ranges, so a rebuild
    cannot pull in a new major version...
Read more

v1.1.3 - Enhanced Music Support

Choose a tag to compare

@nichtlegacy nichtlegacy released this 09 Apr 21:46
645a864

What's Changed

  • Improved display formatting for music tracks in the PlexWatch dashboard
    • Proper artist and track title detection
    • Fixed incorrect episode display for music
    • Added 🎵 emoji for music entries
    • Show bit depth, sample rate, and bitrate for audio
    • Cleaner, more informative UI for Plexamp sessions
      by @jakedev796 in #10

New Contributors

v1.1.2 - SABnzbd URL Fix

Choose a tag to compare

@nichtlegacy nichtlegacy released this 30 Mar 21:33

What's Changed

  • Fixed SABnzbd API connection issue caused by duplicate slashes in the URL (e.g., http://[host]:8082//api) by implementing urllib.parse.urljoin for robust URL construction. Resolves #9 reported by @CaptSonic.

v1.1.0 - Now with Docker Support

Choose a tag to compare

@nichtlegacy nichtlegacy released this 14 Mar 16:13
d82fa39

What's Changed

  • Added Docker support with Dockerfile, entrypoint.sh, and updated core files by @ShrinkWrapper in #1
  • Updated README with optional SABnzbd and Uptime Kuma configuration by @nichtlegacy

New Contributors