mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
Merge branch 'dev'
This commit is contained in:
commit
92f55c254c
25 changed files with 426 additions and 215 deletions
21
CHANGELOG.md
21
CHANGELOG.md
|
@ -1,5 +1,26 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.12 (2016-03-27)
|
||||||
|
|
||||||
|
* Fix: "Check GitHub for updates" not rescheduling when toggling setting.
|
||||||
|
* Fix: Bug where notifications would fail if metadata is not found.
|
||||||
|
* Fix: Bug where notifications would fail if unable to upload poster to Imgur.
|
||||||
|
* Fix: PlexPy will now start properly for different Python environment variables.
|
||||||
|
* New: Feature requests moved to FeatHub.
|
||||||
|
* New: Ability to specify a GitHub API token for updates (optional).
|
||||||
|
* New: Mask out sensitive information from the logs.
|
||||||
|
* New: New and updated Arnold quotes. (Thanks @Vilsol & @Chrisophogus)
|
||||||
|
* New: "First" and "Last" page buttons to datatables.
|
||||||
|
* New: Access log file from the "Help & Info" page.
|
||||||
|
* New: CherryPy environment options (for development). (Thanks @codedecay)
|
||||||
|
* New: PlexPy development environment (for development only).
|
||||||
|
* Change: Facebook posts with a posters now include a summary.
|
||||||
|
* Change: Facebook posts now use a default poster if the poster is not found or unable to upload to Imgur.
|
||||||
|
* Change: IFTTT events can be fromatted with the {action} name.
|
||||||
|
* Change: Logs now use ISO date format to avoid locale encoding errors. (Thanks @alshain)
|
||||||
|
* Remove: Non-functioning Plex notification agent.
|
||||||
|
|
||||||
|
|
||||||
## v1.3.11 (2016-03-15)
|
## v1.3.11 (2016-03-15)
|
||||||
|
|
||||||
* Fix: Typo preventing history logging for websockets.
|
* Fix: Typo preventing history logging for websockets.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# Contributing to PlexPy
|
# Contributing to PlexPy
|
||||||
|
|
||||||
## Issues
|
## Issues
|
||||||
In case you read this because you are posting an issue, please take a minute and conside the things below. The issue tracker is not a support forum. It is primarily intended to submit bugs, improvements or feature requests. However, we are glad to help you, and make sure the problem is not caused by PlexPy, but don't expect step-by-step answers.
|
In case you read this because you are posting an issue, please take a minute and conside the things below. The issue tracker is not a support forum. It is primarily intended to submit bugs. However, we are glad to help you, and make sure the problem is not caused by PlexPy, but don't expect step-by-step answers.
|
||||||
|
|
||||||
##### Many issues can simply be solved by:
|
##### Many issues can simply be solved by:
|
||||||
|
|
||||||
|
@ -35,11 +35,11 @@ In case you read this because you are posting an issue, please take a minute and
|
||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
1. Search for similar existing 'issues', feature requests can be recognized by the blue `enhancement` label.
|
Feature requests are handled on [FeatHub](http://feathub.com/drzoidberg33/plexpy).
|
||||||
2. If a similar request exists, post a comment (+1, or add a new idea to the existing request).
|
|
||||||
3. If no similar requests exist, you can create a new one.
|
1. Search the existing requests to see if your suggestion has already been submitted.
|
||||||
4. Provide a clear title to easily identify the feature request.
|
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
|
||||||
5. Tag your feature request with `[Feature Request]` so it can be identified easily.
|
3. If no similar requests exist, you can create a new one. Make sure to provide a clear title to easily identify the feature request.
|
||||||
|
|
||||||
## Pull Requests
|
## Pull Requests
|
||||||
If you think you can contribute code to the PlexPy repository, do not hesitate to submit a pull request.
|
If you think you can contribute code to the PlexPy repository, do not hesitate to submit a pull request.
|
||||||
|
|
49
PlexPy.py
49
PlexPy.py
|
@ -1,4 +1,9 @@
|
||||||
#!/usr/bin/env python
|
#!/bin/sh
|
||||||
|
''''which python >/dev/null 2>&1 && exec python "$0" "$@" # '''
|
||||||
|
''''which python2 >/dev/null 2>&1 && exec python2 "$0" "$@" # '''
|
||||||
|
''''which python2.7 >/dev/null 2>&1 && exec python2.7 "$0" "$@" # '''
|
||||||
|
''''exec echo "Error: Python not found!" # '''
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# This file is part of PlexPy.
|
# This file is part of PlexPy.
|
||||||
|
@ -76,11 +81,14 @@ def main():
|
||||||
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
'-d', '--daemon', action='store_true', help='Run as a daemon')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'-p', '--port', type=int, help='Force PlexPy to run on a specified port')
|
'-p', '--port', type=int, help='Force PlexPy to run on a specified port')
|
||||||
|
parser.add_argument(
|
||||||
|
'--dev', action='store_true', help='Start PlexPy in the development environment')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--datadir', help='Specify a directory where to store your data files')
|
'--datadir', help='Specify a directory where to store your data files')
|
||||||
parser.add_argument('--config', help='Specify a config file to use')
|
parser.add_argument(
|
||||||
parser.add_argument('--nolaunch', action='store_true',
|
'--config', help='Specify a config file to use')
|
||||||
help='Prevent browser from launching on startup')
|
parser.add_argument(
|
||||||
|
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
'--pidfile', help='Create a pid file (only relevant when running as a daemon)')
|
||||||
|
|
||||||
|
@ -95,6 +103,10 @@ def main():
|
||||||
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
|
logger.initLogger(console=not plexpy.QUIET, log_dir=False,
|
||||||
verbose=plexpy.VERBOSE)
|
verbose=plexpy.VERBOSE)
|
||||||
|
|
||||||
|
if args.dev:
|
||||||
|
plexpy.DEV = True
|
||||||
|
logger.debug(u"PlexPy is running in the dev environment.")
|
||||||
|
|
||||||
if args.daemon:
|
if args.daemon:
|
||||||
if sys.platform == 'win32':
|
if sys.platform == 'win32':
|
||||||
sys.stderr.write(
|
sys.stderr.write(
|
||||||
|
@ -159,6 +171,19 @@ def main():
|
||||||
# Read config and start logging
|
# Read config and start logging
|
||||||
plexpy.initialize(config_file)
|
plexpy.initialize(config_file)
|
||||||
|
|
||||||
|
# Start the background threads
|
||||||
|
plexpy.start()
|
||||||
|
|
||||||
|
# Open connection for websocket
|
||||||
|
if plexpy.CONFIG.MONITORING_USE_WEBSOCKET:
|
||||||
|
try:
|
||||||
|
web_socket.start_thread()
|
||||||
|
except:
|
||||||
|
logger.warn(u"Websocket :: Unable to open connection.")
|
||||||
|
# Fallback to polling
|
||||||
|
plexpy.POLLING_FAILOVER = True
|
||||||
|
plexpy.initialize_scheduler()
|
||||||
|
|
||||||
# Force the http port if neccessary
|
# Force the http port if neccessary
|
||||||
if args.port:
|
if args.port:
|
||||||
http_port = args.port
|
http_port = args.port
|
||||||
|
@ -181,6 +206,7 @@ def main():
|
||||||
'http_port': http_port,
|
'http_port': http_port,
|
||||||
'http_host': plexpy.CONFIG.HTTP_HOST,
|
'http_host': plexpy.CONFIG.HTTP_HOST,
|
||||||
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
'http_root': plexpy.CONFIG.HTTP_ROOT,
|
||||||
|
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
|
||||||
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
|
||||||
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
|
||||||
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
'https_cert': plexpy.CONFIG.HTTPS_CERT,
|
||||||
|
@ -190,21 +216,8 @@ def main():
|
||||||
}
|
}
|
||||||
webstart.initialize(web_config)
|
webstart.initialize(web_config)
|
||||||
|
|
||||||
# Start the background threads
|
|
||||||
plexpy.start()
|
|
||||||
|
|
||||||
# Open connection for websocket
|
|
||||||
if plexpy.CONFIG.MONITORING_USE_WEBSOCKET:
|
|
||||||
try:
|
|
||||||
web_socket.start_thread()
|
|
||||||
except:
|
|
||||||
logger.warn(u"Websocket :: Unable to open connection.")
|
|
||||||
# Fallback to polling
|
|
||||||
plexpy.POLLING_FAILOVER = True
|
|
||||||
plexpy.initialize_scheduler()
|
|
||||||
|
|
||||||
# Open webbrowser
|
# Open webbrowser
|
||||||
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch:
|
if plexpy.CONFIG.LAUNCH_BROWSER and not args.nolaunch and not plexpy.DEV:
|
||||||
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
|
plexpy.launch_browser(plexpy.CONFIG.HTTP_HOST, http_port,
|
||||||
plexpy.CONFIG.HTTP_ROOT)
|
plexpy.CONFIG.HTTP_ROOT)
|
||||||
|
|
||||||
|
|
12
README.md
12
README.md
|
@ -9,6 +9,7 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
||||||
* PlexPy [forum thread](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
* PlexPy [forum thread](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Responsive web design viewable on desktop, tablet and mobile web browsers.
|
* Responsive web design viewable on desktop, tablet and mobile web browsers.
|
||||||
* Themed to complement Plex/Web.
|
* Themed to complement Plex/Web.
|
||||||
* Easy configuration setup (no separate web server required).
|
* Easy configuration setup (no separate web server required).
|
||||||
|
@ -65,13 +66,14 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
|
||||||
|
|
||||||
## Feature Requests
|
## Feature Requests
|
||||||
|
|
||||||
1. Search for similar existing 'issues', feature requests can be recognized by the blue `enhancement` label.
|
Feature requests are handled on [FeatHub](http://feathub.com/drzoidberg33/plexpy).
|
||||||
2. If a similar request exists, post a comment (+1, or add a new idea to the existing request).
|
|
||||||
3. If no similar requests exist, you can create a new one.
|
1. Search the existing requests to see if your suggestion has already been submitted.
|
||||||
4. Provide a clear title to easily identify the feature request.
|
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
|
||||||
5. Tag your feature request with `[Feature Request]` so it can be identified easily.
|
3. If no similar requests exist, you can create a new one. Make sure to provide a clear title to easily identify the feature request.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This is free software under the GPL v3 open source license. Feel free to do with it what you wish, but any modification must be open sourced. A copy of the license is included.
|
This is free software under the GPL v3 open source license. Feel free to do with it what you wish, but any modification must be open sourced. A copy of the license is included.
|
||||||
|
|
||||||
This software includes Highsoft software libraries which you may freely distribute for non-commercial use. Commerical users must licence this software, for more information visit https://shop.highsoft.com/faq/non-commercial#non-commercial-redistribution.
|
This software includes Highsoft software libraries which you may freely distribute for non-commercial use. Commerical users must licence this software, for more information visit https://shop.highsoft.com/faq/non-commercial#non-commercial-redistribution.
|
|
@ -21,7 +21,7 @@ history_table_options = {
|
||||||
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||||
"emptyTable": "No data in table"
|
"emptyTable": "No data in table"
|
||||||
},
|
},
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
|
|
|
@ -19,7 +19,7 @@ history_table_modal_options = {
|
||||||
"infoFiltered":"",
|
"infoFiltered":"",
|
||||||
"emptyTable": "No data in table",
|
"emptyTable": "No data in table",
|
||||||
},
|
},
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "simple_numbers",
|
||||||
"stateSave": false,
|
"stateSave": false,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
|
|
|
@ -17,7 +17,7 @@ libraries_list_table_options = {
|
||||||
"order": [ 2, 'asc'],
|
"order": [ 2, 'asc'],
|
||||||
"autoWidth": true,
|
"autoWidth": true,
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
"targets": [0],
|
"targets": [0],
|
||||||
|
|
|
@ -2,7 +2,7 @@ var log_table_options = {
|
||||||
"destroy": true,
|
"destroy": true,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"order": [ 0, 'desc'],
|
"order": [ 0, 'desc'],
|
||||||
"pageLength": 50,
|
"pageLength": 50,
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
|
|
|
@ -22,7 +22,7 @@ media_info_table_options = {
|
||||||
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
|
||||||
"emptyTable": "No data in table"
|
"emptyTable": "No data in table"
|
||||||
},
|
},
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
|
|
|
@ -2,7 +2,7 @@ notification_log_table_options = {
|
||||||
"destroy": true,
|
"destroy": true,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"order": [ 0, 'desc'],
|
"order": [ 0, 'desc'],
|
||||||
"pageLength": 50,
|
"pageLength": 50,
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
|
|
|
@ -2,7 +2,7 @@ var plex_log_table_options = {
|
||||||
"destroy": true,
|
"destroy": true,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": false,
|
"serverSide": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"order": [ 0, 'desc'],
|
"order": [ 0, 'desc'],
|
||||||
"pageLength": 50,
|
"pageLength": 50,
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
sync_table_options = {
|
sync_table_options = {
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": false,
|
"serverSide": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
||||||
"pageLength": 25,
|
"pageLength": 25,
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
|
|
|
@ -9,7 +9,7 @@ user_ip_table_options = {
|
||||||
"emptyTable": "No data in table",
|
"emptyTable": "No data in table",
|
||||||
},
|
},
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"pageLength": 10,
|
"pageLength": 10,
|
||||||
|
|
|
@ -17,7 +17,7 @@ users_list_table_options = {
|
||||||
"order": [ 2, 'asc'],
|
"order": [ 2, 'asc'],
|
||||||
"autoWidth": true,
|
"autoWidth": true,
|
||||||
"stateSave": true,
|
"stateSave": true,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "full_numbers",
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
"targets": [0],
|
"targets": [0],
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<%inherit file="base.html"/>
|
<%inherit file="base.html"/>
|
||||||
<%!
|
<%!
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import plexpy
|
import plexpy
|
||||||
from plexpy import notifiers, common, versioncheck
|
from plexpy import notifiers, common, versioncheck
|
||||||
|
@ -80,6 +81,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<td>Database File:</td>
|
<td>Database File:</td>
|
||||||
<td>${plexpy.DB_FILE}</td>
|
<td>${plexpy.DB_FILE}</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Log File:</td>
|
||||||
|
<td><a class="no-highlight" href="logFile" target="_blank">${os.path.join(config['log_dir'],'plexpy.log')}</a></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Backup Directory:</td>
|
<td>Backup Directory:</td>
|
||||||
<td>${config['backup_dir']}</td>
|
<td>${config['backup_dir']}</td>
|
||||||
|
@ -88,10 +93,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<td>Cache Directory:</td>
|
<td>Cache Directory:</td>
|
||||||
<td>${config['cache_dir']}</td>
|
<td>${config['cache_dir']}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>Log Directory:</td>
|
|
||||||
<td>${config['log_dir']}</td>
|
|
||||||
</tr>
|
|
||||||
% if plexpy.ARGS:
|
% if plexpy.ARGS:
|
||||||
<tr>
|
<tr>
|
||||||
<td>Arguments:</td>
|
<td>Arguments:</td>
|
||||||
|
@ -110,13 +111,21 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<td class="top-line">Plex Forums:</td>
|
<td class="top-line">Plex Forums:</td>
|
||||||
<td class="top-line"><a class="no-highlight" href="${anon_url('https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program')}" target="_blank">https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program</a></td>
|
<td class="top-line"><a class="no-highlight" href="${anon_url('https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program')}" target="_blank">https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Source:</td>
|
||||||
|
<td><a id="source-link" class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy')}" target="_blank">https://github.com/drzoidberg33/plexpy</a></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Wiki:</td>
|
<td>Wiki:</td>
|
||||||
<td><a class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy/wiki')}" target="_blank">https://github.com/drzoidberg33/plexpy/wiki</a></td>
|
<td><a class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy/wiki')}" target="_blank">https://github.com/drzoidberg33/plexpy/wiki</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Source:</td>
|
<td>Issues:</td>
|
||||||
<td><a class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy')}" target="_blank">https://github.com/drzoidberg33/plexpy</a></td>
|
<td><a class="no-highlight guidelines-modal-link" href="${anon_url('https://github.com/drzoidberg33/plexpy/issues')}" data-id="issue">https://github.com/drzoidberg33/plexpy/issues</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Feature Requests:</td>
|
||||||
|
<td><a class="no-highlight guidelines-modal-link" href="${anon_url('http://feathub.com/drzoidberg33/plexpy')}" data-id="feature request">http://feathub.com/drzoidberg33/plexpy</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Gitter Chat:</td>
|
<td>Gitter Chat:</td>
|
||||||
|
@ -142,6 +151,18 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
</label>
|
</label>
|
||||||
<p class="help-block">If you have Git installed, allow periodic checks for updates.</p>
|
<p class="help-block">If you have Git installed, allow periodic checks for updates.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="git_update_options">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="git_token">GitHub API Token</label>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<input type="text" class="form-control" id="git_token" name="git_token" value="${config['git_token']}" data-parsley-trigger="change">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="help-block">Optional: Use your own GitHub API token when checking for updates.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3>Display Settings</h3>
|
<h3>Display Settings</h3>
|
||||||
</div>
|
</div>
|
||||||
|
@ -169,6 +190,13 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
</label>
|
</label>
|
||||||
<p class="help-block">Group successive play history by the same user as a single entry in the tables and watch statistics.</p>
|
<p class="help-block">Group successive play history by the same user as a single entry in the tables and watch statistics.</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="checkbox">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="log_blacklist" name="log_blacklist" value="1" ${config['log_blacklist']}> Mask Sensitive Information in Logs
|
||||||
|
</label>
|
||||||
|
<p class="help-block">Enable to mask passwords, access tokens, and public IP addresses with asterisks (*) in the logs.<br />
|
||||||
|
Note: Only logs from the time this setting is enabled will be masked. Do not post your logs publically without masking sensitive information!</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="padded-header">
|
<div class="padded-header">
|
||||||
<h3>Directories</h3>
|
<h3>Directories</h3>
|
||||||
|
@ -1098,12 +1126,12 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>YYYY</strong></td>
|
<td><strong>YYYY</strong></td>
|
||||||
<td>Numeric, four digits</td>
|
<td>Numeric, four digits</td>
|
||||||
<td>Eg., 1999, 2003</td>
|
<td>E.g. 1999, 2003</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>YY</strong></td>
|
<td><strong>YY</strong></td>
|
||||||
<td>Numeric, two digits</td>
|
<td>Numeric, two digits</td>
|
||||||
<td>Eg., 99, 03</td>
|
<td>E.g. 99, 03</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1181,7 +1209,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Do</strong></td>
|
<td><strong>Do</strong></td>
|
||||||
<td>Numeric, with suffix</td>
|
<td>Numeric, with suffix</td>
|
||||||
<td>Eg., 1st, 2nd ... 31st.</td>
|
<td>E.g. 1st, 2nd ... 31st.</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1317,12 +1345,12 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>ZZ</strong></td>
|
<td><strong>ZZ</strong></td>
|
||||||
<td>UTC offset</td>
|
<td>UTC offset</td>
|
||||||
<td>Eg., +0100, -0700</td>
|
<td>E.g. +0100, -0700</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>Z</strong></td>
|
<td><strong>Z</strong></td>
|
||||||
<td>UTC offset</td>
|
<td>UTC offset</td>
|
||||||
<td>Eg., +01:00, -07:00</td>
|
<td>E.g. +01:00, -07:00</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1338,7 +1366,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong>X</strong></td>
|
<td><strong>X</strong></td>
|
||||||
<td>Unix timestamp</td>
|
<td>Unix timestamp</td>
|
||||||
<td>Eg., 1456887825</td>
|
<td>E.g. 1456887825</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -1858,6 +1886,26 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="guidelines-modal" class="modal fade" tabindex="-1" role="dialog" aria-labelledby="guidelines-modal">
|
||||||
|
<div class="modal-dialog" role="document">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
|
||||||
|
<h4 class="modal-title">Guidelines</h4>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div style="text-align: center; margin-top: 20px; margin-bottom: 20px;">
|
||||||
|
<strong>Please read the <a href="#" target="_blank" id="guidelines-link">guidelines</a> in the README document <br />before submitting a new <span id="guidelines-type"></span>!</strong>
|
||||||
|
<br /><br />
|
||||||
|
Your post may be removed for failure to follow the guidelines.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<a href="#" target="_blank" id="guidelines-continue" class="btn btn-bright">Continue</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</%def>
|
</%def>
|
||||||
|
|
||||||
|
@ -1990,6 +2038,20 @@ $(document).ready(function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if ($("#check_github").is(":checked")) {
|
||||||
|
$("#git_update_options").show();
|
||||||
|
} else {
|
||||||
|
$("#git_update_options").hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#check_github").click(function(){
|
||||||
|
if ($("#check_github").is(":checked")) {
|
||||||
|
$("#git_update_options").slideDown();
|
||||||
|
} else {
|
||||||
|
$("#git_update_options").slideUp();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$( ".http-settings" ).change(function() {
|
$( ".http-settings" ).change(function() {
|
||||||
httpChanged = true;
|
httpChanged = true;
|
||||||
});
|
});
|
||||||
|
@ -2311,6 +2373,17 @@ $(document).ready(function() {
|
||||||
var c = this.checked ? '#eb8600' : '#737373';
|
var c = this.checked ? '#eb8600' : '#737373';
|
||||||
$('#notify_recently_added_grandparent_note').css('color', c);
|
$('#notify_recently_added_grandparent_note').css('color', c);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$('.guidelines-modal-link').on('click', function (e) {
|
||||||
|
e.preventDefault();
|
||||||
|
$('#guidelines-link').attr('href', $('#source-link').attr('href'));
|
||||||
|
$('#guidelines-type').text($(this).data('id'))
|
||||||
|
$('#guidelines-modal').modal();
|
||||||
|
$('#guidelines-continue').attr('href', $(this).attr('href')).on('click', function () {
|
||||||
|
$('#guidelines-modal').modal('hide');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</%def>
|
</%def>
|
|
@ -74,6 +74,8 @@ UMASK = None
|
||||||
|
|
||||||
POLLING_FAILOVER = False
|
POLLING_FAILOVER = False
|
||||||
|
|
||||||
|
DEV = False
|
||||||
|
|
||||||
|
|
||||||
def initialize(config_file):
|
def initialize(config_file):
|
||||||
with INIT_LOCK:
|
with INIT_LOCK:
|
||||||
|
@ -729,30 +731,6 @@ def dbcheck():
|
||||||
'ALTER TABLE users ADD COLUMN deleted_user INTEGER DEFAULT 0'
|
'ALTER TABLE users ADD COLUMN deleted_user INTEGER DEFAULT 0'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Upgrade notify_log table from earlier versions
|
|
||||||
try:
|
|
||||||
c_db.execute('SELECT on_pause FROM notify_log')
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
logger.debug(u"Altering database. Updating database table notify_log.")
|
|
||||||
c_db.execute(
|
|
||||||
'ALTER TABLE notify_log ADD COLUMN on_pause INTEGER'
|
|
||||||
)
|
|
||||||
c_db.execute(
|
|
||||||
'ALTER TABLE notify_log ADD COLUMN on_resume INTEGER'
|
|
||||||
)
|
|
||||||
c_db.execute(
|
|
||||||
'ALTER TABLE notify_log ADD COLUMN on_buffer INTEGER'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Upgrade notify_log table from earlier versions
|
|
||||||
try:
|
|
||||||
c_db.execute('SELECT on_created FROM notify_log')
|
|
||||||
except sqlite3.OperationalError:
|
|
||||||
logger.debug(u"Altering database. Updating database table notify_log.")
|
|
||||||
c_db.execute(
|
|
||||||
'ALTER TABLE notify_log ADD COLUMN on_created INTEGER'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Upgrade notify_log table from earlier versions
|
# Upgrade notify_log table from earlier versions
|
||||||
try:
|
try:
|
||||||
c_db.execute('SELECT poster_url FROM notify_log')
|
c_db.execute('SELECT poster_url FROM notify_log')
|
||||||
|
|
|
@ -111,6 +111,7 @@ _CONFIG_DEFINITIONS = {
|
||||||
'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}),
|
'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}),
|
||||||
'GIT_BRANCH': (str, 'General', 'master'),
|
'GIT_BRANCH': (str, 'General', 'master'),
|
||||||
'GIT_PATH': (str, 'General', ''),
|
'GIT_PATH': (str, 'General', ''),
|
||||||
|
'GIT_TOKEN': (str, 'General', ''),
|
||||||
'GIT_USER': (str, 'General', 'drzoidberg33'),
|
'GIT_USER': (str, 'General', 'drzoidberg33'),
|
||||||
'GRAPH_TYPE': (str, 'General', 'plays'),
|
'GRAPH_TYPE': (str, 'General', 'plays'),
|
||||||
'GRAPH_DAYS': (int, 'General', 30),
|
'GRAPH_DAYS': (int, 'General', 30),
|
||||||
|
@ -142,6 +143,7 @@ _CONFIG_DEFINITIONS = {
|
||||||
'HTTPS_KEY': (str, 'General', ''),
|
'HTTPS_KEY': (str, 'General', ''),
|
||||||
'HTTPS_DOMAIN': (str, 'General', 'localhost'),
|
'HTTPS_DOMAIN': (str, 'General', 'localhost'),
|
||||||
'HTTPS_IP': (str, 'General', '127.0.0.1'),
|
'HTTPS_IP': (str, 'General', '127.0.0.1'),
|
||||||
|
'HTTP_ENVIRONMENT': (str, 'General', 'production'),
|
||||||
'HTTP_HOST': (str, 'General', '0.0.0.0'),
|
'HTTP_HOST': (str, 'General', '0.0.0.0'),
|
||||||
'HTTP_PASSWORD': (str, 'General', ''),
|
'HTTP_PASSWORD': (str, 'General', ''),
|
||||||
'HTTP_PORT': (int, 'General', 8181),
|
'HTTP_PORT': (int, 'General', 8181),
|
||||||
|
@ -167,6 +169,7 @@ _CONFIG_DEFINITIONS = {
|
||||||
'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0),
|
'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0),
|
||||||
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
|
||||||
'LAUNCH_BROWSER': (int, 'General', 1),
|
'LAUNCH_BROWSER': (int, 'General', 1),
|
||||||
|
'LOG_BLACKLIST': (int, 'General', 1),
|
||||||
'LOG_DIR': (str, 'General', ''),
|
'LOG_DIR': (str, 'General', ''),
|
||||||
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
|
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
|
||||||
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
|
||||||
|
@ -431,6 +434,9 @@ _CONFIG_DEFINITIONS = {
|
||||||
'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0)
|
'XBMC_ON_PMSUPDATE': (int, 'XBMC', 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_BLACKLIST_KEYS = ['_APITOKEN', '_TOKEN', '_KEY', '_SECRET', '_PASSWORD', '_APIKEY', '_ID']
|
||||||
|
_WHITELIST_KEYS = ['HTTPS_KEY', 'UPDATE_SECTION_IDS']
|
||||||
|
|
||||||
|
|
||||||
# pylint:disable=R0902
|
# pylint:disable=R0902
|
||||||
# it might be nice to refactor for fewer instance variables
|
# it might be nice to refactor for fewer instance variables
|
||||||
|
@ -444,6 +450,18 @@ class Config(object):
|
||||||
for key in _CONFIG_DEFINITIONS.keys():
|
for key in _CONFIG_DEFINITIONS.keys():
|
||||||
self.check_setting(key)
|
self.check_setting(key)
|
||||||
self._upgrade()
|
self._upgrade()
|
||||||
|
self._blacklist()
|
||||||
|
|
||||||
|
def _blacklist(self):
|
||||||
|
""" Add tokens and passwords to blacklisted words in logger """
|
||||||
|
blacklist = []
|
||||||
|
|
||||||
|
for key, subkeys in self._config.iteritems():
|
||||||
|
for subkey, value in subkeys.iteritems():
|
||||||
|
if str(value).strip() and subkey.upper() not in _WHITELIST_KEYS and any(bk in subkey.upper() for bk in _BLACKLIST_KEYS):
|
||||||
|
blacklist.append(str(value).strip())
|
||||||
|
|
||||||
|
plexpy.logger._BLACKLIST_WORDS = filter(None, blacklist)
|
||||||
|
|
||||||
def _define(self, name):
|
def _define(self, name):
|
||||||
key = name.upper()
|
key = name.upper()
|
||||||
|
@ -503,6 +521,8 @@ class Config(object):
|
||||||
except IOError as e:
|
except IOError as e:
|
||||||
plexpy.logger.error("Error writing configuration file: %s", e)
|
plexpy.logger.error("Error writing configuration file: %s", e)
|
||||||
|
|
||||||
|
self._blacklist()
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
"""
|
"""
|
||||||
Returns something from the ini unless it is a real property
|
Returns something from the ini unless it is a real property
|
||||||
|
|
|
@ -552,13 +552,14 @@ def uploadToImgur(imgPath, imgTitle=''):
|
||||||
response = json.loads(response.read())
|
response = json.loads(response.read())
|
||||||
|
|
||||||
if response.get('status') == 200:
|
if response.get('status') == 200:
|
||||||
logger.debug(u"PlexPy Helpers :: Image uploaded to Imgur.")
|
t = '\'' + imgTitle + '\' ' if imgTitle else ''
|
||||||
|
logger.debug(u"PlexPy Helpers :: Image %suploaded to Imgur." % t)
|
||||||
img_url = response.get('data').get('link', '')
|
img_url = response.get('data').get('link', '')
|
||||||
elif response.get('status') >= 400 and response.get('status') < 500:
|
elif response.get('status') >= 400 and response.get('status') < 500:
|
||||||
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur: %s" % response.reason)
|
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur: %s" % response.reason)
|
||||||
else:
|
else:
|
||||||
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur.")
|
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur.")
|
||||||
except urllib2.HTTPError as e:
|
except (urllib2.HTTPError, urllib2.URLError) as e:
|
||||||
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur: %s" % e)
|
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur: %s" % e)
|
||||||
|
|
||||||
return img_url
|
return img_url
|
|
@ -27,12 +27,15 @@ import logging
|
||||||
import errno
|
import errno
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
|
|
||||||
# These settings are for file logging only
|
# These settings are for file logging only
|
||||||
FILENAME = "plexpy.log"
|
FILENAME = "plexpy.log"
|
||||||
MAX_SIZE = 1000000 # 1 MB
|
MAX_SIZE = 1000000 # 1 MB
|
||||||
MAX_FILES = 5
|
MAX_FILES = 5
|
||||||
|
|
||||||
|
_BLACKLIST_WORDS = []
|
||||||
|
|
||||||
# PlexPy logger
|
# PlexPy logger
|
||||||
logger = logging.getLogger("plexpy")
|
logger = logging.getLogger("plexpy")
|
||||||
|
|
||||||
|
@ -62,6 +65,62 @@ class NoThreadFilter(logging.Filter):
|
||||||
return not record.threadName == self.threadName
|
return not record.threadName == self.threadName
|
||||||
|
|
||||||
|
|
||||||
|
# Taken from Hellowlol/HTPC-Manager
|
||||||
|
class BlacklistFilter(logging.Filter):
|
||||||
|
"""
|
||||||
|
Log filter for blacklisted tokens and passwords
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
if not plexpy.CONFIG.LOG_BLACKLIST:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for item in _BLACKLIST_WORDS:
|
||||||
|
try:
|
||||||
|
if item in record.msg:
|
||||||
|
record.msg = record.msg.replace(item, 8 * '*' + item[-2:])
|
||||||
|
if any(item in str(arg) for arg in record.args):
|
||||||
|
record.args = tuple(arg.replace(item, 8 * '*' + item[-2:]) if isinstance(arg, basestring) else arg
|
||||||
|
for arg in record.args)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class PublicIPFilter(logging.Filter):
|
||||||
|
"""
|
||||||
|
Log filter for public IP addresses
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def filter(self, record):
|
||||||
|
if not plexpy.CONFIG.LOG_BLACKLIST:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Currently only checking for ipv4 addresses
|
||||||
|
ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}', record.msg)
|
||||||
|
for ip in ipv4:
|
||||||
|
if helpers.is_ip_public(ip):
|
||||||
|
record.msg = record.msg.replace(ip, ip.partition('.')[0] + '.***.***.***')
|
||||||
|
|
||||||
|
args = []
|
||||||
|
for arg in record.args:
|
||||||
|
ipv4 = re.findall(r'[0-9]+(?:\.[0-9]+){3}', arg) if isinstance(arg, basestring) else []
|
||||||
|
for ip in ipv4:
|
||||||
|
if helpers.is_ip_public(ip):
|
||||||
|
arg = arg.replace(ip, ip.partition('.')[0] + '.***.***.***')
|
||||||
|
args.append(arg)
|
||||||
|
record.args = tuple(args)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def listener():
|
def listener():
|
||||||
"""
|
"""
|
||||||
|
@ -160,7 +219,7 @@ def initLogger(console=False, log_dir=False, verbose=False):
|
||||||
if log_dir:
|
if log_dir:
|
||||||
filename = os.path.join(log_dir, FILENAME)
|
filename = os.path.join(log_dir, FILENAME)
|
||||||
|
|
||||||
file_formatter = logging.Formatter('%(asctime)s - %(levelname)-7s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
file_formatter = logging.Formatter('%(asctime)s - %(levelname)-7s :: %(threadName)s : %(message)s', '%Y-%m-%d %H:%M:%S')
|
||||||
file_handler = handlers.RotatingFileHandler(filename, maxBytes=MAX_SIZE, backupCount=MAX_FILES)
|
file_handler = handlers.RotatingFileHandler(filename, maxBytes=MAX_SIZE, backupCount=MAX_FILES)
|
||||||
file_handler.setLevel(logging.DEBUG)
|
file_handler.setLevel(logging.DEBUG)
|
||||||
file_handler.setFormatter(file_formatter)
|
file_handler.setFormatter(file_formatter)
|
||||||
|
@ -169,13 +228,21 @@ def initLogger(console=False, log_dir=False, verbose=False):
|
||||||
|
|
||||||
# Setup console logger
|
# Setup console logger
|
||||||
if console:
|
if console:
|
||||||
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%d-%b-%Y %H:%M:%S')
|
console_formatter = logging.Formatter('%(asctime)s - %(levelname)s :: %(threadName)s : %(message)s', '%Y-%m-%d %H:%M:%S')
|
||||||
console_handler = logging.StreamHandler()
|
console_handler = logging.StreamHandler()
|
||||||
console_handler.setFormatter(console_formatter)
|
console_handler.setFormatter(console_formatter)
|
||||||
console_handler.setLevel(logging.DEBUG)
|
console_handler.setLevel(logging.DEBUG)
|
||||||
|
|
||||||
logger.addHandler(console_handler)
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# Add filters to log handlers
|
||||||
|
# Only add filters after the config file has been initialized
|
||||||
|
# Nothing prior to initialization should contain sensitive information
|
||||||
|
if not plexpy.DEV and plexpy.CONFIG:
|
||||||
|
for handler in logger.handlers:
|
||||||
|
handler.addFilter(BlacklistFilter())
|
||||||
|
handler.addFilter(PublicIPFilter())
|
||||||
|
|
||||||
# Install exception hooks
|
# Install exception hooks
|
||||||
initHooks()
|
initHooks()
|
||||||
|
|
||||||
|
|
|
@ -325,11 +325,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
||||||
notify_action=notify_action)
|
notify_action=notify_action)
|
||||||
|
|
||||||
# Set the notification state in the db
|
# Set the notification state in the db
|
||||||
set_notify_state(session={},
|
set_notify_state(notify_action=notify_action,
|
||||||
notify_action=notify_action,
|
|
||||||
agent_info=agent,
|
agent_info=agent,
|
||||||
notify_strings=notify_strings,
|
notify_strings=notify_strings)
|
||||||
metadata={})
|
|
||||||
|
|
||||||
if agent['on_intdown'] and notify_action == 'intdown':
|
if agent['on_intdown'] and notify_action == 'intdown':
|
||||||
# Build and send notification
|
# Build and send notification
|
||||||
|
@ -343,11 +341,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
||||||
notify_action=notify_action)
|
notify_action=notify_action)
|
||||||
|
|
||||||
# Set the notification state in the db
|
# Set the notification state in the db
|
||||||
set_notify_state(session={},
|
set_notify_state(notify_action=notify_action,
|
||||||
notify_action=notify_action,
|
|
||||||
agent_info=agent,
|
agent_info=agent,
|
||||||
notify_strings=notify_strings,
|
notify_strings=notify_strings)
|
||||||
metadata={})
|
|
||||||
|
|
||||||
if agent['on_extup'] and notify_action == 'extup':
|
if agent['on_extup'] and notify_action == 'extup':
|
||||||
# Build and send notification
|
# Build and send notification
|
||||||
|
@ -361,11 +357,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
||||||
notify_action=notify_action)
|
notify_action=notify_action)
|
||||||
|
|
||||||
# Set the notification state in the db
|
# Set the notification state in the db
|
||||||
set_notify_state(session={},
|
set_notify_state(notify_action=notify_action,
|
||||||
notify_action=notify_action,
|
|
||||||
agent_info=agent,
|
agent_info=agent,
|
||||||
notify_strings=notify_strings,
|
notify_strings=notify_strings)
|
||||||
metadata={})
|
|
||||||
|
|
||||||
if agent['on_intup'] and notify_action == 'intup':
|
if agent['on_intup'] and notify_action == 'intup':
|
||||||
# Build and send notification
|
# Build and send notification
|
||||||
|
@ -379,11 +373,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
||||||
notify_action=notify_action)
|
notify_action=notify_action)
|
||||||
|
|
||||||
# Set the notification state in the db
|
# Set the notification state in the db
|
||||||
set_notify_state(session={},
|
set_notify_state(notify_action=notify_action,
|
||||||
notify_action=notify_action,
|
|
||||||
agent_info=agent,
|
agent_info=agent,
|
||||||
notify_strings=notify_strings,
|
notify_strings=notify_strings)
|
||||||
metadata={})
|
|
||||||
|
|
||||||
if agent['on_pmsupdate'] and notify_action == 'pmsupdate':
|
if agent['on_pmsupdate'] and notify_action == 'pmsupdate':
|
||||||
# Build and send notification
|
# Build and send notification
|
||||||
|
@ -397,11 +389,9 @@ def notify_timeline(timeline_data=None, notify_action=None):
|
||||||
notify_action=notify_action)
|
notify_action=notify_action)
|
||||||
|
|
||||||
# Set the notification state in the db
|
# Set the notification state in the db
|
||||||
set_notify_state(session={},
|
set_notify_state(notify_action=notify_action,
|
||||||
notify_action=notify_action,
|
|
||||||
agent_info=agent,
|
agent_info=agent,
|
||||||
notify_strings=notify_strings,
|
notify_strings=notify_strings)
|
||||||
metadata={})
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.debug(u"PlexPy NotificationHandler :: Notify timeline called but incomplete data received.")
|
logger.debug(u"PlexPy NotificationHandler :: Notify timeline called but incomplete data received.")
|
||||||
|
@ -426,11 +416,14 @@ def get_notify_state(session):
|
||||||
return notify_states
|
return notify_states
|
||||||
|
|
||||||
|
|
||||||
def set_notify_state(session, notify_action, agent_info, notify_strings, metadata):
|
def set_notify_state(notify_action, agent_info, notify_strings, session=None, metadata=None):
|
||||||
|
|
||||||
if notify_action and agent_info:
|
if notify_action and agent_info:
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
|
session = session or {}
|
||||||
|
metadata = metadata or {}
|
||||||
|
|
||||||
if notify_strings[2]:
|
if notify_strings[2]:
|
||||||
script_args = '[' + ', '.join(notify_strings[2]) + ']'
|
script_args = '[' + ', '.join(notify_strings[2]) + ']'
|
||||||
else:
|
else:
|
||||||
|
@ -496,7 +489,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
|
||||||
metadata = metadata_list['metadata']
|
metadata = metadata_list['metadata']
|
||||||
else:
|
else:
|
||||||
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve metadata for rating_key %s" % str(rating_key))
|
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve metadata for rating_key %s" % str(rating_key))
|
||||||
return []
|
return [None, None, None], None
|
||||||
|
|
||||||
# Check for exclusion tags
|
# Check for exclusion tags
|
||||||
if metadata['media_type'] == 'movie':
|
if metadata['media_type'] == 'movie':
|
||||||
|
@ -632,11 +625,14 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
|
||||||
|
|
||||||
# If no previous poster_url
|
# If no previous poster_url
|
||||||
if not poster_url and plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS:
|
if not poster_url and plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS:
|
||||||
|
try:
|
||||||
# Retrieve the poster from Plex and cache to file
|
# Retrieve the poster from Plex and cache to file
|
||||||
urllib.urlretrieve(plexpy.CONFIG.PMS_URL + thumb + '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN,
|
urllib.urlretrieve(plexpy.CONFIG.PMS_URL + thumb + '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN,
|
||||||
os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'))
|
os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'))
|
||||||
# Upload thumb to Imgur and get link
|
# Upload thumb to Imgur and get link
|
||||||
poster_url = helpers.uploadToImgur(os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'), poster_title)
|
poster_url = helpers.uploadToImgur(os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'), poster_title)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(u"PlexPy Notifier :: Unable to retrieve poster for rating_key %s: %s." % (str(rating_key), e))
|
||||||
|
|
||||||
metadata['poster_url'] = poster_url
|
metadata['poster_url'] = poster_url
|
||||||
|
|
||||||
|
@ -918,7 +914,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
|
||||||
else:
|
else:
|
||||||
return [subject_text, body_text, script_args], metadata
|
return [subject_text, body_text, script_args], metadata
|
||||||
else:
|
else:
|
||||||
return None
|
return [None, None, None], None
|
||||||
|
|
||||||
|
|
||||||
def build_server_notify_text(notify_action=None, agent_id=None):
|
def build_server_notify_text(notify_action=None, agent_id=None):
|
||||||
|
|
|
@ -114,24 +114,24 @@ def available_notification_agents():
|
||||||
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP,
|
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP,
|
||||||
'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE
|
'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE
|
||||||
},
|
},
|
||||||
{'name': 'Plex',
|
#{'name': 'Plex',
|
||||||
'id': AGENT_IDS['Plex'],
|
# 'id': AGENT_IDS['Plex'],
|
||||||
'config_prefix': 'plex',
|
# 'config_prefix': 'plex',
|
||||||
'has_config': True,
|
# 'has_config': True,
|
||||||
'state': checked(plexpy.CONFIG.PLEX_ENABLED),
|
# 'state': checked(plexpy.CONFIG.PLEX_ENABLED),
|
||||||
'on_play': plexpy.CONFIG.PLEX_ON_PLAY,
|
# 'on_play': plexpy.CONFIG.PLEX_ON_PLAY,
|
||||||
'on_stop': plexpy.CONFIG.PLEX_ON_STOP,
|
# 'on_stop': plexpy.CONFIG.PLEX_ON_STOP,
|
||||||
'on_pause': plexpy.CONFIG.PLEX_ON_PAUSE,
|
# 'on_pause': plexpy.CONFIG.PLEX_ON_PAUSE,
|
||||||
'on_resume': plexpy.CONFIG.PLEX_ON_RESUME,
|
# 'on_resume': plexpy.CONFIG.PLEX_ON_RESUME,
|
||||||
'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER,
|
# 'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER,
|
||||||
'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
|
# 'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
|
||||||
'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
|
# 'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
|
||||||
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
|
# 'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
|
||||||
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
|
# 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
|
||||||
'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
|
# 'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
|
||||||
'on_intup': plexpy.CONFIG.PLEX_ON_INTUP,
|
# 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP,
|
||||||
'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE
|
# 'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE
|
||||||
},
|
# },
|
||||||
{'name': 'NotifyMyAndroid',
|
{'name': 'NotifyMyAndroid',
|
||||||
'id': AGENT_IDS['NMA'],
|
'id': AGENT_IDS['NMA'],
|
||||||
'config_prefix': 'nma',
|
'config_prefix': 'nma',
|
||||||
|
@ -436,7 +436,7 @@ def get_notification_agent_config(agent_id):
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def send_notification(agent_id, subject, body, **kwargs):
|
def send_notification(agent_id, subject, body, notify_action, **kwargs):
|
||||||
if str(agent_id).isdigit():
|
if str(agent_id).isdigit():
|
||||||
agent_id = int(agent_id)
|
agent_id = int(agent_id)
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ def send_notification(agent_id, subject, body, **kwargs):
|
||||||
tweet.notify(subject=subject, message=body)
|
tweet.notify(subject=subject, message=body)
|
||||||
elif agent_id == 12:
|
elif agent_id == 12:
|
||||||
iftttClient = IFTTT()
|
iftttClient = IFTTT()
|
||||||
iftttClient.notify(subject=subject, message=body)
|
iftttClient.notify(subject=subject, message=body, action=notify_action)
|
||||||
elif agent_id == 13:
|
elif agent_id == 13:
|
||||||
telegramClient = TELEGRAM()
|
telegramClient = TELEGRAM()
|
||||||
telegramClient.notify(message=body, event=subject)
|
telegramClient.notify(message=body, event=subject)
|
||||||
|
@ -1604,10 +1604,11 @@ class IFTTT(object):
|
||||||
self.apikey = plexpy.CONFIG.IFTTT_KEY
|
self.apikey = plexpy.CONFIG.IFTTT_KEY
|
||||||
self.event = plexpy.CONFIG.IFTTT_EVENT
|
self.event = plexpy.CONFIG.IFTTT_EVENT
|
||||||
|
|
||||||
def notify(self, message, subject):
|
def notify(self, message, subject, action):
|
||||||
if not message or not subject:
|
if not message or not subject:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
event = unicode(self.event).format(action=action)
|
||||||
http_handler = HTTPSConnection("maker.ifttt.com")
|
http_handler = HTTPSConnection("maker.ifttt.com")
|
||||||
|
|
||||||
data = {'value1': subject.encode("utf-8"),
|
data = {'value1': subject.encode("utf-8"),
|
||||||
|
@ -1616,7 +1617,7 @@ class IFTTT(object):
|
||||||
# logger.debug(u"Ifttt SENDING: %s" % json.dumps(data))
|
# logger.debug(u"Ifttt SENDING: %s" % json.dumps(data))
|
||||||
|
|
||||||
http_handler.request("POST",
|
http_handler.request("POST",
|
||||||
"/trigger/%s/with/key/%s" % (self.event, self.apikey),
|
"/trigger/%s/with/key/%s" % (event, self.apikey),
|
||||||
headers={'Content-type': "application/json"},
|
headers={'Content-type': "application/json"},
|
||||||
body=json.dumps(data))
|
body=json.dumps(data))
|
||||||
response = http_handler.getresponse()
|
response = http_handler.getresponse()
|
||||||
|
@ -1649,7 +1650,9 @@ class IFTTT(object):
|
||||||
{'label': 'Ifttt Event',
|
{'label': 'Ifttt Event',
|
||||||
'value': self.event,
|
'value': self.event,
|
||||||
'name': 'ifttt_event',
|
'name': 'ifttt_event',
|
||||||
'description': 'The Ifttt maker event to fire. The notification subject and body will be sent'
|
'description': 'The Ifttt maker event to fire. You can include'
|
||||||
|
' the {action} to be substituted with the action name.'
|
||||||
|
' The notification subject and body will be sent'
|
||||||
' as value1 and value2 respectively.',
|
' as value1 and value2 respectively.',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
}
|
}
|
||||||
|
@ -2006,12 +2009,11 @@ class Scripts(object):
|
||||||
logger.error(u"PlexPy Notifiers :: Failed to run script: %s" % e)
|
logger.error(u"PlexPy Notifiers :: Failed to run script: %s" % e)
|
||||||
|
|
||||||
def return_config_options(self):
|
def return_config_options(self):
|
||||||
config_option = [{'label': 'Warning',
|
config_option = [{'label': 'Supported File Types',
|
||||||
'description': '<strong>Script notifications are currently experimental!</strong><br><br>\
|
'description': ', '.join(self.script_exts),
|
||||||
Supported file types: ' + ', '.join(self.script_exts),
|
|
||||||
'input_type': 'help'
|
'input_type': 'help'
|
||||||
},
|
},
|
||||||
{'label': 'Script folder',
|
{'label': 'Script Folder',
|
||||||
'value': plexpy.CONFIG.SCRIPTS_FOLDER,
|
'value': plexpy.CONFIG.SCRIPTS_FOLDER,
|
||||||
'name': 'scripts_folder',
|
'name': 'scripts_folder',
|
||||||
'description': 'Add your script folder.',
|
'description': 'Add your script folder.',
|
||||||
|
@ -2170,50 +2172,60 @@ class FacebookNotifier(object):
|
||||||
if self.incl_poster and 'metadata' in kwargs:
|
if self.incl_poster and 'metadata' in kwargs:
|
||||||
metadata = kwargs['metadata']
|
metadata = kwargs['metadata']
|
||||||
poster_url = metadata.get('poster_url','')
|
poster_url = metadata.get('poster_url','')
|
||||||
|
poster_link = ''
|
||||||
|
caption = ''
|
||||||
|
|
||||||
|
# Use default posters if no poster_url
|
||||||
|
if not poster_url:
|
||||||
|
if metadata['media_type'] in ['artist', 'track']:
|
||||||
|
poster_url = 'https://raw.githubusercontent.com/drzoidberg33/plexpy/master/data/interfaces/default/images/cover.png'
|
||||||
|
else:
|
||||||
|
poster_url = 'https://raw.githubusercontent.com/drzoidberg33/plexpy/master/data/interfaces/default/images/poster.png'
|
||||||
|
|
||||||
if poster_url:
|
|
||||||
if metadata['media_type'] == 'movie':
|
if metadata['media_type'] == 'movie':
|
||||||
title = metadata['title']
|
title = '%s (%s)' % (metadata['title'], metadata['year'])
|
||||||
subtitle = metadata['year']
|
subtitle = metadata['summary']
|
||||||
rating_key = metadata['rating_key']
|
rating_key = metadata['rating_key']
|
||||||
if metadata.get('imdb_url',''):
|
if metadata.get('imdb_url',''):
|
||||||
poster_link = metadata.get('imdb_url', '')
|
poster_link = metadata.get('imdb_url', '')
|
||||||
caption = 'View on IMDB.'
|
caption = 'View on IMDB'
|
||||||
elif metadata.get('themoviedb_url',''):
|
elif metadata.get('themoviedb_url',''):
|
||||||
poster_link = metadata.get('themoviedb_url', '')
|
poster_link = metadata.get('themoviedb_url', '')
|
||||||
caption = 'View on The Movie Database.'
|
caption = 'View on The Movie Database'
|
||||||
|
|
||||||
elif metadata['media_type'] == 'show':
|
elif metadata['media_type'] == 'show':
|
||||||
title = metadata['title']
|
title = '%s (%s)' % (metadata['title'], metadata['year'])
|
||||||
subtitle = metadata['year']
|
subtitle = metadata['summary']
|
||||||
rating_key = metadata['rating_key']
|
rating_key = metadata['rating_key']
|
||||||
if metadata.get('thetvdb_url',''):
|
if metadata.get('thetvdb_url',''):
|
||||||
poster_link = metadata.get('thetvdb_url', '')
|
poster_link = metadata.get('thetvdb_url', '')
|
||||||
caption = 'View on TheTVDB.'
|
caption = 'View on TheTVDB'
|
||||||
elif metadata.get('themoviedb_url',''):
|
elif metadata.get('themoviedb_url',''):
|
||||||
poster_link = metadata.get('themoviedb_url', '')
|
poster_link = metadata.get('themoviedb_url', '')
|
||||||
caption = 'View on The Movie Database.'
|
caption = 'View on The Movie Database'
|
||||||
|
|
||||||
elif metadata['media_type'] == 'episode':
|
elif metadata['media_type'] == 'episode':
|
||||||
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
|
title = '%s - %s (S%s %s E%s)' % (metadata['grandparent_title'],
|
||||||
subtitle = 'S%s %s E%s' % (metadata['parent_media_index'],
|
metadata['title'],
|
||||||
|
metadata['parent_media_index'],
|
||||||
'\xc2\xb7'.decode('utf8'),
|
'\xc2\xb7'.decode('utf8'),
|
||||||
metadata['media_index'])
|
metadata['media_index'])
|
||||||
|
subtitle = metadata['summary']
|
||||||
rating_key = metadata['rating_key']
|
rating_key = metadata['rating_key']
|
||||||
if metadata.get('thetvdb_url',''):
|
if metadata.get('thetvdb_url',''):
|
||||||
poster_link = metadata.get('thetvdb_url', '')
|
poster_link = metadata.get('thetvdb_url', '')
|
||||||
caption = 'View on TheTVDB.'
|
caption = 'View on TheTVDB'
|
||||||
elif metadata.get('themoviedb_url',''):
|
elif metadata.get('themoviedb_url',''):
|
||||||
poster_link = metadata.get('themoviedb_url', '')
|
poster_link = metadata.get('themoviedb_url', '')
|
||||||
caption = 'View on The Movie Database.'
|
caption = 'View on The Movie Database'
|
||||||
|
|
||||||
elif metadata['media_type'] == 'artist':
|
elif metadata['media_type'] == 'artist':
|
||||||
title = metadata['title']
|
title = metadata['title']
|
||||||
subtitle = ''
|
subtitle = metadata['summary']
|
||||||
rating_key = metadata['rating_key']
|
rating_key = metadata['rating_key']
|
||||||
if metadata.get('lastfm_url',''):
|
if metadata.get('lastfm_url',''):
|
||||||
poster_link = metadata.get('lastfm_url', '')
|
poster_link = metadata.get('lastfm_url', '')
|
||||||
caption = 'View on Last.fm.'
|
caption = 'View on Last.fm'
|
||||||
|
|
||||||
elif metadata['media_type'] == 'track':
|
elif metadata['media_type'] == 'track':
|
||||||
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
|
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
|
||||||
|
@ -2221,23 +2233,22 @@ class FacebookNotifier(object):
|
||||||
rating_key = metadata['parent_rating_key']
|
rating_key = metadata['parent_rating_key']
|
||||||
if metadata.get('lastfm_url',''):
|
if metadata.get('lastfm_url',''):
|
||||||
poster_link = metadata.get('lastfm_url', '')
|
poster_link = metadata.get('lastfm_url', '')
|
||||||
caption = 'View on Last.fm.'
|
caption = 'View on Last.fm'
|
||||||
|
|
||||||
# Build Facebook post attachment
|
# Build Facebook post attachment
|
||||||
if self.incl_pmslink:
|
if self.incl_pmslink:
|
||||||
caption = 'View on Plex Web.'
|
caption = 'View on Plex Web'
|
||||||
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
|
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
|
||||||
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
|
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
|
||||||
attachment['caption'] = caption
|
|
||||||
elif poster_link:
|
elif poster_link:
|
||||||
attachment['link'] = poster_link
|
attachment['link'] = poster_link
|
||||||
attachment['caption'] = caption
|
|
||||||
else:
|
else:
|
||||||
attachment['link'] = poster_url
|
attachment['link'] = poster_url
|
||||||
|
|
||||||
attachment['picture'] = poster_url
|
attachment['picture'] = poster_url
|
||||||
attachment['name'] = title
|
attachment['name'] = title
|
||||||
attachment['description'] = subtitle
|
attachment['description'] = subtitle
|
||||||
|
attachment['caption'] = caption
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment)
|
api.put_wall_post(profile_id=self.group_id, message=message, attachment=attachment)
|
||||||
|
@ -2253,13 +2264,12 @@ class FacebookNotifier(object):
|
||||||
|
|
||||||
def return_config_options(self):
|
def return_config_options(self):
|
||||||
config_option = [{'label': 'Instructions',
|
config_option = [{'label': 'Instructions',
|
||||||
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
|
'description': 'Step 1: Visit <a href="' + helpers.anon_url('https://developers.facebook.com/apps') + '" target="_blank"> \
|
||||||
Step 1: Visit <a href="' + helpers.anon_url('https://developers.facebook.com/apps') + '" target="_blank"> \
|
|
||||||
Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\
|
Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\
|
||||||
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
|
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
|
||||||
<strong>Contact Email</strong>.<br>\
|
<strong>Contact Email</strong>.<br>\
|
||||||
Step 3: Go to <strong>Settings > Advanced</strong> and fill in \
|
Step 3: Go to <strong>Settings > Advanced</strong> and fill in \
|
||||||
<strong>Valid OAuth redirect URIs</strong> with your PlexPy URL (i.e. http://localhost:8181).<br>\
|
<strong>Valid OAuth redirect URIs</strong> with your PlexPy URL (e.g. http://localhost:8181).<br>\
|
||||||
Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\
|
Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\
|
||||||
Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\
|
Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\
|
||||||
Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
|
Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
|
||||||
|
@ -2270,7 +2280,8 @@ class FacebookNotifier(object):
|
||||||
{'label': 'PlexPy URL',
|
{'label': 'PlexPy URL',
|
||||||
'value': self.redirect_uri,
|
'value': self.redirect_uri,
|
||||||
'name': 'facebook_redirect_uri',
|
'name': 'facebook_redirect_uri',
|
||||||
'description': 'Your PlexPy URL. This will tell Facebook where to redirect you after authorization.',
|
'description': 'Your PlexPy URL. This will tell Facebook where to redirect you after authorization.\
|
||||||
|
(e.g. http://localhost:8181)',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
},
|
},
|
||||||
{'label': 'Facebook App ID',
|
{'label': 'Facebook App ID',
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
PLEXPY_VERSION = "master"
|
PLEXPY_VERSION = "master"
|
||||||
PLEXPY_RELEASE_VERSION = "1.3.11"
|
PLEXPY_RELEASE_VERSION = "1.3.12"
|
||||||
|
|
|
@ -124,6 +124,7 @@ def checkGithub():
|
||||||
# Get the latest version available from github
|
# Get the latest version available from github
|
||||||
logger.info('Retrieving latest version information from GitHub')
|
logger.info('Retrieving latest version information from GitHub')
|
||||||
url = 'https://api.github.com/repos/%s/plexpy/commits/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_BRANCH)
|
url = 'https://api.github.com/repos/%s/plexpy/commits/%s' % (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_BRANCH)
|
||||||
|
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN
|
||||||
version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict)
|
version = request.request_json(url, timeout=20, validator=lambda x: type(x) == dict)
|
||||||
|
|
||||||
if version is None:
|
if version is None:
|
||||||
|
@ -144,6 +145,7 @@ def checkGithub():
|
||||||
|
|
||||||
logger.info('Comparing currently installed version with latest GitHub version')
|
logger.info('Comparing currently installed version with latest GitHub version')
|
||||||
url = 'https://api.github.com/repos/%s/plexpy/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.LATEST_VERSION, plexpy.CURRENT_VERSION)
|
url = 'https://api.github.com/repos/%s/plexpy/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.LATEST_VERSION, plexpy.CURRENT_VERSION)
|
||||||
|
if plexpy.CONFIG.GIT_TOKEN: url = url + '?access_token=%s' % plexpy.CONFIG.GIT_TOKEN
|
||||||
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
|
commits = request.request_json(url, timeout=20, whitelist_status_code=404, validator=lambda x: type(x) == dict)
|
||||||
|
|
||||||
if commits is None:
|
if commits is None:
|
||||||
|
|
|
@ -1145,6 +1145,15 @@ class WebInterface(object):
|
||||||
line))
|
line))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@cherrypy.expose
|
||||||
|
def logFile(self):
|
||||||
|
try:
|
||||||
|
with open(os.path.join(plexpy.CONFIG.LOG_DIR, 'plexpy.log'), 'r') as f:
|
||||||
|
return '<pre>%s</pre>' % f.read()
|
||||||
|
except IOError as e:
|
||||||
|
return "Log file not found."
|
||||||
|
|
||||||
|
|
||||||
##### Settings #####
|
##### Settings #####
|
||||||
|
|
||||||
@cherrypy.expose
|
@cherrypy.expose
|
||||||
|
@ -1180,6 +1189,7 @@ class WebInterface(object):
|
||||||
"backup_dir": plexpy.CONFIG.BACKUP_DIR,
|
"backup_dir": plexpy.CONFIG.BACKUP_DIR,
|
||||||
"cache_dir": plexpy.CONFIG.CACHE_DIR,
|
"cache_dir": plexpy.CONFIG.CACHE_DIR,
|
||||||
"log_dir": plexpy.CONFIG.LOG_DIR,
|
"log_dir": plexpy.CONFIG.LOG_DIR,
|
||||||
|
"log_blacklist": checked(plexpy.CONFIG.LOG_BLACKLIST),
|
||||||
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
|
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
|
||||||
"interface_list": interface_list,
|
"interface_list": interface_list,
|
||||||
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
|
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
|
||||||
|
@ -1261,7 +1271,8 @@ class WebInterface(object):
|
||||||
"home_library_cards": json.dumps(plexpy.CONFIG.HOME_LIBRARY_CARDS),
|
"home_library_cards": json.dumps(plexpy.CONFIG.HOME_LIBRARY_CARDS),
|
||||||
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
|
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
|
||||||
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT,
|
"buffer_wait": plexpy.CONFIG.BUFFER_WAIT,
|
||||||
"group_history_tables": checked(plexpy.CONFIG.GROUP_HISTORY_TABLES)
|
"group_history_tables": checked(plexpy.CONFIG.GROUP_HISTORY_TABLES),
|
||||||
|
"git_token": plexpy.CONFIG.GIT_TOKEN
|
||||||
}
|
}
|
||||||
|
|
||||||
return serve_template(templatename="settings.html", title="Settings", config=config)
|
return serve_template(templatename="settings.html", title="Settings", config=config)
|
||||||
|
@ -1281,7 +1292,7 @@ class WebInterface(object):
|
||||||
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
|
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
|
||||||
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters",
|
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters",
|
||||||
"notify_recently_added", "notify_recently_added_grandparent",
|
"notify_recently_added", "notify_recently_added_grandparent",
|
||||||
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes"
|
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist"
|
||||||
]
|
]
|
||||||
for checked_config in checked_configs:
|
for checked_config in checked_configs:
|
||||||
if checked_config not in kwargs:
|
if checked_config not in kwargs:
|
||||||
|
@ -1308,7 +1319,8 @@ class WebInterface(object):
|
||||||
refresh_users = False
|
refresh_users = False
|
||||||
|
|
||||||
# If we change any monitoring settings, make sure we reschedule tasks.
|
# If we change any monitoring settings, make sure we reschedule tasks.
|
||||||
if kwargs.get('monitoring_interval') != str(plexpy.CONFIG.MONITORING_INTERVAL) or \
|
if kwargs.get('check_github') != plexpy.CONFIG.CHECK_GITHUB or \
|
||||||
|
kwargs.get('monitoring_interval') != str(plexpy.CONFIG.MONITORING_INTERVAL) or \
|
||||||
kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \
|
kwargs.get('refresh_libraries_interval') != str(plexpy.CONFIG.REFRESH_LIBRARIES_INTERVAL) or \
|
||||||
kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
|
kwargs.get('refresh_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
|
||||||
kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \
|
kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \
|
||||||
|
@ -1454,7 +1466,7 @@ class WebInterface(object):
|
||||||
|
|
||||||
if this_agent:
|
if this_agent:
|
||||||
logger.debug(u"Sending test %s notification." % this_agent['name'])
|
logger.debug(u"Sending test %s notification." % this_agent['name'])
|
||||||
notifiers.send_notification(this_agent['id'], subject, body, **kwargs)
|
notifiers.send_notification(this_agent['id'], subject, body, 'test', **kwargs)
|
||||||
return "Notification sent."
|
return "Notification sent."
|
||||||
else:
|
else:
|
||||||
logger.debug(u"Unable to send test notification, invalid notification agent ID %s." % agent_id)
|
logger.debug(u"Unable to send test notification, invalid notification agent ID %s." % agent_id)
|
||||||
|
@ -2222,11 +2234,18 @@ class WebInterface(object):
|
||||||
'I need your clothes, your boots and your motorcycle.',
|
'I need your clothes, your boots and your motorcycle.',
|
||||||
'No, it\'s not a tumor. It\'s not a tumor!',
|
'No, it\'s not a tumor. It\'s not a tumor!',
|
||||||
'I LIED!',
|
'I LIED!',
|
||||||
'See you at the party, Richter!',
|
'Are you Sarah Connor?',
|
||||||
'Are you Sarah Conner?',
|
|
||||||
'I\'m a cop you idiot!',
|
'I\'m a cop you idiot!',
|
||||||
'Come with me if you want to live.',
|
'Come with me if you want to live.',
|
||||||
'Who is your daddy and what does he do?'
|
'Who is your daddy and what does he do?',
|
||||||
|
'Oh, cookies! I can\'t wait to toss them.',
|
||||||
|
'Can you hurry up. My horse is getting tired.',
|
||||||
|
'What killed the dinosaurs? The Ice Age!',
|
||||||
|
'That\'s for sleeping with my wife!',
|
||||||
|
'Remember when I said I’d kill you last... I lied!',
|
||||||
|
'You want to be a farmer? Here\'s a couple of acres',
|
||||||
|
'Now, this is the plan. Get your ass to Mars.',
|
||||||
|
'I just had a terrible thought... What if this is a dream?'
|
||||||
]
|
]
|
||||||
|
|
||||||
random_number = randint(0, len(quote_list) - 1)
|
random_number = randint(0, len(quote_list) - 1)
|
||||||
|
|
|
@ -46,12 +46,11 @@ def initialize(options):
|
||||||
options_dict = {
|
options_dict = {
|
||||||
'server.socket_port': options['http_port'],
|
'server.socket_port': options['http_port'],
|
||||||
'server.socket_host': options['http_host'],
|
'server.socket_host': options['http_host'],
|
||||||
|
'environment': options['http_environment'],
|
||||||
'server.thread_pool': 10,
|
'server.thread_pool': 10,
|
||||||
'tools.encode.on': True,
|
'tools.encode.on': True,
|
||||||
'tools.encode.encoding': 'utf-8',
|
'tools.encode.encoding': 'utf-8',
|
||||||
'tools.decode.on': True,
|
'tools.decode.on': True,
|
||||||
'log.screen': False,
|
|
||||||
'engine.autoreload.on': False,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if enable_https:
|
if enable_https:
|
||||||
|
@ -61,6 +60,10 @@ def initialize(options):
|
||||||
else:
|
else:
|
||||||
protocol = "http"
|
protocol = "http"
|
||||||
|
|
||||||
|
if plexpy.DEV:
|
||||||
|
options_dict['environment'] = "test_suite"
|
||||||
|
options_dict['engine.autoreload.on'] = True
|
||||||
|
|
||||||
logger.info("Starting PlexPy web server on %s://%s:%d/", protocol,
|
logger.info("Starting PlexPy web server on %s://%s:%d/", protocol,
|
||||||
options['http_host'], options['http_port'])
|
options['http_host'], options['http_port'])
|
||||||
cherrypy.config.update(options_dict)
|
cherrypy.config.update(options_dict)
|
||||||
|
@ -119,7 +122,12 @@ def initialize(options):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cherrypy.process.servers.check_port(str(options['http_host']), options['http_port'])
|
cherrypy.process.servers.check_port(str(options['http_host']), options['http_port'])
|
||||||
|
if not plexpy.DEV:
|
||||||
cherrypy.server.start()
|
cherrypy.server.start()
|
||||||
|
else:
|
||||||
|
cherrypy.engine.signals.subscribe()
|
||||||
|
cherrypy.engine.start()
|
||||||
|
cherrypy.engine.block()
|
||||||
except IOError:
|
except IOError:
|
||||||
sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port']))
|
sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port']))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue