CLI based tool to sync watch state between different media servers.
Ever wanted to sync your watch state without having to rely on 3rd party service like trakt.tv? then this tool is for you. I had multiple problems with Plex trakt.tv plugin which led to my account being banned at trakt.tv, and on top of that the plugin no longer supported. And I like to keep my own data locally if possible.
- Plex
- Emby
- Jellyfin
create your docker-compose.yaml
file
version: '3.3'
services:
watchstate:
image: arabcoders/watchstate:latest
container_name: watchstate
restart: unless-stopped
environment:
# For more ENV variables please read at the bottom of README.md
WS_UID: ${UID:-1000} # Set container operation user id.
WS_GID: ${GID:-1000} # Set container operation group id.
ports:
- "8081:80" # webhook listener port
volumes:
- ${PWD}/:/config:rw # mount current directory to container /config directory.
After creating your docker-compose file, start the container.
$ docker-compose up -d
Please run the following command to see all available commands you can also run help on each command to get more info.
# Show all commands.
$ docker exec -ti watchstate console list
# Show help document for each command.
$ docker exec -ti watchstate console help state:import
After starting the container, you have to add your media servers, to do so run the following command
$ docker exec -ti watchstate console servers:manage --add -- [SERVER_NAME]
This command will ask you for some questions to add your servers, you can run the command as many times as you want, if
you want to edit the config again or if you made mistake just run the same command without --add
flag.
After adding your servers, You should import your current watch state by running the following command.
$ docker exec -ti watchstate console state:import -vvrm
now that you have imported your watch state, you can stop manually running the command again. and rely on the webhooks to update the watch state. To start receiving webhook events from servers you need to do few more steps.
To see the server specific api key run the following command
$ docker exec -ti watchstate console servers:view --servers-filter [SERVER_NAME] -- webhook.token
If you see 'Not configured, or invalid key.' or empty value. run the following command
$ docker exec -ti watchstate console servers:edit --regenerate-api-key -- [SERVER_NAME]
If you have multiple plex servers and use the same plex account for all of them, you have to unify the API key, by running the following command
$ docker exec -ti watchstate console servers:unify plex
Plex global webhook API key is: [random_string]
The reason is due to the way plex handle webhooks, And to know which webhook request belong to which server we have to identify the servers, The unify command will do the necessary adjustments to handle multi plex server setup. for more information run.
$ docker exec -ti watchstate console help servers:unify
This command is not limited to plex, you can unify API key for all supported backend servers.
If you don't want to use webhooks and want to rely only on scheduled task for importing, then set the value
of WS_CRON_IMPORT
to 1
. By default, we run the import command every hour. However, you can change the scheduled task
timer by adding another variable WS_CRON_IMPORT_AT
and set it value to valid cron expression. for
example, 0 */2 * * *
it will run every two hours instead of 1 hour. beware, this operation is somewhat costly as it's
pulls the entire server library.
You should still have WS_CRON_IMPORT
enabled as sometimes plex does not really report new items, or report them in a
way that is not compatible with the way we handle webhooks events. running the import command regularly helps keep
healthy GUIDS <> serverInternalID mapping
relations.
To manually export your watch state back to servers you can run the following command
$ docker exec -ti watchstate console state:export --mapper-preload -vvr
to sync specific server/s, use the --servers-filter
which accept comma seperated list of server names.
$ docker exec -ti watchstate console state:export -vvr --mapper-preload --servers-filter 'server1,server2'
To enable the export scheduled task set the value of WS_CRON_EXPORT
to 1
. By default, we run export every 90
minutes. However, you can change the schedule by adding another variable called WS_CRON_EXPORT_AT
and set its value to
valid cron expression. for example, 0 */3 * * *
it will run every three hours instead of 90 minutes.
By default, the official container includes a small http server exposed at port 80
, we officially don't support HTTPS
inside the container for the HTTP server. However, for the adventurous people we expose port 443 as well, as such you
can customize the Caddyfile to support SSL. and do the necessary adjustments. However, do not expect us to help with it.
server {
server_name watchstate.domain.example;
location / {
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Protocol $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_pass http://localhost:8081/;
}
}
to your server the url will be dependent on how you expose the server, but typically it will be like this:
Via reverse proxy : https://watchstate.domain.example/?apikey=[WEBHOOK_TOKEN]
.
Directly to container: https://localhost:8081/?apikey=[WEBHOOK_TOKEN]
If your server support sending headers then omit the query parameter '?apikey=[WEBHOOK_TOKEN]', and add new this header
X-apikey: [WEBHOOK_TOKEN]
it's more secure that way.
Should match the server specific webhook.token
value. in server.yaml
. if the key does not exist please refer to
the steps described at Steps to enable webhook servers.
go to your jellyfin dashboard > plugins > Catalog > install: Notifications > Webhook, restart your jellyfin. After that
go back again to dashboard > plugins > webhook. Add A Add Generic Destination
,
Choose whatever name you want.
http://localhost:8081
Select the following events
- Item Added
- User Data Saved
- Playback Start
- Playback Stop
- Movies
- Episodes
Toggle this checkbox.
Key: X-apikey
Value: [YOUR_API_KEY]
Click save
Go to your Manage Emby Server > Server > Webhooks > (Click Add Webhook)
http://localhost:8081/?apikey=[YOUR_API_KEY]
Select the following events
- Playback events
- User events
Click Add Webhook
Go to your plex WebUI > Settings > Your Account > Webhooks > (Click ADD WEBHOOK)
http://localhost:8081/?apikey=[YOUR_API_KEY]
Click Save Changes
Does not send webhooks events for "marked as watched/unwatched", or you added more than 1 item at time i.e. folder import.
If you have multiuser setup, please will still report the admin account user_id as 1 even though when you get the list
of users ids it shows completely different user ID, so when you initially set up your server for multiuser, select your
admin account and after finishing you have to set the value manually to 1
. to do so please do the following
$ docker exec -ti watchstate console servers:edit --key user --set 1 -- [SERVER_NAME]
This only for the main admin account, other managed/home/external users, you should leave the user as it is reported by plex.
Emby does not send webhooks events for newly added items.
None that we are aware of.
- (string)
WS_DATA_PATH
Where key data stored (config|db). - (string)
WS_TMP_DIR
Where temp data stored. (logs|cache). Defaults toWS_DATA_PATH
if not set. - (string)
WS_STORAGE_PDO_DSN
PDO Data source Name, if you want to change from sqlite. - (string)
WS_STORAGE_PDO_USERNAME
PDO username - (string)
WS_STORAGE_PDO_PASSWORD
PDO password - (string)
WS_TZ
Set timezone for example,UTC
- (bool)
WS_WEBHOOK_DEBUG
enable debug mode for webhook events. - (bool)
WS_REQUEST_DEBUG
enable debug mode for pre webhook request. - (integer)
WS_WEBHOOK_TOKEN_LENGTH
how many bits for the webhook api key generator. - (bool)
WS_LOGGER_STDERR_ENABLED
enable stderr output logging. - (string)
WS_LOGGER_STDERR_LEVEL
level to log (DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY, 100|200|250|300|400|500|550|600). - (bool)
WS_LOGGER_FILE_ENABLE
enable file logging. - (string)
WS_LOGGER_FILE_LEVEL
level to log (DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY, 100|200|250|300|400|500|550|600). - (string)
WS_LOGGER_FILE
full path for log file. By default, it's stored at$(WS_TMP_DIR)/logs/app.log
- (bool)
WS_LOGGER_SYSLOG_ENABLED
enable syslog logger. - (int)
WS_LOGGER_SYSLOG_FACILITY
syslog logging facility - (string)
WS_LOGGER_SYSLOG_LEVEL
level to log (DEBUG|INFO|NOTICE|WARNING|ERROR|CRITICAL|ALERT|EMERGENCY, 100|200|250|300|400|500|550|600). - (string)
WS_LOGGER_SYSLOG_NAME
What name should logs be under. - (int)
WS_CRON_IMPORT
enable import scheduled task. - (string)
WS_CRON_IMPORT_AT
cron expression timer. - (int)
WS_CRON_EXPORT
enable export scheduled task. - (string)
WS_CRON_EXPORT_AT
cron expression timer. - (int)
WS_CRON_PUSH
enable push scheduled task. - (string)
WS_CRON_PUSH_AT
cron expression timer. - (int)
WS_CRON_CACHE
enable caching of GUIDs relations. - (string)
WS_CRON_CACHE_AT
cron expression timer. - (string)
WS_LOGS_PRUNE_AFTER
Delete logs older than specified time, set todisable
to disable logs pruning. it follows php strtotime function rules.
- (int)
WS_NO_CHOWN
do not change ownership of/config
inside container. - (int)
WS_DISABLE_HTTP
disable included http server. - (int)
WS_UID
Container user ID - (int)
WS_GID
Container group ID
For some common questions, Please take look at this frequently asked questions page.