Compare commits

...

62 Commits

Author SHA1 Message Date
JonnyWong16
541d2904d3 Merge branch 'dev' 2016-04-18 21:29:13 -07:00
JonnyWong16
2080bbcbca v1.3.15 2016-04-18 21:28:45 -07:00
JonnyWong16
f4c38008a1 Fix logger typo 2016-04-16 22:46:47 -07:00
JonnyWong16
2d38b15f1b Merge pull request #672 from alotufo/dev
Optimized iOS, Android and IE10 images
2016-04-16 09:36:20 -07:00
Al Lotufo
13257b9f86 Optimize iOS images
Ran images through ImageOptim to reduce file size
2016-04-16 12:23:09 -04:00
Al Lotufo
22f106b357 Optimize IE10 images
Ran images through ImageOptim to reduce file size
2016-04-16 12:21:17 -04:00
Al Lotufo
352b5aadba Optimize Android images
Ran images through ImageOptim to reduce file size
2016-04-16 12:20:42 -04:00
JonnyWong16
f86c9ea947 Fix 127.0.0.1 showing as external IP address on tables 2016-04-13 18:03:05 -07:00
JonnyWong16
2f5f0ba1e1 Fix getting pms_url when multiple connections published
* Fixes bug grabbing the wrong pms_url when multiple local/remote
connections are published to plex.tv
* This tries to find the connection with the matching address first,
otherwise grabs the first valid local/remote connection (prior
behaviour)
2016-04-12 22:52:53 -07:00
JonnyWong16
6a72923182 Fix Slack notifications with an icon URL not sending 2016-04-12 08:55:08 -07:00
JonnyWong16
8ca6255ff3 Merge pull request #658 from xtjoeytx/patch-1
Update welcome.html
2016-04-08 16:13:33 -07:00
Joseph
d3b3afd593 Update welcome.html
fixed spelling error
2016-04-08 01:07:41 -04:00
JonnyWong16
57cb5ff6cf Fix fallback pms_url if server URL not found 2016-04-06 19:23:59 -07:00
JonnyWong16
39f3da6cde Fix regression file sizes not shown in media info table footer 2016-04-06 19:21:52 -07:00
JonnyWong16
5a236c9357 Fix wording of notification delay help text 2016-04-06 19:19:13 -07:00
JonnyWong16
4b0eab57a8 Fix regression PMS down notifications failing 2016-04-06 19:17:56 -07:00
JonnyWong16
74a232630a Add {transcode_key} and {username} notification options 2016-04-02 14:11:18 -07:00
JonnyWong16
71d023ab77 Fix logger typo in notification handler 2016-04-02 12:13:54 -07:00
JonnyWong16
786a374233 Merge branch 'dev' 2016-03-29 08:16:25 -07:00
JonnyWong16
41899872cd v1.3.14 2016-03-29 08:15:35 -07:00
JonnyWong16
076659db52 Fix regression missing notify_action for script notifications 2016-03-28 22:21:23 -07:00
JonnyWong16
8f665622d6 Fix typo for home stats cards in settings 2016-03-28 18:12:32 -07:00
JonnyWong16
5cc6e0b172 Merge branch 'dev' 2016-03-27 17:25:53 -07:00
JonnyWong16
bff22900cb v1.3.13 2016-03-27 17:25:16 -07:00
JonnyWong16
5e79c9fd62 Only filter logger if string is longer than 5 characters 2016-03-27 17:22:30 -07:00
JonnyWong16
92f55c254c Merge branch 'dev' 2016-03-27 16:51:30 -07:00
JonnyWong16
39034e38f6 v1.3.12 2016-03-27 16:43:06 -07:00
JonnyWong16
3c7b9558fe Access log file from the Help & Info page 2016-03-27 16:41:57 -07:00
JonnyWong16
c8f7f40b46 ISO date format for logs 2016-03-27 16:10:02 -07:00
JonnyWong16
2a764cf190 Add "First" and "Last" page buttons to datatables 2016-03-27 15:32:03 -07:00
JonnyWong16
ba6ef4d629 Add toggle for log blacklist and mask public IP addresses 2016-03-27 15:18:41 -07:00
JonnyWong16
67d3505733 Merge pull request #627 from JonnyWong16/dev-env
Enable PlexPy dev environment
2016-03-27 10:30:42 -07:00
JonnyWong16
252145cf58 Enable PlexPy dev environment using --dev flag 2016-03-27 10:25:46 -07:00
JonnyWong16
dbc62542ef Merge pull request #634 from evilmarty/enhancement/ifttt
Allow formatting of IFTTT event key with action name
2016-03-26 18:08:45 -07:00
Marty Zalega
005829ab72 Allow formatting of ifttt event key with action name 2016-03-27 10:46:09 +10:00
JonnyWong16
448c8b0e8a Merge pull request #632 from alshain/patch-2
Fix unicode logging with 1252 encoded locale on Windows
2016-03-26 12:09:27 -07:00
Chris
ff0e724ee5 Fix unicode logging with 1252 encoded locale on Windows
Replaces the abbreviation for months in the log output with numerals. Works around logging exceptions on Windows during March ("Mär") with Swiss-German locale on Windows, which is encoded with Windows-1252.
2016-03-26 19:59:12 +01:00
JonnyWong16
568e4a5ee8 Fix blacklist logging again ed8c7c1 2016-03-26 10:00:58 -07:00
JonnyWong16
fbf4a524c1 Catch URLError when uploading to Imgur 2016-03-25 15:39:31 -07:00
JonnyWong16
29db2e958f Hide Plex notification agent 2016-03-25 13:24:24 -07:00
JonnyWong16
cc7bcbf9d5 Change log directory to log file in "Help & Info" 2016-03-25 13:20:50 -07:00
JonnyWong16
98d4484e6c Update CONTRIBUTING.md 2016-03-25 13:20:25 -07:00
JonnyWong16
0b126278f9 Fix blacklisting of blank strings from ed8c7c1 2016-03-25 13:00:04 -07:00
JonnyWong16
cc919415bb Merge pull request #612 from codedecay/dev
Add CherryPy Environment Option
2016-03-25 12:59:28 -07:00
JonnyWong16
a3f398390c Clean up Arnie quotes 2016-03-25 09:16:24 -07:00
JonnyWong16
5ae89368f1 Merge pull request #625 from Chrisophogus/patch-1
Extra Arnie
2016-03-25 09:14:25 -07:00
JonnyWong16
c8f1cb0a0a Start PlexPy for different environment variables 2016-03-25 09:12:40 -07:00
Chrisophogus
f783b08b78 Additional changes 2016-03-23 20:52:50 +00:00
Chrisophogus
85e0c6d3cd Extra Arnie
Added some additional Arnie quotes.
2016-03-23 20:14:48 +00:00
JonnyWong16
2259a96058 Use default poster for Facebook if unable to upload poster 2016-03-21 18:53:56 -07:00
JonnyWong16
afed5841e7 Remove old notify_log table upgrades 2016-03-20 17:17:50 -07:00
JonnyWong16
52361cd505 Catch error if unable to retrieve poster for notification 2016-03-20 17:06:58 -07:00
JonnyWong16
b743cca7bc Add summary to Facebook posts 2016-03-19 23:27:26 -07:00
JonnyWong16
1d01d0bff1 Add FeatHub feature requests and guidelines modal popup 2016-03-19 22:24:09 -07:00
JonnyWong16
c45a488962 Remove experimental from Facebook and Scripts 2016-03-19 20:45:49 -07:00
JonnyWong16
6731c44541 Merge pull request #614 from Vilsol/dev
Update Arnold Quotes
2016-03-19 16:20:31 -07:00
JonnyWong16
b04ed83963 Make sure build_notify_text returns two values 2016-03-16 19:13:29 -07:00
Vilsol
c35b79e642 Update Arnold Quotes 2016-03-16 16:33:56 +00:00
JonnyWong16
ed8c7c1052 Filter out tokens/keys/passwords from logger 2016-03-15 23:49:35 -07:00
JonnyWong16
498a074222 Add user GitHub API Token to settings 2016-03-15 23:49:27 -07:00
JonnyWong16
3fe6db4d42 Fix "Check GitHub for updates" not rescheduling when toggling setting 2016-03-15 20:42:45 -07:00
Eric Solari
6e5cd82dfb Add CherryPy Environment Option 2016-03-15 22:24:08 -05:00
62 changed files with 532 additions and 269 deletions

