diff --git a/FAQ.md b/FAQ.md index 5291da7e..126038b6 100644 --- a/FAQ.md +++ b/FAQ.md @@ -443,329 +443,6 @@ Go to the `Env` page, click `+` button, you will get list of all supported keys --- -# How to Add Webhooks - -Webhook URLs are **backend-specific** and follow this structure: - -``` -/v1/api/backend/[USER]@[BACKEND_NAME]/webhook -``` - -- `[USER]` should be the username of the sub-user, or `main` for the main user. -- `[BACKEND_NAME]` is the name of the backend you want to configure the webhook for. - -A typical full URL might look like: - -``` -http://localhost:8080/v1/api/backend/main@plex_foo/webhook -``` - -To get the correct URL easily: - -* Go to `WebUI > Backends`. -* Click on **Copy Webhook URL** next to the relevant backend. - -> [!IMPORTANT] -> If you have enabled `WS_SECURE_API_ENDPOINTS`, you have to add `?apikey=yourapikey` to the end of the the webhook URL. - -> [!NOTE] -> You may see a `webhook.token` key in older configurations. This is retained only for backward compatibility and has no -> effect. It will be removed in future versions. -> -> If you're using Plex and have sub-users, make sure to enable **Webhook Match User** to prevent sub-user activity from -> affecting the main user's watch state. - ------ - -## Emby (you need `Emby Premiere` to use webhooks). - -Go to your Manage Emby Server: - -* Old Emby versions: Server > Webhooks > (Click Add Webhook) `Old version` -* New Emby versions: username Preferences > Notifications > + Add Notification > Webhooks - -### Name (Emby v4.9+): - -`user@backend` or whatever you want, i simply prefer the name to reflect which user it belongs to. - -### Webhook/Notifications URL: - -`http://localhost:8080/v1/api/backend/[USER]@[BACKEND_NAME]/webhook` - -* Replace `[BACKEND_NAME]` with the name you have chosen for your backend. -* Replace `[USER]` with the `main` for main user or the sub user username. - -### Request content type (Emby v4.9+): - -`application/json` - -### Webhook Events: - -#### v4.7.9 or higher - -* New Media Added -* Playback -* Mark played -* Mark unplayed - -#### Versions prior to 4.7.9 - -* Playback events -* User events - -### Limit user events to: - -* Select your user. - -### Limit library events to: - -* Select libraries that you want to sync or leave it blank for all libraries. - -Click `Add Webhook / Save` - ------ - -## Jellyfin (Free) - -go to your jellyfin dashboard > plugins > Catalog > install: Notifications > Webhook, restart your jellyfin. After that -go back again to dashboard > plugins > webhook. Add `Add Generic Destination`, - -### Webhook Name: - -`user@backend` or whatever you want, i simply prefer the name to reflect which user it belongs to. - -#### Webhook Url: - -`http://localhost:8080/v1/api/backend/[USER]@[BACKEND_NAME]/webhook` - -* Replace `[BACKEND_NAME]` with the name you have chosen for your backend. -* Replace `[USER]` with the `main` for main user or the sub user username. - -#### Notification Type: - -* Item Added -* User Data Saved -* Playback Start -* Playback Stop - -#### User Filter: - -* Select your user. - -#### Item Type: - -* Movies -* Episodes - -### Send All Properties (ignores template) - -Toggle this checkbox. - -Click `Save` - ------ - -## Plex (You need `Plex Pass` to use webhooks) - -Go to your Plex Web UI > Settings > Your Account > Webhooks > (Click ADD WEBHOOK) - -### URL: - -`http://localhost:8080/v1/api/backend/[USER]@[BACKEND_NAME]/webhook` - -* Replace `[BACKEND_NAME]` with the name you have chosen for your backend. -* Replace `[USER]` with the `main` for main user or the sub user username. - -Click `Save Changes` - -> [!NOTE] -> If you share your plex server with other users, i,e. `Home/managed users`, you have to enable match user id, otherwise -> their play state will end up changing your play state. -> -> If you use multiple plex servers and use the same PlexPass account for all of them, You have to add each backend -> using the same method above, while enabling `limit webhook events to` `selected user` and `backend unique id`. -> Essentially, this method replaced the old unified webhook token for backends. - ----- - -## Plex Via tautulli - -Go to options > Notification Agents > Add a new notification agent > Webhook - -### Webhook URL: - -`http://localhost:8080/v1/api/backend/[USER]@[BACKEND_NAME]/webhook` - -* Replace `[BACKEND_NAME]` with the name you have chosen for your backend. -* Replace `[USER]` with the `main` for main user or the sub user username. - -> [!IMPORTANT] -> If you have enabled `WS_SECURE_API_ENDPOINTS`, you have to add `?apikey=yourapikey` to the end of the URL. - -### Webhook Method - -`PUT` - -### Description - -it's recommended to use something like `webhook for user XX for backend XX`. - -### Triggers - -Select the following events. - -- Playback Start -- Playback Stop -- Playback Pause -- Playback Resume -- Watched -- Recently Added - -### Data - -For each event there is a corresponding headers/data fields that you need to set using the following format. - -> [!IMPORTANT] -> It's extremely important that you copy the headers and data as it is, don't alter them if you don't know what you are -> doing. - -### JSON headers - -```json -{ - "user-agent": "Tautulli/{tautulli_version}" -} -``` - -### JSON Data - -```json -{ - "event": "tautulli.{action}", - "Account": { - "id": "{user_id}", - "thumb": "{user_thumb}", - "title": "{username}" - }, - "Server": { - "title": "{server_name}", - "uuid": "{server_machine_id}", - "version": "{server_version}" - }, - "Player": { - "local": "{stream_local}", - "publicAddress": "{ip_address}", - "title": "{player}", - "uuid": "{machine_id}" - }, - "Metadata": { - "librarySectionType": null, - "ratingKey": "{rating_key}", - "key": null, - "parentRatingKey": "{parent_rating_key}", - "grandparentRatingKey": "{grandparent_rating_key}", - "guid": "{guid}", - "parentGuid": null, - "grandparentGuid": null, - "grandparentSlug": null, - "type": "{media_type}", - "title": "{episode_name}", - "grandparentKey": null, - "parentKey": null, - "librarySectionTitle": "{library_name}", - "librarySectionID": "{section_id}", - "librarySectionKey": null, - "grandparentTitle": "{show_name}", - "parentTitle": "{season_name}", - "contentRating": "{content_rating}", - "summary": "{summary}", - "index": "{episode_num}", - "parentIndex": "{season_num}", - "audienceRating": "{audience_rating}", - "viewOffset": "{view_offset}", - "skipCount": null, - "lastViewedAt": "{last_viewed_date}", - "year": "{show_year}", - "thumb": "{poster_thumb}", - "art": "{art}", - "parentThumb": "{parent_thumb}", - "grandparentThumb": "{grandparent_thumb}", - "grandparentArt": null, - "grandparentTheme": null, - "duration": "{duration_ms}", - "originallyAvailableAt": "{air_date}", - "addedAt": "{added_date}", - "updatedAt": "{updated_date}", - "audienceRatingImage": null, - "userRating": "{user_rating}", - "Guids": { - "imdb": "{imdb_id}", - "tvdb": "{thetvdb_id}", - "tmdb": "{themoviedb_id}", - "tvmaze": "{tvmaze_id}" - }, - "file": "{file}", - "file_size": "{file_size_bytes}" - } -} -``` - -You need to do this for each event that you enabled in `Triggers` section. - -Click `Save` - -> [!NOTE] -> Tautulli Doesn't support sending user id with `created` event. as such if you enabled `Match webhook user`, new items -> will not be added and fail with `Request user id '' does not match configured value`. -> -> Marked as unplayed will most likely not work with Tautulli webhook events as it's missing critical data we need to -> determine if the item is marked as unplayed. - ---- - -# Webhook Limitations by Media Backend - -Below are known limitations and issues when using webhooks with different media backends: - -## Plex - -- Webhooks are **not sent** for "marked as played/unplayed" actions on all item types. -- Webhook events may be **skipped** if multiple items are added at once. -- When items are marked as **unwatched**, Plex resets the date on the media object. - -## Plex via Tautulli - -- Tautulli does **not send user IDs** with `itemAdd` (`created`) events. If `Match webhook user` is enabled, the request - will fail with: `Request user id '' does not match configured value`. -- **Marking items as unplayed** is not reliable, as Tautulli's webhook payload lacks critical data required to detect - this change. - -## Emby - -- Emby does **not send webhook events** for newly added items. - ~~[See feature request](https://emby.media/community/index.php?/topic/97889-new-content-notification-webhook/)~~ This - was implemented in version `4.7.9`, but still does **not include metadata**, making it ineffective. -- The Emby **webhook test event** previously contained no data. This appears to be **fixed in `4.9.0.37+`**. -- To verify if your Emby webhook setup is working, try playing or marking an item as played/unplayed, go to the history - page after a min or two and check if the changes are reflected in the database. - -## Jellyfin - -- If no user ID is selected in the plugin, the `itemAdd` event will be sent **without user data**, which will cause a - failure if `webhook.match.user` is enabled. -- Occasionally, Jellyfin will fire `itemAdd` events that **without it being matched**. -- Even if a user ID is selected, Jellyfin may **still send events without user data**. -- Items may be marked as **unplayed** if the following setting is enabled **Libraries > Display > Date added behavior - for new content: `Use date scanned into library`** This often happens when media files are replaced or updated. - ---- - -# Sometimes newly added episodes or movies don't reach the webhook endpoint? - -As noted in the webhook limitations section, some media backends do not reliably send webhook events for newly added -content. To address this, you should enable **import/export tasks** to complement webhook functionality. - -Simply, visit the `Tasks` page and enable the `import` and `export` task. --- @@ -859,8 +536,7 @@ redis://172.23.1.10:6379?password=my_secret_password&db=8 ``` > [!NOTE] - -* Only **Redis** and **API-compatible alternatives** are supported. +> Only **Redis** and **API-compatible alternatives** are supported. After updating the environment variables, **restart the container** to apply the changes. diff --git a/README.md b/README.md index f01c1cb3..1d437ac7 100644 --- a/README.md +++ b/README.md @@ -7,68 +7,50 @@ This tool primary goal is to sync your backends play state without relying on third party services, out of the box, this tool support `Jellyfin`, `Plex` and `Emby` media servers. -## Updates +# Updates -## 2025-04-06 +### 2025-04-06 -We have recently re-worked how the `backend:create` command works, and we no longer generate random name for invalid -backends names or usernames. We do a normalization step to make sure the name is valid. This should help with the -confusion of having random names. This means if you re-run the `backend:create` you most likely will get a different -name than before. So, we suggest to re-run the command with `--re-create` flag. This flag will delete the current -sub-users, and regenerate updated config files. +We have recently re-worked how the `backend:create` command works, and we no longer generate random name for invalid backends names or usernames. We do a normalization step to make sure the name is valid. This should help with the confusion of having random names. This means if you re-run the `backend:create` you most likely will get a different name than before. So, we suggest to re-run the command with `--re-create` flag. This flag will delete the current sub-users, and regenerate updated config files. -We have also added new guard for the command, so if you already generated your sub-users, re-running the command will -show you a warning message and exit without doing anything. to run the command again either you need to use -`--re-create`or `--run` flag. The `--run` flag will run the command without deleting the current sub-users. - -We have also updated `mapper.yaml` file format to v1.5, please take moment to read the -related the [FAQ](FAQ.md#whats-the-schema-for-the-mapperyaml-file) -about it. The new format has added support for renaming the usernames, and the new spec is more versatile which should -help us if we need to update something or add new features in the future. +We have also added new guard for the command, so if you already generated your sub-users, re-running the command will show you a warning message and exit without doing anything. to run the command again either you need to use `--re-create`or `--run` flag. The `--run` flag will run the command without deleting the current sub-users. ### 2025-03-13 -We have recently added support for plex webhooks via tautulli which you can use if you don't have PlexPass. This should -help -close the gap with other media servers. +We have recently added support for plex webhooks via tautulli which you can use if you don't have PlexPass. This should help close the gap with other media servers. ### 2025-02-19 -We have introduced new experimental feature to allow syncing watch progress for played items. This feature is still in -early stages, and might not work as expected. and there are probably still many bugs that we need to fix. Please report -any issues you might face. +We have introduced new experimental feature to allow syncing watch progress for played items. This feature is still in early stages, and might not work as expected. and there are probably still many bugs that we need to fix. Please report any issues you might face. -The feature is disabled by default, to enable it you need to run add this environment variable `WS_PROGRESS_THRESHOLD` -with seconds as value, the minimum value is `180` seconds. `0` seconds means it's disabled. We think reasonable value is -`86400` or more this number is about 1day. +The feature is disabled by default, to enable it you need to run add this environment variable `WS_PROGRESS_THRESHOLD` with seconds as value, the minimum value is `180` seconds. `0` seconds means it's disabled. We think reasonable value is `86400` or more this number is about 1day. -We are still not keen on this feature, and it might be removed in future releases if we aren't able to deal with the -issues we are facing. +We are still not keen on this feature, and it might be removed in future releases if we aren't able to deal with the issues we are facing. --- Refer to [NEWS](NEWS.md) for old updates. # Features -* WebUI. -* Sync backends play state (from many to many). +* Management via WebUI. +* Sync backends play state (`Many-to-Many` or `One-Way`). * Backup your backends play state into `portable` format. -* Receive Webhook events from media backends. +* Receive webhook events from media backends. * Find `un-matched` or `mis-matched` items. -* Search your backend for `title` or `item id`. -* Display and filter your play state. Can be exported as `yaml` or `json`. -* Check if your media servers reporting same data via the parity command. -* Sync your watch progress via webhooks or scheduled tasks. +* Search your backend metadata. +* Check if your media servers reporting same data via the parity checks. +* Sync your watch progress/play state via webhooks or scheduled tasks. * Check if your media backends have stale references to old files. -If you like my work, you might also like my other project [YTPTube](https://github.com/arabcoders/ytptube), which is -simple and to the point yt-dlp frontend to help download content from all supported sites by yt-dlp. - ----- +If you like my work, you might also like my other project [YTPTube](https://github.com/arabcoders/ytptube), which is simple and to the point yt-dlp frontend to help download content from all supported sites by yt-dlp. # Install -create your `compose.yaml` with the following content: +First, start by creating a directory to store the data, to follow along with this setup, create directory called `data` at your working directory. Then proceed to use your preferred method to install the tool. + +### Via compose file. + +create your `compose.yaml` next to the `data` directory, and add the following content to it. ```yaml services: @@ -79,186 +61,89 @@ services: container_name: watchstate restart: unless-stopped ports: - - "8080:8080" # The port which will serve WebUI + API + Webhooks + - "8080:8080" # The port which the webui will be available on. volumes: - ./data:/config:rw # mount current directory to container /config directory. ``` -Create directory called `data` next to the `compose.yaml` file. After creating your docker compose file, start -the container. +Next, to run the container, use the following command ```bash -$ mkdir -p ./data && docker-compose pull && docker-compose up -d +$ docker compose up -d +``` + +### Via docker command. + +```bash +$ docker run -d --rm --user "${UID:-1000}:${GID:-1000}" --name watchstate --restart unless-stopped -p 8080:8080 -v ./data:/config:rw ghcr.io/arabcoders/watchstate:latest ``` > [!IMPORTANT] -> It's really important to match the `user:` to the owner of the `data` directory, the container is rootless, as such -> it will crash if it's unable to write to the data directory. It's really not recommended to run containers as root, -> but if you fail to run the container you can try setting the `user: "0:0"` if that works it means you have permissions -> issues. refer to [FAQ](FAQ.md) to troubleshoot the problem. +> It's really important to match the `user:`, `--user` to the owner of the `data` directory, the container is rootless, as such it will crash if it's unable to write to the data directory. +> +> It's really not recommended to run containers as root, but if you fail to run the container you can try setting the `user: "0:0"` or `--user '0:0'` if that works it means you have permissions issues. refer to [FAQ](FAQ.md) to troubleshoot the problem. -> [!NOTE] -> For `Unraid` users You can install the `Community Applications` plugin, and search for `watchstate` it comes -> preconfigured. Otherwise, to manually install it, you need to add value to the `Extra Parameters` section in advanced -> tab/view. add the following value `--user 99:100`. This has to happen before you start the container, otherwise it -> will -> have the old user id, and you then have to run the following command from -> terminal `chown -R 99:100 /mnt/user/appdata/watchstate`. +### Unraid users -> [!NOTE] -> To use this container with `podman` set `compose.yaml` `user` to `0:0`. it will appear to be working as root -> inside the container, but it will be mapped to the user in which the command was run under. +For `Unraid` users You can install the `Community Applications` plugin, and search for **watchstate** it comes preconfigured. Otherwise, to manually install it, you need to add value to the `Extra Parameters` section in advanced tab/view. add the following value `--user 99:100`. + +This has to happen before you start the container, otherwise it will have the old user id, and + you then have to run the following command from terminal `chown -R 99:100 /mnt/user/appdata/watchstate`. -# Management via WebUI +### Podman instead of docker + +To use this container with `podman` set `compose.yaml` `user` to `0:0`. it will appear to be working as root inside the container, but it will be mapped to the user in which the command was run under. + +# Management After starting the container, you can access the WebUI by visiting `http://localhost:8080` in your browser. -At the start you won't see anything as the `WebUI` is decoupled from the WatchState and need to be configured to be able -to access the API. -In the top right corner, you will see a cogwheel icon, click on it and then Configure the connection settings. +At the start you won't see anything as the `WebUI` is decoupled from the WatchState and need to be configured to be able to access the API. In the top right corner, you will see a cogwheel icon, click on it and then Configure the connection settings. ![Connection settings](screenshots/api_settings.png) -As shown in the screenshot, to get your `API Token`, run the following command +As shown in the screenshot, to get your `API Token`, you can do via two methods + +### Method 1 + +view the contents of the `./data/config/.env` file, and copy the contents after `WS_API_KEY=` variable. + +### Method 2 + +From the host machine, you can run the following command ```bash -$ docker exec -ti watchstate console system:apikey +# change docker to podman if you are using podman +$ docker exec watchstate console system:apikey ``` -Copy the random string in dark yellow, into the `API Token` field Make sure to set the `API URL` or click -the `current page URL` link. If everything is set, then the Status field will turn -green. and `Status: OK` will be shown, and the reset of the navbar will show up. Which hopefully means everything is ok. +Insert the `API key` into the `API Token` field and make sure to set the `API URL` or click the `current page URL` link. If everything is ok, the reset of the navbar will show up. -To add a backend, click on the `Backends` link in the navbar, then `+` button. as showing in the following screenshot +To add your backends, please click on the help button in the top right corner, and choose which method you want [one-way](guides/one-way-sync.md) or [two-way](guides/two-way-sync.md) sync. and follow the instructions. -![Add backend](screenshots/add_backend.png) +### Supported import method -Fill the required information, if you get a green notification, then the backend is added successfully. If you get a -red/yellow notification, Then most likely incorrect information was provided. -You can check the message in the notification itself to know what went wrong. Or check the logs page, Most likely an -error has been logged to a file named `app.YYYYMMDD.log`. +Currently, the tool supports three methods to import data from backends. -If everything went ok, you should see the backend shows up in the same page. You can then go to the Tasks page and click -on `Queue Task`, for first time import we recommend letting -the task run in the background, as it might take a while to import all the data. - -Once you have done all for your backends, You should go back again to `Tasks` page and enable the `Import` and `Export` -tasks. This will make sure your data is always in sync. -To enable/disable the task, simply click on the slider next to the task name if it's green then it's enabled, if it's -gray then it's disabled. - -Once that is done, you can let the tool do its job, and you can start using the tool to track your play state. - -# Management via CLI. - -# Adding backend - -After starting the container you should start adding your backends and to do so run the following command: +- **Scheduled Tasks**. + - `A scheduled job that pull data from backends on a schedule.` +- **On demand**. + - `Pull data from backends on demand. By running the import task manually.` +- **Webhooks**. + - `Receive events from backends and update the database accordingly.` > [!NOTE] -> to get your plex token, please -> visit [this plex page](https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/) to -> know how to extract your plex token. For jellyfin & emby. Go to Dashboard > Advanced > API keys > then create new API -> keys. - -```bash -$ docker exec -ti watchstate console config:add -``` - -This command is interactive and will ask you for some questions to add your backend. - -# Managing backend - -To edit backend settings run - -```bash -$ docker exec -ti watchstate console config:manage -s backend_name -``` - -# Importing play state. - -What does `Import` or what does the command `state:import` means in context of watchstate? - -Import means, pulling data from the backends into the database while attempting to normalize the state. - -To import your current play state from backends that have import enabled, run the following command: - -```bash -$ docker exec -ti watchstate console state:import -v -``` - -This command will pull your play state from all your backends. To import from specific backends use -the `[-s, --select-backend]` flag. For example, - -```bash -$ docker exec -ti watchstate console state:import -v -s home_plex -s home_jellyfin -``` - -> [!NOTE] -> Now that you have imported your current play state enable the import task by using the following command - -```bash -$ docker exec -ti watchstate console system:env -k WS_CRON_IMPORT -e true -``` - -### Supported import methods - -Out of the box, we support the following import methods: - -* Scheduled Tasks. `Cron jobs that pull data from backends on a schedule.` -* On demand. `Pull data from backends on demand. By running the state:import command manually` -* Webhooks. `Receive events from backends and update the database accordingly.` - -> [!NOTE] -> Even if all your backends support webhooks, you should keep import task enabled. This help keep healthy relationship. -> and pick up any missed events. - ---- - -# Exporting play state - -What does `export` or what does the command `state:export` means in context of watchstate? - -Export means, sending data back to backends, while trying to minimize the network traffic. To export your current play -state to backends that have export enabled, run the following command: - -```bash -$ docker exec -ti watchstate console state:export -v -``` - -This command will export your current play state to all of your export enabled backends. To export to -specific backends use the `[-s, --select-backend]`flag. For example, - -```bash -$ docker exec -ti watchstate console state:export -v -s home_plex -s home_jellyfin -``` - -> [!NOTE] -> Now that you have exported your current play state, enable the export task by using the following command - -```bash -$ docker exec -ti watchstate console system:env -k WS_CRON_EXPORT -e true -``` - ---- +> Even if all your backends support webhooks, you should keep import task enabled. This help keep healthy relationship and pick up any missed events. For more information please check the FAQ related to webhooks limitations. # FAQ -Take look at this [frequently asked questions](FAQ.md) page. to know more about this tool and how to enable webhook -support and answers to many questions. - ---- +Take look at this [frequently asked questions](FAQ.md) page, or the [guides](guides/) for more in-depth guides on how to setup things. # Social channels -If you have short or quick questions, or just want to chat with other users, feel free to join -my [discord server](https://discord.gg/haUXHJyj6Y). -keep in mind it's solo project, as such it might take me a bit of time to reply to questions. - ---- +If you have short or quick questions, or just want to chat with other users, feel free to join this [discord server](https://discord.gg/haUXHJyj6Y), keep in mind it's solo project, as such it might take me a bit of time to reply to questions, I operate in `UTC+3` timezone. # Donate If you feel like donating and appreciate my work, you can do so by donating to children charity. For -example [Make-A-Wish](https://worldwish.org). -I Personally don't need the money, but I do appreciate the gesture. +example the [International Make-A-Wish foundation](https://worldwish.org). diff --git a/frontend/components/BackendAdd.vue b/frontend/components/BackendAdd.vue index 28129ff5..b789aa03 100644 --- a/frontend/components/BackendAdd.vue +++ b/frontend/components/BackendAdd.vue @@ -1,24 +1,25 @@