Improve the documentations and guides

This commit is contained in:
arabcoders
2025-05-04 19:48:20 +03:00
parent 6f5509f827
commit a168912b71
8 changed files with 464 additions and 631 deletions

326
FAQ.md
View File

@@ -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.

251
README.md
View File

@@ -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`.
# Management via WebUI
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`.
### 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).

View File

@@ -1,24 +1,25 @@
<template>
<Message title="Important" message_class="has-background-warning-80 has-text-dark" icon="fas fa-info-circle">
<ul>
<li v-if="api_user === 'main'">
Support for sub users is in early stages. For more information please visit
<NuxtLink target="_blank" v-text="'Visit this link'"
to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#is-there-support-for-multi-user-setup"/>
to learn more. <b>DO NOT</b> add sub users backends directly. Use the create sub-users button after setting up
the main user.
</li>
<li>
If you are adding new backend that is fresh and doesn't have your current watch state, you should turn off
import and enable only metadata import at the start to prevent overriding your current play state.
<NuxtLink target="_blank" v-text="'Visit this link'"
to="https://github.com/arabcoders/watchstate/blob/master/FAQ.md#my-new-backend-overriding-my-old-backend-state--my-watch-state-is-not-correct"/>
to learn more.
import and enable only metadata import at the start to prevent overriding your current play state. Visit the
following guide
<NuxtLink to="/help/one-way-sync">
<span class="icon"><i class="fas fa-circle-question" /></span> One-way sync
</NuxtLink> to learn more.
</li>
<li v-if="api_user === 'main'">
Do not add sub-users backends manually, after finishing the main user backends setup. Visit
<NuxtLink target="_blank" to="/tools/sub_users">
<span class="icon"><i class="fas fa-tools" /></span> Tools >
<span class="icon"><i class="fas fa-users" /></span> Sub-users
</NuxtLink> page to create their own user and backends automatically.
</li>
</ul>
</Message>
<form id="backend_add_form" @submit.prevent="stage<4 ? changeStep() : addBackend()">
<form id="backend_add_form" @submit.prevent="stage < 4 ? changeStep() : addBackend()">
<div class="card">
<div class="card-header">
<p class="card-header-title">Add backend to '<u class="has-text-danger">{{ api_user }}</u>' user config.</p>
@@ -27,28 +28,28 @@
<div class="card-content">
<div class="field" v-if="error">
<Message title="Backend Error" id="backend_error" message_class="has-background-danger-80 has-text-dark"
icon="fas fa-exclamation-triangle" useClose @close="error=null">
icon="fas fa-exclamation-triangle" useClose @close="error = null">
<p>{{ error }}</p>
</Message>
</div>
<template v-if="stage>=0">
<template v-if="stage >= 0">
<div class="field">
<label class="label">Local User</label>
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select class="is-capitalized" disabled>
<option v-text="api_user"/>
<option v-text="api_user" />
</select>
</div>
<div class="icon is-left">
<i class="fas fa-users"></i>
</div>
<p class="help">
The local user which this backend will be associated with. You can change this user via the users icon
on top.
</p>
</div>
<p class="help">
The local user which this backend will be associated with. You can change this user via the
<span class="icon"><i class="fas fa-users" /></span> users icon on top right of the page.
</p>
</div>
<div class="field">
@@ -56,7 +57,7 @@
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select v-model="backend.type" class="is-capitalized" required :disabled="stage > 0">
<option v-for="type in supported" :key="'type-'+type" :value="type">
<option v-for="type in supported" :key="'type-' + type" :value="type">
{{ type }}
</option>
</select>
@@ -64,12 +65,8 @@
<div class="icon is-left">
<i class="fas fa-server"></i>
</div>
<p class="help">
The backend server type. The supported types are <code>{{
supported.map(v => ucFirst(v)).join(', ')
}}</code>.
</p>
</div>
<p class="help">The backend type.</p>
</div>
<div class="field">
@@ -110,11 +107,12 @@
</div>
</div>
<p class="help">
<template v-if="'plex'===backend.type">
<template v-if="'plex' === backend.type">
Enter the <code>X-Plex-Token</code>.
<NuxtLink target="_blank" to="https://support.plex.tv/articles/204059436"
v-text="'Visit This link'"/>
to learn how to get the token.
v-text="'Visit This link'" /> to learn how to get the token. <span
class="is-bold has-text-danger">If you plan to add sub-users, YOU MUST use admin level
token.</span>
</template>
<template v-else>
Generate a new API Key from <code>Dashboard > Settings > API Keys</code>.<br>
@@ -122,6 +120,8 @@
You can use <code>username:password</code> as API key and we will automatically generate limited
token if you are unable to generate API Key. This should be used as last resort. and it's mostly
untested. and things might not work as expected.
<span class="is-bold has-text-danger">If you plan to add sub-users, YOU MUST use API KEY and not
username:password.</span>
</template>
</p>
</div>
@@ -141,7 +141,7 @@
</template>
</template>
<template v-if="stage>=1">
<template v-if="stage >= 1">
<div class="field" v-if="'plex' !== backend.type">
<label class="label">URL</label>
<div class="control has-icons-left">
@@ -157,10 +157,10 @@
<label class="label">Plex Server URL</label>
<div class="control has-icons-left">
<div class="select is-fullwidth">
<select v-model="backend.url" class="is-capital" @change="stage=1; updateIdentifier()" required
<select v-model="backend.url" class="is-capital" @change="stage = 1; updateIdentifier()" required
:disabled="stage > 1">
<option value="" disabled>Select Server URL</option>
<option v-for="server in servers" :key="'server-'+server.uuid" :value="server.uri">
<option v-for="server in servers" :key="'server-' + server.uuid" :value="server.uri">
{{ server.name }} - {{ server.uri }}
</option>
</select>
@@ -171,14 +171,14 @@
</div>
<p class="help">
<NuxtLink @click="getServers" v-text="'Attempt to discover servers associated with the token.'"
v-if="stage<2"/>
v-if="stage < 2" />
Try to use non <code>.plex.direct</code> urls if possible, as they are often have problems working in
docker. If you use custom domain for your plex server and it's not showing in the list, you can add it
via Plex settings page. <code>Plex > Settings > Network > <strong>Custom server access
URLs:</strong></code>. For more information
<NuxtLink target="_blank"
to="https://support.plex.tv/articles/200430283-network/#Custom-server-access-URLs"
v-text="'Visit this link'"/>
v-text="'Visit this link'" />
.
</p>
</div>
@@ -193,7 +193,7 @@
<div class="select is-fullwidth">
<select v-model="backend.user" class="is-capitalized" :disabled="stage > 3">
<option value="" disabled>Select User</option>
<option v-for="user in users" :key="'uid-'+user.id" :value="user.id">
<option v-for="user in users" :key="'uid-' + user.id" :value="user.id">
{{ user.name }}
</option>
</select>
@@ -204,7 +204,7 @@
</div>
<p class="help">
Which user we should associate this backend with?
<NuxtLink @click="getUsers" v-text="'Retrieve User ids from backend.'" v-if="stage < 4"/>
<NuxtLink @click="getUsers" v-text="'Retrieve User ids from backend.'" v-if="stage < 4" />
</p>
</div>
</div>
@@ -320,7 +320,7 @@
<div class="card-footer">
<div class="card-footer-item" v-if="stage >= 1">
<button class="button is-fullwidth is-warning" type="button" @click="stage = stage-1">
<button class="button is-fullwidth is-warning" type="button" @click="stage = stage - 1">
<span class="icon"><i class="fas fa-arrow-left"></i></span>
<span>Previous Step</span>
</button>
@@ -346,8 +346,8 @@
<script setup>
import 'assets/css/bulma-switch.css'
import request from '~/utils/request'
import {awaitElement, explode, notification, ucFirst} from '~/utils/index'
import {useStorage} from "@vueuse/core";
import { awaitElement, explode, notification, ucFirst } from '~/utils/index'
import { useStorage } from "@vueuse/core";
const emit = defineEmits(['addBackend', 'backupData', 'forceExport', 'forceImport'])
@@ -767,5 +767,5 @@ const n_proxy = (type, title, message, e = null) => {
return notification(type, title, message)
}
watch(error, v => v ? awaitElement('#backend_error', (_, e) => e.scrollIntoView({behavior: 'smooth'})) : null)
watch(error, v => v ? awaitElement('#backend_error', (_, e) => e.scrollIntoView({ behavior: 'smooth' })) : null)
</script>

