Plugins - nosmokingbandit/watcher GitHub Wiki

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.

What

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

Added Movie

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"}

Snatched Release

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

Postprocessing Finished

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 IMDB ID tt0063350
3 Resolution 1080P
4 Rated PG-13
5 Original File /home/user/downloads/movie.mkv
6 New File /home/user/movies/movie.mkv
7 Download ID 123456789
8 Finished Date 2017-01-01
9 Quality Profile Bluray-1080P only
10 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.

Where

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/

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.

How

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.

Logging

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.

Exit status

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

Config

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.

Config files must include only a plain-text JSON object.

{
  "host": "localhost", 
  "port": "9090", 
  "name": "Watcher"
}

The user will be able to modify any of these values in Settings > Plugins.

The config will then be passed to the plugin as the final argument. All key:value pairs in the config will be represented as string, regardless of content. It is the responsibility of the plugin author to parse integers or split lists according to their needs.

Since the JSON object must be passed to the script as a string you will need to load it in your script. Once loaded you can treat it as a normal python dictionary. Check the example section for clarification.

Example

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)
⚠️ **GitHub.com Fallback** ⚠️