View File

@@ -1,5 +1,48 @@
# Changelog # Changelog
## v1.3.15 (2016-04-18)
* Fix: Slack notifications failing when using and icon URL.
* Fix: 127.0.0.1 showing as an external IP address on the history tables.
* Fix: Regression file sizes not shown in the media info table footer.
* Fix: Retrieving proper PMS URL when multiple connections are published to plex.tv.
* Fix: Some typos in the logger.
* Fix: Some other typos in the WebUI. (Thanks @xtjoeytx)
* Change: Optimized mobile web app icons and spash screens. (Thanks @alotufo)
## v1.3.14 (2016-03-29)
* Fix: Regression for missing notify_action for script notifications.
* Fix: Typo for home stats cards in the settings.
## v1.3.13 (2016-03-27)
* Fix: Only mask strings longer than 5 characters in logs.
## 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.

View File

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

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 KiB

After

Width:  |  Height:  |  Size: 349 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

After

Width:  |  Height:  |  Size: 4.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

After

Width:  |  Height:  |  Size: 4.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

After

Width:  |  Height:  |  Size: 4.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 MiB

After

Width:  |  Height:  |  Size: 4.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 977 KiB

After

Width:  |  Height:  |  Size: 788 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 207 KiB

After

Width:  |  Height:  |  Size: 167 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 455 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 460 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 466 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 463 KiB

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 458 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 464 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 456 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 KiB

After

Width:  |  Height:  |  Size: 7.4 KiB

