-
Notifications
You must be signed in to change notification settings - Fork 60
Plugins
Watcher supplies a simple method to run external scripts, or plugins
, when triggered by various events.
Plugins can be found and submitted in the watcherplugins repo.
Watcher is able to execute a python script when triggered by certain events. The script will be passed several arguments which will vary depending on the calling event. Plugins will be executed using the same python binary that executed Watcher. All arguments are passed as strings.
When a movie is added via user-entry, automatic watchlist, or api call.
The script will receive the following command line arguments:
Position | Argument | Example |
---|---|---|
0 | Script | /opt/watcher/plugins/added/added_movie.py |
1 | Title | Night of the Living Dead |
2 | Year | 1968 |
3 | IMDB ID | tt0063350 |
4 | Quality Profile | Default |
5 | Config | {"key1": "value1", "key2": "value2"} |
When a release is snatched and sent to a download client.
The script will receive the following command line arguments:
Position | Argument | Example |
---|---|---|
0 | Script | /opt/watcher/plugins/added/snatched_movie.py |
1 | Title | Night of the Living Dead |
2 | Year | 1968 |
3 | IMDB ID | tt0063350 |
4 | Resolution | 1080P |
5 | Type | nzb |
6 | Download client | NZBGet |
7 | Download ID | 12 |
8 | Indexer | www.indexer.com |
9 | Info Link | www.indexer.com/details/123456789 |
10 | Config | {"key1": "value1", "key2": "value2"} |
Note that Info Link will be url encoded.
www.indexer.com%2Fdetails%2F123456789
After all postprocessing steps have completed.
The script will receive the following command line arguments:
Position | Argument | Example |
---|---|---|
0 | Script | /opt/watcher/plugins/added/finished_movie.py |
1 | Title | Night of the Living Dead |
2 | Year | 1968 |
3 | IMDB ID | tt0063350 |
4 | Resolution | 1080P |
5 | Rated | PG-13 |
6 | Original File | /home/user/downloads/movie.mkv |
7 | New File | /home/user/movies/movie.mkv |
8 | Download ID | 123456789 |
9 | Finished Date | 2017-01-01 |
10 | Quality Profile | Bluray-1080P only |
11 | Config | {"key1": "value1", "key2": "value2"} |
Note that Original File and New File may be None
when moving or renaming is disabled.
When processing downloads that were not snatched by Watcher, several other arguments may also be None
.
Each script must be placed in the appropriate directory for its calling event.
Event | Directory |
---|---|
Added Movie | watcher/plugins/added/ |
Snatched Release | watcher/plugins/snatched/ |
Postprocessing Finished | watcher/plugins/finished/ |
A plugin should be directly placed in under the folder. So if you want Plex API Scan
from the watcherplugins repository, you end up with a structure like this:
watcher/plugins/finished/Plex API Scan.conf
watcher/plugins/finished/Plex API Scan.py
Plugins may then be enabled in Settings > Plugins. Enable the plugin by checking the box next to its name. Set the order of plugin execution by dragging the handle next to the checkbox.
Since the plugins are executed using the same python binary you used to start Watcher, plugins must be compatible with that python version as well. There are minimal differences between python 2.7.9 and newer, so this should be of little concern.
Plugins are executed sequentially in a separate thread as to not block the main application.
Watcher.py____<Watcher.py continues as normal>__
\__myplugin.py__myplugin2.py__
Always follow good practices and close all open file handlers and avoid any potential infinite loops.
Since the plugin will be executed in a separate thread, logging lines my be interrupted by other logging events.
Logging will begin with a line indicating execution. All lines printed by a plugin will be logged to the Watcher log file. Logging will finish with a line indicating the exit status.
INFO 2017-01-28 15:43:31,710 core.plugins.added: Executing plugin my_plugin.py.
INFO 2017-01-28 15:43:31,789 core.plugins.execute: writelog.py - This line was printed in the plugin.
INFO 2017-01-28 15:43:31,789 core.plugins.execute: writelog.py - Execution finished. Exit code 0.
A plugin's exit status will have no effect on subsequent plugin executions and should be used as means of troubleshooting and logging errors.
Any non-zero exit code will be logged.
For example:
import sys
sys.exit('Something when horribly wrong')
Will result in the following log entries:
INFO 2017-01-28 15:49:04,082 core.plugins.execute: my_broken_plugin.py - Something went horribly wrong
INFO 2017-01-28 15:49:04,082 core.plugins.execute: my_broken_plugin.py - Execution failed. Exit code 1
Important Note As of commit 46f8898d1462c89a18cd55b0a28122e9bd3120e1, config file construction has been changed. The instruction listed below is applicable to the newest version of the plugin interface. Any legacy plugin configuration files will be automatically converted when the user saves their config, but all fields will be represented as strings until changed in the config json.
User-editable configs can be provided with any plugin. Place a .conf
file in the plugin directory with the same name as your plugin file. So my_plugin.py
would use the config my_plugin.conf
. Watcher will automatically find config files and allow users to edit via the Web UI at watcher/settings/plugins
.
Config files must include only a plain-text JSON object. The first key:value pair should be "Config Version": 2
to indicate that this is not a legacy config file. Config entities are nested JSON objects as follows:
{
"Config Version": 2,
"url": {
"display": 0,
"type": "string",
"label": "Server URL",
"helptext": "Remote Server URL",
"value": "http://localhost"
},
"port": {
"display": 1,
"type": "int",
"label": "Port",
"helptext": "Remote Server Port",
"max": "",
"min": 0,
"value": 2113
},
"option": {
"display": 2,
"type": "bool",
"label": "Enable Option",
"helptext": "",
"value": false
}
}
The config file will be condensed and passed to the plugin as the final argument, formatted as follows:
{
"url": "http://localhost",
"port": 2113,
"option": False
}
The config will then be passed to the plugin as the final argument. The dictionary will be JSON-encoded and must be decoded in order to allow access in the plugin script. See the example script for clarification. Version 2 config files will pass values as their specified type
, unlike version 1 config files that pass all values as strings.
Config options have several key:val pairs:
type
(str) Required. The data type of the option. Must be one of string
, int
, or bool
.
display
(int) Optional. Order in which to show options in the WebUI. This is not requied but recommended for consistency.
label
(str) Optional. Label to show in the WebUI. bool
types will display this adjacent to the checkbox.
helptext
(str) Optional. Tooltip text to show when field is hovered over.
value
(str/int/bool): Optional. Type must match type
. Can be filled to provide a default value to the user.
min
& max
(int): Optional. Applies only to int
types. Restrict range of input.
A basic example is as follows:
# Added movie script template
import sys
script, title, year, imdbid, quality, config_json = sys.argv
print 'Executing plugin {} for {}'.format(script, title)
sys.exit(0)
This example shows how to load a config file.
# Added movie script template
import json
import sys
script, title, year, imdbid, quality, config_json = sys.argv
config = json.loads(config_json)
# type(config) is now <'dict'>
sys.exit(0)