View File

@@ -66,7 +66,7 @@ onMounted(async () => {
</span>`)
text = text.replace(
/<!--\s*?i:([\w.-]+)\s*?-->/g,
/<!--\s*?i:([\w.-]+)\s*?-->/gi,
(_, list) => `<span class="icon"><i class="fas ${list.split('.').map(n => n.trim()).join(' ')}"></i></span>`
);

View File

@@ -49,18 +49,18 @@ const choices = [
number: 2,
title: 'Two-way sync',
text: 'This will allow all backends to sync with each other. I.e. plex to jellyfin, jellyfin to emby, emby to plex.',
url: null
url: '/help/two-way-sync',
},
{
number: 3,
title: 'Enable webhooks',
text: 'How to enable webhooks for your backends and for sub-users.',
url: null
url: '/help/webhooks',
},
{
number: 4,
title: 'Enable Sub-users',
text: 'Guide on how to enable sub-users.',
title: 'Creating Sub-users',
text: 'Guide on how to create and use sub-users.',
url: null
},
]

View File

@@ -1,41 +1,32 @@
# One-way sync
# One-Way Sync
One-way sync in WatchState is the ability to sync data from one backend to one or more backends without
effecting the data in the source backend. This is useful for scenarios where you want to keep a backup of your data or
sync data to a different environment without modifying the original data.
One-way sync in WatchState allows you to sync data from one backend to one or more other backends without affecting the data in the source backend. This is useful for scenarios where you want to back up your data or sync it to a different environment without modifying the original data.
# Use cases
# Use Cases
- you want to try jellyfin/plex/emby without altering your original data.
- you want to keep your jellyfin/emby/plex backend in sync with your preferred media backend.
- You made new media backend that doesn't have your play state yet, and you want to get it in sync with your main
backend.
- You want to try Jellyfin, Plex, or Emby without changing your original data.
- You want to keep your Jellyfin, Emby, or Plex backend in sync with your preferred media backend.
- Youve created a new media backend that doesnt have your play state yet, and you want to sync it with your main backend.
# How to set up one-way sync
# How to Set Up One-Way Sync
First, go to <!--i:fa-server--> Backends and click on <!-- i:fa-plus --> plus button. follow the interactive setup
guide, when you reach the step with `Export data to this backend?`, select `No`, this instruction applies to the main
backend only. as you don't want to alter its data. Keep `Import data from this backend?` to `Yes`, this will allow you
to import data from the backend.
### Adding your backend with has the most accurate data
First, go to <!--i:fa-server--> **Backends** and click on the <!--i:fa-plus--> **Add Backend** button. Follow the interactive setup guide. When you reach the step asking *`Export data to this backend?`*, disable it, this applies to your main backend as you dont want to alter its data. Keep *`Import data from this backend?`* enabled, which will allow you to import data from the backend.
Enable the *`Force one time import from this backend?`* option to import your current data into WatchState. This will bring all the data from the backend into WatchState.
> [!NOTE]
> It's recommended to keep `Create backup for this backend data?` to `Yes`, this will create a snapshot of your
> backend data, so that you may return to it should something happens. via <!--i:fa-tools--> Tools > <!--i:fa-sd-card-->
> Backups.
> Its recommended to keep *`Create backup for this backend data?`* enabled. This will create a
> snapshot of your backend data so you can restore it if anything goes wrong. You can access
> backups from <!--i:fa-tools--> **Tools** > <!--i:fa-sd-card--> **Backups**.
Enable `Force one time import from this backend?` option to get your current data into WatchState. This will import all
the data from the backend into WatchState.
# Importing the Data
# Importing the data
The import process may take some time, depending on the size of your library. To check the import status, go to the <!--i:fa-server--> **Backends** page and check the `last import date` for the backend. If it shows the current time, the import is finished.
It will take a while to import the data, depending on the size of your library.
To see the import status, you can go to <!--i:fa-globe--> Logs page, and check the `task.XXXXXXX.log` file. Or
via <!--i:fa-ellipsis-vertical--> More > <!--i:fa-calendar-alt--> Events page, and look for `run_console` event. We
recommend the tasks log as it's updated more frequently.
To know if it has finished, you can look for the following message:
Alternatively, visit the <!--i:fa-globe--> **Logs** page and look at the `task.YYYYMMDD.log` file, or go to the <!--i:fa-ellipsis-vertical--> **More** > <!--i:fa-calendar-alt--> **Events** page and look for the *`run_console`* events. The logs page is updated more frequently, so we recommend using it. Once the import is complete, you should see a message like this:
```text
[DD/MM HH:mm:SS]: NOTICE: SYSTEM: Completed 'XXX' requests in 'XXX.XXX's for 'main' backends.
@@ -49,71 +40,63 @@ To know if it has finished, you can look for the following message:
└─────────┴───────┴─────────┴────────┘
```
Or go to the <!--i:fa-server--> Backends page, and check the backend last import date, if it shows the current time it
means the import is done.
Once the import is done, go to the <!--i:fa-history--> **History** page to view the imported items. You can also navigate through the data to check its accuracy. Once satisfied, proceed to the next step.
After you see the message, you can go to <!--i:fa-history--> History page and see the import history. Navigate around to
check the data, once you are satisfied with the data, proceed to the next step.
# Adding Other Backends
# Adding the other backends
For each backend, follow these steps:
For each backend do these steps
### Step 1
## Step 1
Do exactly as you did for the main backend, but make the following changes:
Do exactly as you did for the main backend, But change the follow options:
- `Import data from this backend?`: No
- `Import metadata only from this backend?`: Yes
- `Export data to this backend?`: Yes
- `Force Export local data to this backend?`: depends
- If you only have 1 extra backend and already have imported your main backend data, then select `Yes`, and skip
step 2.
- Otherwise, keep it disabled and proceed to step 2.
- *`Import data from this backend?`*: No
- *`Import metadata only from this backend?`*: Yes
- *`Export data to this backend?`*: Yes
- *`Force Export local data to this backend?`*: This depends on your setup:
- If you have only one extra backend and have already imported your main backend data, select *Yes* and skip Step 2.
- If you have multiple backends, keep this option disabled and proceed to Step 2.
> [!IMPORTANT]
> It's really important that you select those options, otherwise you might inadvertently alter the data in the main
> backend.
>
> The option `Import metadata only from this backend?` only shows up when you select `No` for
`Import data from this backend?`.
> Selecting the correct options is crucial to avoid altering the data in the main backend. The
> *`Import metadata only from this backend?`* option will only appear if you have *`Import data from this backend?`* disabled.
## Step 2
### Step 2
You have two options,
You have two options:
### Option 1 (1 Extra backend)
##### Option 1 (One Extra Backend)
Go to <!--i:fa-server--> Backends page beneath the backend there is `Quick operations` list, select
`2. Force export local play state to this backend.`. Once you select the option, you will be redirected to
the <!--i:fa-ellipsis-vertical--> More > <!--i:fa-terminal--> Console page. Once you are there, the command will be
pre-filled for you, just hit enter to run it. Or click on <!--i:fa-terminal--> Execute button.
Go to the <!--i:fa-server--> **Backends** page, and under the backend, there is a `Quick operations` list. Select *`2. Force export local play state to this backend.`* Once selected, you'll be redirected to the <!--i:fa-ellipsis-vertical--> **More** > <!--i:fa-terminal--> **Console** page, where the command will be pre-filled for you. Just hit *Enter* or click the <!--i:fa-terminal--> **Execute** button.
### Option 2 (Multiple backends)
##### Option 2 (Multiple Backends)
If you have multiple backends that you want to sync, then skip step 2 for now, and add all your backends.
Once you are done, go to <!--i:fa-tools--> Tools > <!--i:fa-terminal--> Console page, and run the following command:
If you have multiple backends, skip Step 2 for now and add all your backends. Once all backends are added, go to the <!--i:fa-tools--> **Tools** > <!--i:fa-terminal--> **Console** page and run the following command:
```bash
state:export -fi -v -u main
```
This command will force export your locally stored play state to all export enabled backends. Once that is done proceed
to the next step.
This command will force export your locally stored play state to all **export enabled** backends. Once thats done, proceed to the next step.
## Enable Automation
# Enable Scheduled Tasks
If you are satisfied with the results, and you want to automate the process from now on, go to <!--i:fa-tasks--> Tasks
page. There are two tasks that you need to enable by clicking on the slider next to the name `Import` and `Export`.
If youre happy with the setup and want to automate the process, go to the <!--i:fa-tasks--> **Tasks** page. Enable the two tasks by toggling the sliders next to *`Import`* and *`Export`*.
To change how often the tasks runs, you have to go to <!--i:fa-cogs--> Env page, click on <!--i:fa-plus--> add button,
select the relevant environment variable. In this case, `WS_CRON_EXPORT_AT` and `WS_CRON_IMPORT_AT`. These two variables
accept valid CRON timer expressions. if you want to run the export task every 6 hours for example, you can set the
variable `WS_CRON_EXPORT_AT` value to `0 */6 * * *`. For more information about CRON expressions, check
out [crontab.guru](https://crontab.guru/).
To adjust how often the tasks run, go to the <!--i:fa-cogs--> **Env** page, click on the <!--i:fa-plus--> **Add** button, and select the relevant environment variables: `WS_CRON_EXPORT_AT` and `WS_CRON_IMPORT_AT`. These variables accept CRON timer expressions. For example, to run the export task every 6 hours, set `WS_CRON_EXPORT_AT` to `0 */6 * * *`.
For more info about CRON expressions, visit [crontab.guru](https://crontab.guru/).
## Enabling webhooks
> [!IMPORTANT]
> The `Import` task can be resource-intensive, especially for large libraries. It may take some time to complete and could use a lot of CPU power. Its recommended to run it a few times a day, with every 6 hours being a good starting point.
To know how to enable webhooks for faster sync operations, Please check out the webhooks guide.
# Enable Webhooks
For faster sync operations, you can enable webhooks. For more information, check out the [webhooks guide](/help/webhooks).
# Further Reading
If you are satisfied with the results and want to enable two-way sync, check out the [two-way sync guide](/help/two-way-sync).
# Troubleshooting
TBA

52
guides/two-way-sync.md Normal file
View File

@@ -0,0 +1,52 @@
# Two-Way Sync
Two-way sync in WatchState helps keep your `play progress` and `watch state` synchronized across multiple backends. Its called "many-to-many" sync, meaning you can sync data between several backends, and they all stay up to date with each other. This sync is powered by WatchState's `import` and `export` features.
# Use Cases
- If you watch a show on Plex and want to continue it on Jellyfin or Emby, two-way sync ensures your progress is saved.
- Keep your media backends synced so you always know where you left off.
# How Sync Works
WatchState first pulls the latest play and progress information from your backends, then stores it locally this is the `import` process. The system checks to ensure the data is up-to-date, and older data is saved as metadata without overriding the most current watch state.
On the export side, we compare the backend's last sync date with any local changes. From there, we create a list of items that need updating for each backend. If there are only a few changes, we trigger a quick sync operation `push mode`. If the changes are more extensive, we perform a full export, which compares all remote data with the local data. This full export only happens when there are many changes and/or metadata is missing from the backend, which is why it's crucial to keep the `Import data from this backend?` or, at a minimum, the `Import metadata only from this backend?` option enabled.
# Setting Up Two-Way Sync
To set up two-way sync, follow the steps below:
### Step 1: Setting Up The Backends.
First, make sure you have completed the [one-way sync guide](/help/one-way-sync) to get your backends synced.
### Step 2: Enable Sync Sliders.
Go to the <!--i:fa-server--> *Backends* page. Here, you'll see two sliders for each backend: `Import` and `Export`.
- The `Import` slider brings data from the backend into WatchState.
- The `Export` slider sends data from WatchState to the backend.
When you're sure the data looks correct, turn on the `Export` slider for your main backend and the `Import` slider for the others. This will keep your backends synced.
# Enable Scheduled Tasks
If everything looks good and you want WatchState to automatically sync your backends, do the following:
Go to the <!--i:fa-tasks--> **Tasks** page. Enable the two tasks by toggling the sliders next to `Import` and `Export`.
### Tuning The run schedule
To control how often these tasks run, go to the <!--i:fa-cogs--> **Env** page, click the <!--i:fa-plus--> **Add** button, and select the environment variables `WS_CRON_EXPORT_AT` and `WS_CRON_IMPORT_AT`. These variables use CRON timer expressions. For example, if you want the export task to run every 6 hours, set `WS_CRON_EXPORT_AT` to `0 */6 * * *`. For more help with CRON expressions, visit [crontab.guru](https://crontab.guru/).
> [!IMPORTANT]
> The `Import` task can be resource-intensive, especially for large libraries. It may take some time to complete and could use a lot of CPU power. Its recommended to run it a few times a day, with every 6 hours being a good starting point.
# Enable Webhooks
For even faster sync operations, you can enable webhooks. For more details, check out the [webhooks guide](/help/webhooks).
# Troubleshooting
TBA

237
guides/webhooks.md Normal file
View File

@@ -0,0 +1,237 @@
# Webhooks
Webhooks are a powerful and fast way to sync your backends nearly instantly. They are triggered by user actions, such as play, pause, stop, etc., on a backend. When an action is performed, the backend sends a webhook to the WatchState API, which processes the data, updates the system, and triggers events to the other backends.
Although webhooks are great for syncing data quickly, they should not be used as the sole method for synchronization. Webhooks are not 100% reliable—they may be missed or delayed. To ensure your data is always up-to-date, its recommended to use webhooks in combination with scheduled tasks.
## Getting started
Webhook URLs are **user and backend-specific**, think of them as **identification ID** for that user and backend. So, you need to obtain the webhook URL for each user and backend. Start first by adding your main user webhooks urls and repeat the process for each user that you want to enable webhooks for. By switching to that user via the <!--i:fa-users--> **users** icon on the top right corner of the page.
To easily obtain the correct URL, go to the <!--i:fa-server--> *Backends* page and click **Copy Webhook URL** next to the relevant backend.
# Adding Webhooks to Your Backends
### Emby (Emby Premiere Required)
1. Go to your Emby Server:
- **Old Emby Versions**: Go to *Server > Webhooks* and click *Add Webhook*.
- **New Emby Versions**: Go to *username Preferences > Notifications > + Add Notification > Webhooks*.
2. **Name**:
- You can name it `user@backend` or something that reflects the user it belongs to.
3. **Webhook/Notifications URL**:
- Copy the URL from the WatchState <!--i:fa-server--> *Backends* page and paste it here.
4. **Request Content Type (Emby v4.9+)**:
- Select `application/json`.
5. **Webhook Events (v4.7.9 or higher)**:
- New Media Added
- Playback
- Mark Played
- Mark Unplayed
For versions prior to v4.7.9:
- Playback events
- User events
6. **Limit User Events to**:
- Select your user.
7. **Limit Library Events to**:
- Select libraries you want to sync or leave it blank for all libraries.
Click *Add Webhook / Save*.
### Jellyfin (Free)
1. Go to your Jellyfin dashboard, then navigate to *Plugins > Catalog* and install *Notifications > Webhook*. Restart Jellyfin.
2. After the restart, go back to *Plugins > Webhook* and add `Add Generic Destination`.
3. **Webhook Name**:
- You can name it `user@backend` or something that reflects the user it belongs to.
4. **Webhook URL**:
- Copy the URL from the WatchState <!--i:fa-server--> *Backends* page and paste it here.
5. **Notification Type**:
- Item Added
- User Data Saved
- Playback Start
- Playback Stop
6. **User Filter**:
- Select your user.
7. **Item Type**:
- Select *Movies* and *Episodes*.
8. **Send All Properties**:
- Toggle this checkbox to send all properties.
Click *Save*.
### Plex (PlexPass Required)
1. Go to your Plex Web UI and navigate to *Settings > Your Account > Webhooks*. Click *Add Webhook*.
2. **Webhook URL**:
- Copy the URL from the WatchState <!--i:fa-server--> *Backends* page and paste it here.
Click *Save Changes*.
### Plex via Tautulli
1. Go to *Options > Notification Agents* and click *Add a new notification agent > Webhook*.
2. **Webhook URL**:
- Copy the URL from the WatchState <!--i:fa-server--> *Backends* page and paste it here.
3. **Webhook Method**:
- Select `PUT`.
4. **Description**:
- Use something like `Webhook for user XX for backend XX`.
5. **Triggers**:
Select the following events:
- Playback Start
- Playback Stop
- Playback Pause
- Playback Resume
- Watched
- Recently Added
6. **Data**:
- For each event, you will need to set the corresponding headers/data fields using the following format.
> [!IMPORTANT]
> Its important that you copy the headers and data as they are, without modifying them if you're unsure.
**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}"
}
}
```
Click *Save*.
# Media Backends Webhook Limitations
Here are some known limitations and issues when using webhooks with different media backends:
### Plex
- Plex doesn't send events for *marked as played/unplayed* actions.
- Webhook events may be **skipped** when multiple items are added at once.
- When items are marked as **unwatched**, Plex resets the date on the media object.
- If you share your Plex server with other users (e.g., home/managed users), you must enable **Webhook match user** to prevent their play state from affecting yours.
- If you use multiple Plex servers with the same PlexPass account, you must add each backend separately and enable both *Webhook Match User* and *Webhook Match Backend ID*. Plex webhooks are account-wide and not server or user specific.
### 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`.
- A workaround is to manually hardcode the `Account.user_id` for that specific user.
- **Marking items as unplayed** is not reliable, as Tautullis webhook payload lacks the data needed to detect this change.
### Emby
- Emby **does not send webhook events** for newly added items.
- This feature was implemented in version `4.7.9`, but it still **does not include metadata**, which makes it ineffective.
- The **webhook test event** previously contained no data, but this issue appears to be fixed in version `4.9.0.37+`.
- To verify if your Emby webhook setup works, try playing or marking an item as played/unplayed, and check if the changes appear in the database.
### Jellyfin
- If no user ID is selected in the plugin, the `itemAdd` event will be sent **without user data**, causing failures if `webhook.match.user` is enabled.
- Occasionally, Jellyfin will fire `itemAdd` events **without matching**.
- Even if a user ID is selected, Jellyfin may **still send events without user data**.
- Items may be marked as **unplayed** if the setting *Libraries > Display > Date Added Behavior for New Content: Use Date Scanned into Library* is enabled.
- This can happen when media files are replaced or updated.
# Sometimes Newly Added Content Does Not Show Up
As mentioned in the webhook limitations section, some media backends do not reliably send webhook events for newly added content. To address this, its recommended to enable **import/export tasks** to complement webhook functionality.
Simply go to the *Tasks* page and enable the *Import* and *Export* tasks.
# Troubleshooting
TBA