View File

@@ -39,7 +39,6 @@ function showMsg(msg,loader,timeout,ms,error) {
} }
if (error) { if (error) {
feedback.css("background-color", "rgba(255,0,0,0.5)"); feedback.css("background-color", "rgba(255,0,0,0.5)");
console.log('is error');
} }
$(feedback).html(message); $(feedback).html(message);
feedback.fadeIn(); feedback.fadeIn();
@@ -48,7 +47,8 @@ function showMsg(msg,loader,timeout,ms,error) {
setTimeout(function(){ setTimeout(function(){
message.fadeOut(function(){ message.fadeOut(function(){
$(this).remove(); $(this).remove();
feedback.fadeOut(); feedback.fadeOut();
feedback.css("background-color", "");
}); });
},ms); },ms);
} }
@@ -242,7 +242,8 @@ function isPrivateIP(ip_address) {
// get IPv4 mapped address (xxx.xxx.xxx.xxx) from IPv6 addresss (::ffff:xxx.xxx.xxx.xxx) // get IPv4 mapped address (xxx.xxx.xxx.xxx) from IPv6 addresss (::ffff:xxx.xxx.xxx.xxx)
var parts = ip_address.split(":"); var parts = ip_address.split(":");
var parts = parts[parts.length - 1].split('.'); var parts = parts[parts.length - 1].split('.');
if (parts[0] === '10' || if ((parts[0] === '127' && parts[1] === '0' && parts[2] === '0' && parts[3] === '1') ||
(parts[0] === '10') ||
(parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) || (parts[0] === '172' && (parseInt(parts[1], 10) >= 16 && parseInt(parts[1], 10) <= 31)) ||
(parts[0] === '192' && parts[1] === '168')) { (parts[0] === '192' && parts[1] === '168')) {
return true; return true;

View File

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

View File

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

View File

@@ -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],

View File

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

View File

@@ -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,
@@ -276,7 +276,7 @@ media_info_table_options = {
get_file_sizes = false; get_file_sizes = false;
} }
$("#media_info_table_info").append('<span class="hidden-md hidden-sm hidden-xs"> with a total file size of ' + $("#media_info_table-SID-" + section_id + "_info").append('<span class="hidden-md hidden-sm hidden-xs"> with a total file size of ' +
Math.round(settings.json.filtered_file_size / Math.pow(1024, 3)).toString() + ' GiB' + Math.round(settings.json.filtered_file_size / Math.pow(1024, 3)).toString() + ' GiB' +
' (filtered from ' + Math.round(settings.json.total_file_size / Math.pow(1024, 3)).toString() + ' GiB)</span>'); ' (filtered from ' + Math.round(settings.json.total_file_size / Math.pow(1024, 3)).toString() + ' GiB)</span>');
}, },

View File

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

View File

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

View File

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

View File

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

View File

@@ -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],

View File

@@ -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>
@@ -241,13 +269,13 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<li class="card card-sortable"> <li class="card card-sortable">
<div class="card-handle"><i class="fa fa-bars"></i></div> <div class="card-handle"><i class="fa fa-bars"></i></div>
<label> <label>
<input type="checkbox" id="hscard-top_music" name="hscard-top_music" value="top_music"> Most Listened Music <input type="checkbox" id="hscard-top_music" name="hscard-top_music" value="top_music"> Most Listened to Artist
</label> </label>
</li> </li>
<li class="card card-sortable"> <li class="card card-sortable">
<div class="card-handle"><i class="fa fa-bars"></i></div> <div class="card-handle"><i class="fa fa-bars"></i></div>
<label> <label>
<input type="checkbox" id="hscard-popular_music" name="hscard-popular_music" value="popular_music"> Most Popular Music <input type="checkbox" id="hscard-popular_music" name="hscard-popular_music" value="popular_music"> Most Popular Artist
</label> </label>
</li> </li>
<li class="card card-sortable"> <li class="card card-sortable">
@@ -804,7 +832,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div> </div>
<div id="notify_recently_added_delay_error" class="alert alert-danger settings-alert" role="alert"></div> <div id="notify_recently_added_delay_error" class="alert alert-danger settings-alert" role="alert"></div>
</div> </div>
<p class="help-block">Set the delay for recently added notifications to allow metadata to be processed. Minimum 60 seconds.</p> <p class="help-block">Set the delay (in seconds) for recently added notifications to allow metadata to be processed. Minimum 60 seconds.</p>
</div> </div>
<div class="padded-header"> <div class="padded-header">
@@ -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>
@@ -1456,6 +1484,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
<tr> <tr>
<td><strong>{user}</strong></td> <td><strong>{user}</strong></td>
<td>The friendly name of the person streaming.</td>
</tr>
<tr>
<td><strong>{username}</strong></td>
<td>The username of the person streaming.</td> <td>The username of the person streaming.</td>
</tr> </tr>
<tr> <tr>
@@ -1576,7 +1608,11 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr> </tr>
<tr> <tr>
<td><strong>{session_key}</strong></td> <td><strong>{session_key}</strong></td>
<td>The unique identifier for the session.</td> <td>The unique identifier for the stream session.</td>
</tr>
<tr>
<td><strong>{transcode_key}</strong></td>
<td>The unique identifier for the transcode session.</td>
</tr> </tr>
<tr> <tr>
<td><strong>{user_id}</strong></td> <td><strong>{user_id}</strong></td>
@@ -1858,6 +1894,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 +2046,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 +2381,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>

View File

@@ -127,7 +127,7 @@ from plexpy import common
</div> </div>
<div class="wizard-card" data-cardname="card5" data-validate="validateNotifications"> <div class="wizard-card" data-cardname="card5" data-validate="validateNotifications">
<h3>Notifications</h3> <h3>Notifications</h3>
<p class="help-block">PlexPy supports a wide variety of notification options. To set up a notification agent conifgure this in <strong>Settings -> Notification Agents</strong> <p class="help-block">PlexPy supports a wide variety of notification options. To set up a notification agent configure this in <strong>Settings -> Notification Agents</strong>
after you have completed this setup wizard.</p><br/> after you have completed this setup wizard.</p><br/>
<div class="wizard-input-section"> <div class="wizard-input-section">
<input type="checkbox" name="movie_notify_enable" id="movie_notify_enable" value="1" ${config['movie_notify_enable']}> Enable notifications on Movie playback <input type="checkbox" name="movie_notify_enable" id="movie_notify_enable" value="1" ${config['movie_notify_enable']}> Enable notifications on Movie playback

View File

@@ -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:
@@ -394,8 +396,8 @@ def dbcheck():
# sessions table :: This is a temp table that logs currently active sessions # sessions table :: This is a temp table that logs currently active sessions
c_db.execute( c_db.execute(
'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, ' 'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, session_key INTEGER, '
'session_key INTEGER, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, ' 'transcode_key TEXT, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, stopped INTEGER, '
'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, ' 'paused_counter INTEGER DEFAULT 0, state TEXT, user_id INTEGER, user TEXT, friendly_name TEXT, '
'ip_address TEXT, machine_id TEXT, player TEXT, platform TEXT, title TEXT, parent_title TEXT, ' 'ip_address TEXT, machine_id TEXT, player TEXT, platform TEXT, title TEXT, parent_title TEXT, '
'grandparent_title TEXT, parent_rating_key INTEGER, grandparent_rating_key INTEGER, ' 'grandparent_title TEXT, parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
@@ -628,6 +630,15 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN stopped INTEGER' 'ALTER TABLE sessions ADD COLUMN stopped INTEGER'
) )
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT transcode_key FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN transcode_key TEXT'
)
# Upgrade session_history table from earlier versions # Upgrade session_history table from earlier versions
try: try:
c_db.execute('SELECT reference_id FROM session_history') c_db.execute('SELECT reference_id FROM session_history')
@@ -729,30 +740,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')

View File

@@ -29,6 +29,7 @@ class ActivityProcessor(object):
def write_session(self, session=None, notify=True): def write_session(self, session=None, notify=True):
if session: if session:
values = {'session_key': session['session_key'], values = {'session_key': session['session_key'],
'transcode_key': session['transcode_key'],
'section_id': session['section_id'], 'section_id': session['section_id'],
'rating_key': session['rating_key'], 'rating_key': session['rating_key'],
'media_type': session['media_type'], 'media_type': session['media_type'],

View File

@@ -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,19 @@ 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 isinstance(value, basestring) and len(value.strip()) > 5 and \
subkey.upper() not in _WHITELIST_KEYS and any(bk in subkey.upper() for bk in _BLACKLIST_KEYS):
blacklist.append(value.strip())
plexpy.logger._BLACKLIST_WORDS = blacklist
def _define(self, name): def _define(self, name):
key = name.upper() key = name.upper()
@@ -503,6 +522,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
@@ -577,4 +598,4 @@ class Config(object):
self.NOTIFY_ON_WATCHED_SUBJECT_TEXT = self.NOTIFY_ON_WATCHED_SUBJECT_TEXT.replace('{progress}','{progress_duration}') self.NOTIFY_ON_WATCHED_SUBJECT_TEXT = self.NOTIFY_ON_WATCHED_SUBJECT_TEXT.replace('{progress}','{progress_duration}')
self.NOTIFY_ON_WATCHED_BODY_TEXT = self.NOTIFY_ON_WATCHED_BODY_TEXT.replace('{progress}','{progress_duration}') self.NOTIFY_ON_WATCHED_BODY_TEXT = self.NOTIFY_ON_WATCHED_BODY_TEXT.replace('{progress}','{progress_duration}')
self.NOTIFY_SCRIPTS_ARGS_TEXT = self.NOTIFY_SCRIPTS_ARGS_TEXT.replace('{progress}','{progress_duration}') self.NOTIFY_SCRIPTS_ARGS_TEXT = self.NOTIFY_SCRIPTS_ARGS_TEXT.replace('{progress}','{progress_duration}')
self.CONFIG_VERSION = '3' self.CONFIG_VERSION = '3'

View File

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

View File

@@ -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()

View File

@@ -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:
@@ -470,12 +463,8 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
plex_tv = plextv.PlexTV() plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times() server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
if server_times: if server_times:
updated_at = server_times[0]['updated_at'] updated_at = server_times['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
else: else:
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.") logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
@@ -496,7 +485,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 +621,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:
# Retrieve the poster from Plex and cache to file try:
urllib.urlretrieve(plexpy.CONFIG.PMS_URL + thumb + '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN, # Retrieve the poster from Plex and cache to file
os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg')) urllib.urlretrieve(plexpy.CONFIG.PMS_URL + thumb + '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN,
# Upload thumb to Imgur and get link os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'))
poster_url = helpers.uploadToImgur(os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'), poster_title) # Upload thumb to Imgur and get link
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
@@ -657,13 +649,14 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
available_params = {# Global paramaters available_params = {# Global paramaters
'server_name': server_name, 'server_name': server_name,
'server_uptime': server_uptime, 'server_uptime': server_uptime,
'server_version': server_identity.get('version',''), 'server_version': server_times.get('version',''),
'action': notify_action.title(), 'action': notify_action.title(),
'datestamp': arrow.now().format(date_format), 'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format), 'timestamp': arrow.now().format(time_format),
# Stream parameters # Stream parameters
'streams': stream_count, 'streams': stream_count,
'user': session.get('friendly_name',''), 'user': session.get('friendly_name',''),
'username': session.get('user',''),
'platform': session.get('platform',''), 'platform': session.get('platform',''),
'player': session.get('player',''), 'player': session.get('player',''),
'ip_address': session.get('ip_address','N/A'), 'ip_address': session.get('ip_address','N/A'),
@@ -694,6 +687,7 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
'transcode_audio_codec': session.get('transcode_audio_codec',''), 'transcode_audio_codec': session.get('transcode_audio_codec',''),
'transcode_audio_channels': session.get('transcode_audio_channels',''), 'transcode_audio_channels': session.get('transcode_audio_channels',''),
'session_key': session.get('session_key',''), 'session_key': session.get('session_key',''),
'transcode_key': session.get('transcode_key',''),
'user_id': session.get('user_id',''), 'user_id': session.get('user_id',''),
'machine_id': session.get('machine_id',''), 'machine_id': session.get('machine_id',''),
# Metadata parameters # Metadata parameters
@@ -747,9 +741,9 @@ def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=
try: try:
script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()] script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()]
except LookupError as e: except LookupError as e:
logger.error(u"PlexPy Notifier :: Unable to parse field %s in script argument. Using fallback." % e) logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in script argument. Using fallback." % e)
except Exception as e: except Exception as e:
logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e) logger.error(u"PlexPy NotificationHandler :: Unable to parse custom script arguments %s. Using fallback." % e)
if notify_action == 'play': if notify_action == 'play':
# Default body text # Default body text
@@ -918,7 +912,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):
@@ -933,16 +927,12 @@ def build_server_notify_text(notify_action=None, agent_id=None):
plex_tv = plextv.PlexTV() plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times() server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = {} update_status = {}
if notify_action == 'pmsupdate': if notify_action == 'pmsupdate':
update_status = pms_connect.get_update_staus() update_status = pms_connect.get_update_staus()
if server_times: if server_times:
updated_at = server_times[0]['updated_at'] updated_at = server_times['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at))) server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
else: else:
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.") logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
@@ -965,7 +955,7 @@ def build_server_notify_text(notify_action=None, agent_id=None):
available_params = {# Global paramaters available_params = {# Global paramaters
'server_name': server_name, 'server_name': server_name,
'server_uptime': server_uptime, 'server_uptime': server_uptime,
'server_version': server_identity.get('version',''), 'server_version': server_times.get('version',''),
'action': notify_action.title(), 'action': notify_action.title(),
'datestamp': arrow.now().format(date_format), 'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format), 'timestamp': arrow.now().format(time_format),
@@ -984,9 +974,9 @@ def build_server_notify_text(notify_action=None, agent_id=None):
try: try:
script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()] script_args = [unicode(arg).format(**available_params) for arg in script_args_text.split()]
except LookupError as e: except LookupError as e:
logger.error(u"PlexPy Notifier :: Unable to parse field %s in script argument. Using fallback." % e) logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in script argument. Using fallback." % e)
except Exception as e: except Exception as e:
logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e) logger.error(u"PlexPy NotificationHandler :: Unable to parse custom script arguments %s. Using fallback." % e)
if notify_action == 'extdown': if notify_action == 'extdown':
# Default body text # Default body text

View File

@@ -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)
@@ -487,7 +487,7 @@ def send_notification(agent_id, subject, body, **kwargs):
slackClient.notify(message=body, event=subject) slackClient.notify(message=body, event=subject)
elif agent_id == 15: elif agent_id == 15:
scripts = Scripts() scripts = Scripts()
scripts.notify(message=body, subject=subject, **kwargs) scripts.notify(message=body, subject=subject, notify_action=notify_action, **kwargs)
elif agent_id == 16: elif agent_id == 16:
facebook = FacebookNotifier() facebook = FacebookNotifier()
facebook.notify(subject=subject, message=body, **kwargs) facebook.notify(subject=subject, message=body, **kwargs)
@@ -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'
} }
@@ -1773,7 +1776,7 @@ class SLACK(object):
if urlparse(self.icon_emoji).scheme == '': if urlparse(self.icon_emoji).scheme == '':
data['icon_emoji'] = self.icon_emoji data['icon_emoji'] = self.icon_emoji
else: else:
data['icon_url'] = self.icon_url data['icon_url'] = self.icon_emoji
url = urlparse(self.slack_hook).path url = urlparse(self.slack_hook).path
@@ -1881,7 +1884,7 @@ class Scripts(object):
script_args(list): ["python2", '-p', '-zomg'] script_args(list): ["python2", '-p', '-zomg']
""" """
logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" % logger.debug(u"PlexPy Notifiers :: Trying to run notify script, action: %s, arguments: %s" %
(notify_action if notify_action else None, script_args if script_args else None)) (notify_action or None, script_args or None))
if script_args is None: if script_args is None:
script_args = [] script_args = []
@@ -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,74 +2172,83 @@ 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 = ''
if poster_url: # Use default posters if no poster_url
if metadata['media_type'] == 'movie': if not poster_url:
title = metadata['title'] if metadata['media_type'] in ['artist', 'track']:
subtitle = metadata['year'] poster_url = 'https://raw.githubusercontent.com/drzoidberg33/plexpy/master/data/interfaces/default/images/cover.png'
rating_key = metadata['rating_key']
if metadata.get('imdb_url',''):
poster_link = metadata.get('imdb_url', '')
caption = 'View on IMDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'show':
title = metadata['title']
subtitle = metadata['year']
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'episode':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
subtitle = 'S%s %s E%s' % (metadata['parent_media_index'],
'\xc2\xb7'.decode('utf8'),
metadata['media_index'])
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB.'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database.'
elif metadata['media_type'] == 'artist':
title = metadata['title']
subtitle = ''
rating_key = metadata['rating_key']
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm.'
elif metadata['media_type'] == 'track':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
subtitle = metadata['parent_title']
rating_key = metadata['parent_rating_key']
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm.'
# Build Facebook post attachment
if self.incl_pmslink:
caption = 'View on Plex Web.'
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
attachment['caption'] = caption
elif poster_link:
attachment['link'] = poster_link
attachment['caption'] = caption
else: else:
attachment['link'] = poster_url poster_url = 'https://raw.githubusercontent.com/drzoidberg33/plexpy/master/data/interfaces/default/images/poster.png'
attachment['picture'] = poster_url if metadata['media_type'] == 'movie':
attachment['name'] = title title = '%s (%s)' % (metadata['title'], metadata['year'])
attachment['description'] = subtitle subtitle = metadata['summary']
rating_key = metadata['rating_key']
if metadata.get('imdb_url',''):
poster_link = metadata.get('imdb_url', '')
caption = 'View on IMDB'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database'
elif metadata['media_type'] == 'show':
title = '%s (%s)' % (metadata['title'], metadata['year'])
subtitle = metadata['summary']
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database'
elif metadata['media_type'] == 'episode':
title = '%s - %s (S%s %s E%s)' % (metadata['grandparent_title'],
metadata['title'],
metadata['parent_media_index'],
'\xc2\xb7'.decode('utf8'),
metadata['media_index'])
subtitle = metadata['summary']
rating_key = metadata['rating_key']
if metadata.get('thetvdb_url',''):
poster_link = metadata.get('thetvdb_url', '')
caption = 'View on TheTVDB'
elif metadata.get('themoviedb_url',''):
poster_link = metadata.get('themoviedb_url', '')
caption = 'View on The Movie Database'
elif metadata['media_type'] == 'artist':
title = metadata['title']
subtitle = metadata['summary']
rating_key = metadata['rating_key']
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm'
elif metadata['media_type'] == 'track':
title = '%s - %s' % (metadata['grandparent_title'], metadata['title'])
subtitle = metadata['parent_title']
rating_key = metadata['parent_rating_key']
if metadata.get('lastfm_url',''):
poster_link = metadata.get('lastfm_url', '')
caption = 'View on Last.fm'
# Build Facebook post attachment
if self.incl_pmslink:
caption = 'View on Plex Web'
attachment['link'] = 'http://app.plex.tv/web/app#!/server/' + plexpy.CONFIG.PMS_IDENTIFIER + \
'/details/%2Flibrary%2Fmetadata%2F' + rating_key
elif poster_link:
attachment['link'] = poster_link
else:
attachment['link'] = poster_url
attachment['picture'] = poster_url
attachment['name'] = title
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',

View File

@@ -71,32 +71,37 @@ def get_real_pms_url():
if plexpy.CONFIG.PMS_SSL: if plexpy.CONFIG.PMS_SSL:
result = PlexTV().get_server_urls(include_https=True) result = PlexTV().get_server_urls(include_https=True)
process_urls = True
elif plexpy.CONFIG.PMS_IS_REMOTE:
result = PlexTV().get_server_urls(include_https=False)
process_urls = True
else: else:
result = PlexTV().get_server_urls(include_https=False) result = PlexTV().get_server_urls(include_https=False)
process_urls = False
if process_urls: # Only need to retrieve PMS_URL if using SSL
if plexpy.CONFIG.PMS_SSL:
if result: if result:
for item in result: if plexpy.CONFIG.PMS_IS_REMOTE:
if plexpy.CONFIG.PMS_IS_REMOTE and item['local'] == '0': # Get all remote connections
plexpy.CONFIG.__setattr__('PMS_URL', item['uri']) connections = [c for c in result if c['local'] == '0' and 'plex.direct' in c['uri']]
plexpy.CONFIG.write() else:
logger.info(u"PlexPy PlexTV :: Server URL retrieved.") # Get all local connections
if not plexpy.CONFIG.PMS_IS_REMOTE and item['local'] == '1' and 'plex.direct' in item['uri']: connections = [c for c in result if c['local'] == '1' and 'plex.direct' in c['uri']]
plexpy.CONFIG.__setattr__('PMS_URL', item['uri'])
plexpy.CONFIG.write() if connections:
logger.info(u"PlexPy PlexTV :: Server URL retrieved.") # Get connection with matching address, otherwise return first connection
else: conn = next((c for c in connections if c['address'] == plexpy.CONFIG.PMS_IP), connections[0])
plexpy.CONFIG.__setattr__('PMS_URL', conn['uri'])
plexpy.CONFIG.write()
logger.info(u"PlexPy PlexTV :: Server URL retrieved.")
# get_server_urls() failed or PMS_URL not found, fallback url doesn't use SSL
if not plexpy.CONFIG.PMS_URL:
plexpy.CONFIG.__setattr__('PMS_URL', fallback_url) plexpy.CONFIG.__setattr__('PMS_URL', fallback_url)
plexpy.CONFIG.write() plexpy.CONFIG.write()
logger.warn(u"PlexPy PlexTV :: Unable to retrieve server URLs. Using user-defined value.") logger.warn(u"PlexPy PlexTV :: Unable to retrieve server URLs. Using user-defined value without SSL.")
# Not using SSL, remote has no effect
else: else:
plexpy.CONFIG.__setattr__('PMS_URL', fallback_url) plexpy.CONFIG.__setattr__('PMS_URL', fallback_url)
plexpy.CONFIG.write() plexpy.CONFIG.write()
logger.info(u"PlexPy PlexTV :: Using user-defined URL.")
class PlexTV(object): class PlexTV(object):
@@ -450,19 +455,20 @@ class PlexTV(object):
def get_server_times(self): def get_server_times(self):
servers = self.get_plextv_server_list(output_format='xml') servers = self.get_plextv_server_list(output_format='xml')
server_times = [] server_times = {}
try: try:
xml_head = servers.getElementsByTagName('Server') xml_head = servers.getElementsByTagName('Server')
except Exception as e: except Exception as e:
logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_server_times: %s." % e) logger.warn(u"PlexPy PlexTV :: Unable to parse XML for get_server_times: %s." % e)
return [] return {}
for a in xml_head: for a in xml_head:
if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER: if helpers.get_xml_attr(a, 'machineIdentifier') == plexpy.CONFIG.PMS_IDENTIFIER:
server_times.append({"created_at": helpers.get_xml_attr(a, 'createdAt'), server_times = {"created_at": helpers.get_xml_attr(a, 'createdAt'),
"updated_at": helpers.get_xml_attr(a, 'updatedAt') "updated_at": helpers.get_xml_attr(a, 'updatedAt'),
}) "version": helpers.get_xml_attr(a, 'version')
}
break break
return server_times return server_times

View File

@@ -939,7 +939,7 @@ class PmsConnect(object):
try: try:
xml_head = session_data.getElementsByTagName('MediaContainer') xml_head = session_data.getElementsByTagName('MediaContainer')
except Exception as e: except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_sessions: %s." % e) logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_current_activity: %s." % e)
return [] return []
session_list = [] session_list = []
@@ -1001,6 +1001,7 @@ class PmsConnect(object):
if session.getElementsByTagName('TranscodeSession'): if session.getElementsByTagName('TranscodeSession'):
transcode_session = session.getElementsByTagName('TranscodeSession')[0] transcode_session = session.getElementsByTagName('TranscodeSession')[0]
transcode_key = helpers.get_xml_attr(transcode_session, 'key')
throttled = helpers.get_xml_attr(transcode_session, 'throttled') throttled = helpers.get_xml_attr(transcode_session, 'throttled')
transcode_progress = helpers.get_xml_attr(transcode_session, 'progress') transcode_progress = helpers.get_xml_attr(transcode_session, 'progress')
transcode_speed = helpers.get_xml_attr(transcode_session, 'speed') transcode_speed = helpers.get_xml_attr(transcode_session, 'speed')
@@ -1011,6 +1012,7 @@ class PmsConnect(object):
transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol') transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol')
duration = helpers.get_xml_attr(transcode_session, 'duration') duration = helpers.get_xml_attr(transcode_session, 'duration')
else: else:
transcode_key = ''
throttled = '0' throttled = '0'
transcode_progress = '0' transcode_progress = '0'
transcode_speed = '' transcode_speed = ''
@@ -1051,6 +1053,7 @@ class PmsConnect(object):
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
'transcode_key': transcode_key,
'throttled': throttled, 'throttled': throttled,
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)), 'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)), 'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
@@ -1099,6 +1102,7 @@ class PmsConnect(object):
if session.getElementsByTagName('TranscodeSession'): if session.getElementsByTagName('TranscodeSession'):
transcode_session = session.getElementsByTagName('TranscodeSession')[0] transcode_session = session.getElementsByTagName('TranscodeSession')[0]
transcode_key = helpers.get_xml_attr(transcode_session, 'key')
throttled = helpers.get_xml_attr(transcode_session, 'throttled') throttled = helpers.get_xml_attr(transcode_session, 'throttled')
transcode_progress = helpers.get_xml_attr(transcode_session, 'progress') transcode_progress = helpers.get_xml_attr(transcode_session, 'progress')
transcode_speed = helpers.get_xml_attr(transcode_session, 'speed') transcode_speed = helpers.get_xml_attr(transcode_session, 'speed')
@@ -1112,6 +1116,7 @@ class PmsConnect(object):
transcode_container = helpers.get_xml_attr(transcode_session, 'container') transcode_container = helpers.get_xml_attr(transcode_session, 'container')
transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol') transcode_protocol = helpers.get_xml_attr(transcode_session, 'protocol')
else: else:
transcode_key = ''
throttled = '0' throttled = '0'
transcode_progress = '0' transcode_progress = '0'
transcode_speed = '' transcode_speed = ''
@@ -1174,6 +1179,7 @@ class PmsConnect(object):
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
'transcode_key': transcode_key,
'throttled': throttled, 'throttled': throttled,
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)), 'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)), 'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
@@ -1232,6 +1238,7 @@ class PmsConnect(object):
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
'transcode_key': transcode_key,
'throttled': throttled, 'throttled': throttled,
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)), 'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)), 'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
@@ -1290,6 +1297,7 @@ class PmsConnect(object):
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
'transcode_key': transcode_key,
'throttled': throttled, 'throttled': throttled,
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)), 'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)), 'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),
@@ -1329,6 +1337,7 @@ class PmsConnect(object):
if session.getElementsByTagName('TranscodeSession'): if session.getElementsByTagName('TranscodeSession'):
transcode_session = session.getElementsByTagName('TranscodeSession')[0] transcode_session = session.getElementsByTagName('TranscodeSession')[0]
transcode_key = helpers.get_xml_attr(transcode_session, 'key')
throttled = helpers.get_xml_attr(transcode_session, 'throttled') throttled = helpers.get_xml_attr(transcode_session, 'throttled')
transcode_progress = helpers.get_xml_attr(transcode_session, 'progress') transcode_progress = helpers.get_xml_attr(transcode_session, 'progress')
transcode_speed = helpers.get_xml_attr(transcode_session, 'speed') transcode_speed = helpers.get_xml_attr(transcode_session, 'speed')
@@ -1381,6 +1390,7 @@ class PmsConnect(object):
'rating_key': helpers.get_xml_attr(session, 'ratingKey'), 'rating_key': helpers.get_xml_attr(session, 'ratingKey'),
'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'), 'parent_rating_key': helpers.get_xml_attr(session, 'parentRatingKey'),
'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'), 'grandparent_rating_key': helpers.get_xml_attr(session, 'grandparentRatingKey'),
'transcode_key': transcode_key,
'throttled': throttled, 'throttled': throttled,
'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)), 'transcode_progress': int(round(helpers.cast_to_float(transcode_progress), 0)),
'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)), 'transcode_speed': str(round(helpers.cast_to_float(transcode_speed), 1)),

View File

@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master" PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.3.11" PLEXPY_RELEASE_VERSION = "1.3.15"

View File

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

View File

@@ -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 Id 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)
@@ -2248,4 +2267,4 @@ class WebInterface(object):
def check_pms_updater(self): def check_pms_updater(self):
pms_connect = pmsconnect.PmsConnect() pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_update_staus() result = pms_connect.get_update_staus()
return json.dumps(result) return json.dumps(result)

View File

@@ -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'])
cherrypy.server.start() if not plexpy.DEV:
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)