Compare commits

...

118 Commits

Author SHA1 Message Date
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
JonnyWong16
cbf3488de9 Merge branch 'dev' 2016-03-15 19:15:46 -07:00
JonnyWong16
c72314fb71 v1.3.11 2016-03-15 19:14:56 -07:00
JonnyWong16
c4af6feb92 Fix typo preventing history logging for websockets 2016-03-15 19:13:13 -07:00
JonnyWong16
08537c1d69 Merge branch 'dev' 2016-03-12 15:04:40 -08:00
JonnyWong16
425da82f5f v1.3.10 2016-03-12 15:04:16 -08:00
JonnyWong16
2cfbf7c39a Rename "watched" to "played" on user/library pages 2016-03-12 14:35:29 -08:00
JonnyWong16
d4eed9f8fd Merge pull request #594 from chiviak/160223_freenas_dev
Few improvements to the FreeNAS/FreeBSD init scripts
2016-03-09 22:13:24 -08:00
JonnyWong16
fe10170826 Fix checked settings to int when saving config 2016-03-09 22:11:23 -08:00
JonnyWong16
8dc3b0b250 Do not retrieve user/library details if importing plexWatch database 2016-03-09 22:10:33 -08:00
JonnyWong16
75da1220af Fix expanding media info tables from 464d2a5 2016-03-08 18:23:58 -08:00
JonnyWong16
37a2c3c631 Fix typo in notification settings 2016-03-08 18:21:01 -08:00
JonnyWong16
5c5722714d Add ability to clear the temporary sessions table from database 2016-03-07 19:40:18 -08:00
JonnyWong16
2ba529f9e3 Fix missing time import 2016-03-06 17:06:05 -08:00
JonnyWong16
fd760ff015 Fix missing notifiers import 2016-03-06 17:05:52 -08:00
Scott Serrano
daab1d917b Update FreeBSD script to match the latest FreeNAS changes 2016-03-05 21:14:53 -08:00
Scott Serrano
4c3a63a7e1 Allow for additional plexpy arguments like port 2016-03-05 21:14:48 -08:00
Scott Serrano
7cc58b84da Run plexpy directly in daemon mode instead of using the daemon program 2016-03-05 21:14:42 -08:00
Scott Serrano
2bac4ac1a7 Make the freenas init script executable 2016-03-05 21:14:35 -08:00
JonnyWong16
c5b2b86786 Enable keep_history for default user/library
* Log sessions to "Local" if retrieving user/library data fails
2016-03-05 16:21:59 -08:00
JonnyWong16
5652a2b6c2 Revert set_session_state from d73e379 2016-03-05 16:05:32 -08:00
JonnyWong16
bd19f543a2 Merge pull request #586 from JonnyWong16/websockets-watched-notify-fix
Check if notification agents enabled before sending notifications
2016-03-05 13:22:11 -08:00
JonnyWong16
cc1e888227 Check if notification agents enabled before sending notifications 2016-03-05 13:20:28 -08:00
JonnyWong16
d73e379dcf Do not remove session from db until it is successfully written
* For activity pinger only
2016-03-05 13:07:26 -08:00
JonnyWong16
0569abd00d Add customizable backup, cache, and log directory 2016-03-04 23:41:18 -08:00
JonnyWong16
7f5d9bec87 Add button to clear notification logs 2016-03-04 23:13:19 -08:00
JonnyWong16
b39e7bbb6d Do not strip newlines from notification text
* Behaviour is more predictable this way
2016-03-04 22:50:21 -08:00
JonnyWong16
70270a8e3b Hide days selection from Play Totals graph 2016-03-04 22:27:52 -08:00
JonnyWong16
efdc050a28 Filter history modal on graphs based on clicked series 2016-03-04 22:21:34 -08:00
JonnyWong16
e8a65df7f0 Add transcode_decision to media_info table 2016-03-04 22:20:15 -08:00
JonnyWong16
59628a72fb Fix typo in PlexWatch importer 2016-03-04 22:19:16 -08:00
JonnyWong16
a4d6c6c0d8 Fix datatables modal popups from 464d2a5 2016-03-04 22:18:34 -08:00
JonnyWong16
bea82c6640 Fix IPv6 address 2016-03-03 14:26:39 -08:00
drzoidberg33
1ba3bdfbda Don't check for PMS updates every 10 seconds. 2016-03-02 14:00:30 +02:00
JonnyWong16
98b4000bc0 Add ability to reset Imgur posters from info page 2016-03-01 23:29:49 -08:00
JonnyWong16
14f6824931 Use Parsley to verify pms logs folder is not a shortcut 2016-03-01 21:49:12 -08:00
JonnyWong16
795d7d0a93 Add ability to get notified of PMS updates 2016-03-01 21:04:57 -08:00
JonnyWong16
673fa2b556 Fix auto-refresh of log tabs 2016-03-01 20:31:45 -08:00
JonnyWong16
0e2504fc78 Document remaining time format options 2016-03-01 20:31:21 -08:00
JonnyWong16
2afca9f2b4 Alert if PMS logs folder is a shortcut 2016-02-28 15:39:13 -08:00
JonnyWong16
464d2a541d Give tables unique ids to save state indivdually 2016-02-27 13:39:21 -08:00
JonnyWong16
fa8c5e0982 Only use user_id in current activity link to user page 2016-02-27 01:18:40 -08:00
JonnyWong16
5e15884d8f Fix scrollers when items don't fill up the row 2016-02-27 01:00:12 -08:00
JonnyWong16
b5e9ff3b4e Add scrolling recently watched and added to user and library pages 2016-02-27 00:00:11 -08:00
JonnyWong16
fed7d4cc34 Add scrolling recently added to homepage 2016-02-26 23:53:03 -08:00
JonnyWong16
d7ab066ff8 Revert datatables save state to true 2016-02-26 21:57:22 -08:00
JonnyWong16
4100917016 Add ability to disable Facebook poster link to Plex Web 2016-02-26 21:39:41 -08:00
JonnyWong16
5d2c1ffb88 Fix bug in checking for PMS version in settings 2016-02-26 19:17:42 -08:00
JonnyWong16
ddb0f198a9 Add tooltip to current activity progress bars 2016-02-25 21:56:23 -08:00
JonnyWong16
13438e3e25 Anonymize more URLs 2016-02-25 09:57:05 -08:00
JonnyWong16
05a410b327 Catch blank view_offset or duration in history table query 2016-02-24 21:40:29 -08:00
JonnyWong16
23fa64d289 Change colour of grouped recently added note on checkbox toggle 2016-02-24 21:40:19 -08:00
JonnyWong16
1920c9b7e3 Reconnect websocket on server change 2016-02-23 19:05:20 -08:00
JonnyWong16
eedd0d9c07 Use subprocess.Popen on windows to restart PlexPy
* See python bug: https://bugs.python.org/issue19066
2016-02-23 18:29:33 -08:00
JonnyWong16
9ef389d335 Actually allow HTML tags for Pushover 2016-02-22 21:20:05 -08:00
JonnyWong16
6542997520 Merge branch 'dev' 2016-02-21 23:07:33 -08:00
JonnyWong16
a58b2e2038 v1.3.9 2016-02-21 23:06:22 -08:00
JonnyWong16
6860e348dc Fix typo in setting recently added notification state 2016-02-21 23:01:18 -08:00
JonnyWong16
5e094e7597 Merge pull request #477 from elseym/pushover-html-support
Pushover HTML Support
2016-02-21 21:35:07 -08:00
JonnyWong16
2f2cb8386b Change wording for enable posters in notification help text 2016-02-21 20:41:18 -08:00
JonnyWong16
965fd170bd Merge branch 'dev' 2016-02-21 20:32:14 -08:00
JonnyWong16
2610d29b60 v1.3.8 2016-02-21 20:31:51 -08:00
JonnyWong16
064131c842 Uncheck monitor remote access if remote access diabled on server 2016-02-21 17:20:18 -08:00
JonnyWong16
00b6bf8394 Return default ip_address/poster_url if database query fails 2016-02-21 17:11:49 -08:00
JonnyWong16
2a885d709d Allow disabling poster upload to Imgur
* Disabled by default
2016-02-21 17:06:05 -08:00
JonnyWong16
5bed46c0aa Encode poster title to UTF-8 for Imgur upload 2016-02-21 17:05:10 -08:00
JonnyWong16
8b27c7e01a Remove poster url from notification logs table 2016-02-21 16:42:29 -08:00
JonnyWong16
177902a286 Remove media tags from script_args for server notifications 2016-02-21 16:42:08 -08:00
JonnyWong16
48b0f7dc27 Fix NoneType error in set_notify_state 2016-02-21 16:33:42 -08:00
JonnyWong16
d5f4a1a48a Make readme consistent with settings page 2016-02-21 16:18:26 -08:00
JonnyWong16
de9f60aa7f Add notification log table 2016-02-21 15:45:28 -08:00
JonnyWong16
c93b65b299 Rework notify_log table to save each notification separately 2016-02-21 15:44:21 -08:00
JonnyWong16
3c6a6cdc5b Fix wording on settings page 2016-02-21 14:56:19 -08:00
JonnyWong16
b669f3d715 Fix regression unable to clear the http password 2016-02-21 09:58:27 -08:00
JonnyWong16
f663fac220 Save Imgur URL to database 2016-02-21 09:34:51 -08:00
JonnyWong16
bc42e79bb5 Catch HTTP errors for Imgur upload 2016-02-21 09:33:31 -08:00
JonnyWong16
ca29333cd0 Log if opening secure websocket 2016-02-20 20:50:20 -08:00
JonnyWong16
f9f478e100 Update CONTRIBUTING.md with info about issue reporting and feature requests 2016-02-20 20:47:33 -08:00
elseym
0873beaed2 enable pushover html support by default, introduce option to deactivate 2016-01-24 18:06:36 +01:00
57 changed files with 2877 additions and 1126 deletions

View File

@@ -1,5 +1,85 @@
# Changelog
## 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)
* Fix: Typo preventing history logging for websockets.
## v1.3.10 (2016-03-12)
* Fix: Actually allow HTML tags for Pushover.
* Fix: PlexPy not restarting on Windows if there is a space in the folder path.
* Fix: Reconnect websocket when changing PMS SSL setting.
* Fix: Datatables not loading when view_offset or duration is blank.
* Fix: Bug when checking the PMS version in the settings.
* Fix: Auto-refreshing of log tables.
* Fix: Logging of IPv6 addresses. (PMS version >0.9.14 only.)
* Fix: Hide days selection from the Play Totals graph page.
* Fix: PlexPy overwriting user's own SSL certificate/key.
* Fix: Multiple watched notifications when using websocket.
* Fix: Some missing python library imports.
* Fix: Some typos in settings and PlexWatch importer.
* New: Ability to get notified of PMS updates.
* New: Ability to disable the link to Plex Web with Facebook notifications and use IMDB, TVDB, TMDb, or Last.fm instead.
* New: Ability to reset Imgur poster url from the info page if the poster is changed.
* New: Tooltips on the current activity progress bars.
* New: Side scrolling of Recently Added/Recently Played items.
* New: Document all date/time format options.
* New: Button to clear notification logs.
* New: Customizable backup, cache, and log directories.
* Change: Retry writing sessions to history if it fails, so sessions don't get lost. (Activity pinger only, not availble for websocket.)
* Change: Save any unknown sessions to the "Local" user.
* Change: History table modal is filtered depending on which graph series is clicked.
* Change: Revert back to saving the state of datatables (search, sorting, entries per page, etc.).
* Change: Newlines are not longer stripped from notification text which allows for finer control of how notifications look.
* Change: Updated FreeNAS/FreeBSD init scripts. (Must have updated jails.) (Thanks @chiviak)
## v1.3.9 (2016-02-21)
* Fix: Recently added notification not sent to all notification agents.
* New: Pushover HTML support. (Thanks @elseym)
## v1.3.8 (2016-02-21)
* Fix: Regression unable to clear HTTP password.
* Fix: Remove media tags from script arguments for server notifications.
* Fix: Encode poster titles to UTF-8 for Imgur upload.
* Fix: Allow notifications to send without poster if Imgur upload fails.
* New: Notification Logs table in the Logs tab.
* New: Toggle in settings to enable posters in notifications. (Disabled by default.)
* Change: Save Imgur poster URL to database so upload is not needed every time.
* Change: Notify log in database to log each event as a separate entry.
* Change: Monitor remote access is unchecked if remote access is disabled on server.
## v1.3.7 (2016-02-20)
* Fix: Verifying server with SSL enabled.
@@ -7,18 +87,18 @@
* Fix: Video metadata flags showing up for track info.
* Fix: Custom library icons not applied to Library Statistics.
* Fix: Typos in the Web UI.
* Add: ETA to Current Activity overlay.
* Add: Total duration to Libraries and Users tables.
* Add: {machine_id} to notification options.
* Add: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
* Add: {poster_url} to notification options using Imgur.
* Add: Poster and link for Facebook notifications.
* Add: Log javascript errors from the Web UI.
* Add: Configuration and Scheduler info to the settings page.
* Add: Schedule background task to backup the PlexPy database.
* Add: URL anonymizer for external links.
* Add: Plex Media Scanner log file to Log viewer.
* Add: API v2 (sill very experimental) (Thanks @Hellowlol)
* New: ETA to Current Activity overlay.
* New: Total duration to Libraries and Users tables.
* New: {machine_id} to notification options.
* New: IMDB, TVDB, TMDb, Last.fm, and Trackt IDs/URLs to notification options.
* New: {poster_url} to notification options using Imgur.
* New: Poster and link for Facebook notifications.
* New: Log javascript errors from the Web UI.
* New: Configuration and Scheduler info to the settings page.
* New: Schedule background task to backup the PlexPy database.
* New: URL anonymizer for external links.
* New: Plex Media Scanner log file to Log viewer.
* New: API v2 (sill very experimental). (Thanks @Hellowlol)
* Change: Allow secure websocket connections.
* Change: History grouping now accounts for the view offset.
* Change: Subject line can be toggled off for Facebook, Slack, Telegram, and Twitter.
@@ -41,9 +121,9 @@
* Fix: Unable to expand media info table when missing "Added At" date.
* Fix: Server verification for unpublished servers.
* Fix: Updating PMS identifier for server change.
* Add: {stream_time}, {remaining_time}, and {progress_time} to notification options.
* Add: Powershell script support. (Thanks @Hellowlol)
* Add: Method to delete duplicate libraries.
* New: {stream_time}, {remaining_time}, and {progress_time} to notification options.
* New: Powershell script support. (Thanks @Hellowlol)
* New: Method to delete duplicate libraries.
* Change: Daemonize before running start up tasks.
@@ -55,7 +135,7 @@
* Fix: Libraries and Users lists not refreshing.
* Fix: Server verification in settings.
* Fix: Empty libraries not added to database.
* Add: Unique identifiers to notification options.
* New: Unique identifiers to notification options.
* Remove: Requirement of media type toggles for recently added notifications.
* Remove: Built in Twitter key and secret.
* Change: Unnecessary quoting of script arguments.
@@ -88,21 +168,21 @@
## v1.3.0 (2016-01-23)
* Add: Brand new Libraries section.
* Add: Lots of new library statistics.
* Add: Media info table for libraries.
* Add: Web app for Android and iOS. (Thanks @zobe123)
* Add: Slack notification agent. (Thanks @richipargo)
* Add: Facebook notification agent.
* Add: Custom script notification agent. (Thanks @Hellowlol)
* Add: Custom "From Name" to email notification agent.
* Add: Ability to test notifications / send custom one-off notifications.
* Add: 'datestamp' and 'timestamp' notification options.
* Add: More concurrent stream statistics.
* Add: Media info flags on the info pages.
* Add: Ability to fix broken metadata if the item has been moved in Plex.
* Add: Ability to rearrange the homepage statistics cards.
* Add: CentOS startup script (Thanks @PHoSawyer)
* New: Brand new Libraries section.
* New: Lots of new library statistics.
* New: Media info table for libraries.
* New: Web app for Android and iOS. (Thanks @zobe123)
* New: Slack notification agent. (Thanks @richipargo)
* New: Facebook notification agent.
* New: Custom script notification agent. (Thanks @Hellowlol)
* New: Custom "From Name" to email notification agent.
* New: Ability to test notifications / send custom one-off notifications.
* New: 'datestamp' and 'timestamp' notification options.
* New: More concurrent stream statistics.
* New: Media info flags on the info pages.
* New: Ability to fix broken metadata if the item has been moved in Plex.
* New: Ability to rearrange the homepage statistics cards.
* New: CentOS startup script (Thanks @PHoSawyer)
* Fix: Server name blank after first run wizard.
* Fix: Incorrect duration for grouped home stats.
* Fix: Allow SSL when verifying server in settings.

View File

@@ -1,12 +1,45 @@
# Contributing to PlexPy
## 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.
* Use the search function. Chances are that your problem is already discussed. Do not append to (closed) issues if your problem does not fit the discussion.
* Visit the [Troubleshooting](../../wiki/TroubleShooting) wiki first.
* Use [proper formatting](https://help.github.com/articles/github-flavored-markdown/). Paste your logs in code blocks.
* Close your issue if you resolved it.
##### Many issues can simply be solved by:
- Making sure you update to the latest version.
- Turning your device off and on again.
- Analyzing your logs, you just might find the solution yourself!
- Using the **search** function to see if this issue has already been reported/solved.
- Checking the [Wiki](https://github.com/drzoidberg33/plexpy/wiki) for
[ [Installation] ](https://github.com/drzoidberg33/plexpy/wiki/Installation) and
[ [FAQs] ](https://github.com/drzoidberg33/plexpy/wiki/Frequently-Asked-Questions-(FAQ)).
- For basic questions try asking on [Gitter](https://gitter.im/drzoidberg33/plexpy) or the [Plex Forums](https://forums.plex.tv/discussion/169591/plexpy-another-plex-monitoring-program) first before opening an issue.
##### If nothing has worked:
1. Open a new issue on the GitHub [issue tracker](http://github.com/drzoidberg33/plexpy/issues).
2. Provide a clear title to easily help identify your problem.
3. Use proper [markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your post (i.e. code/log in code blocks).
4. Make sure you provide the following information:
- [ ] Version
- [ ] Branch
- [ ] Commit hash
- [ ] Operating system
- [ ] Python version
- [ ] What you did?
- [ ] What happened?
- [ ] What you expected?
- [ ] How can we reproduce your issue?
- [ ] What are your (relevant) settings?
- [ ] Include a link to your **FULL** (not just a few lines!) log file that has the error. Please use [Gist](http://gist.github.com) or [Pastebin](http://pastebin.com/).
5. Close your issue when it's solved! If you found the solution yourself please comment so that others benefit from it.
## Feature Requests
Feature requests are handled on [FeatHub](http://feathub.com/drzoidberg33/plexpy).
1. Search the existing requests to see if your suggestion has already been submitted.
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
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
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 -*-
# This file is part of PlexPy.
@@ -76,11 +81,14 @@ def main():
'-d', '--daemon', action='store_true', help='Run as a daemon')
parser.add_argument(
'-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(
'--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('--nolaunch', action='store_true',
help='Prevent browser from launching on startup')
parser.add_argument(
'--config', help='Specify a config file to use')
parser.add_argument(
'--nolaunch', action='store_true', help='Prevent browser from launching on startup')
parser.add_argument(
'--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,
verbose=plexpy.VERBOSE)
if args.dev:
plexpy.DEV = True
logger.debug(u"PlexPy is running in the dev environment.")
if args.daemon:
if sys.platform == 'win32':
sys.stderr.write(
@@ -159,6 +171,19 @@ def main():
# Read config and start logging
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
if args.port:
http_port = args.port
@@ -181,6 +206,7 @@ def main():
'http_port': http_port,
'http_host': plexpy.CONFIG.HTTP_HOST,
'http_root': plexpy.CONFIG.HTTP_ROOT,
'http_environment': plexpy.CONFIG.HTTP_ENVIRONMENT,
'http_proxy': plexpy.CONFIG.HTTP_PROXY,
'enable_https': plexpy.CONFIG.ENABLE_HTTPS,
'https_cert': plexpy.CONFIG.HTTPS_CERT,
@@ -190,21 +216,8 @@ def main():
}
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
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.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)
## Features
* Responsive web design viewable on desktop, tablet and mobile web browsers.
* Themed to complement Plex/Web.
* Easy configuration setup (no separate web server required).
@@ -50,9 +51,11 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
2. Provide a clear title to easily help identify your problem.
3. Use proper [markdown syntax](https://help.github.com/articles/github-flavored-markdown) to structure your post (i.e. code/log in code blocks).
4. Make sure you provide the following information:
- [ ] Version
- [ ] Branch
- [ ] Version/Commit hash
- [ ] Your operating system and python version
- [ ] Commit hash
- [ ] Operating system
- [ ] Python version
- [ ] What you did?
- [ ] What happened?
- [ ] What you expected?
@@ -63,13 +66,14 @@ This project is based on code from [Headphones](https://github.com/rembo10/headp
## Feature Requests
1. Search for similar existing 'issues', feature requests can be recognized by the blue `enhancement` label.
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.
4. Provide a clear title to easily identify the feature request.
5. Tag your feature request with `[Feature Request]` so it can be identified easily.
Feature requests are handled on [FeatHub](http://feathub.com/drzoidberg33/plexpy).
1. Search the existing requests to see if your suggestion has already been submitted.
2. If a similar request exists, give it a thumbs up (+1), or add additional comments to the request.
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
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.

View File

@@ -1,6 +1,7 @@
<%
import plexpy
from plexpy import version
from plexpy.helpers import anon_url
%>
<!doctype html>
@@ -136,15 +137,15 @@ from plexpy import version
<div id="ajaxMsg" class="ajaxMsg"></div>
% if plexpy.CONFIG.CHECK_GITHUB and not plexpy.CURRENT_VERSION:
<div id="updatebar" style="display: none;">
You're running an unknown version of PlexPy. <a href="update">Update</a> or
<a href="#" id="updateDismiss">Close</a>
You're running an unknown version of PlexPy.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
</div>
% elif plexpy.CONFIG.CHECK_GITHUB and plexpy.CURRENT_VERSION != plexpy.LATEST_VERSION and plexpy.COMMITS_BEHIND > 0 and plexpy.INSTALL_TYPE != 'win':
<div id="updatebar" style="display: none;">
A <a
href="https://github.com/${plexpy.CONFIG.GIT_USER}/plexpy/compare/${plexpy.CURRENT_VERSION}...${plexpy.LATEST_VERSION}" target="_blank">
newer version</a> is available. You're ${plexpy.COMMITS_BEHIND} commits behind. <a href="update">Update</a> or
<a href="#" id="updateDismiss">Close</a>
A <a href="${anon_url('https://github.com/%s/plexpy/compare/%s...%s' % (plexpy.CONFIG.GIT_USER, plexpy.CURRENT_VERSION, plexpy.LATEST_VERSION))}" target="_blank">
newer version</a> is available.<br />
You're ${plexpy.COMMITS_BEHIND} commits behind.<br />
<a href="update">Update</a> or <a href="#" id="updateDismiss">Close</a>
</div>
% endif
<nav class="navbar navbar-fixed-top">

View File

@@ -886,6 +886,7 @@ a .dashboard-activity-metadata-user-thumb:hover {
text-align: center;
position: relative;
z-index: 0;
overflow: hidden;
}
.dashboard-recent-media {
width: 100%;
@@ -2407,6 +2408,9 @@ a .home-platforms-instance-list-oval:hover,
padding: 0;
border: 0;
}
.history-thumbnail-popover.popover.left {
margin-left: -15px;
}
.history-thumbnail-popover.popover.right {
margin-left: 15px;
}
@@ -2655,6 +2659,7 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
margin: 5px 0 0 0.5em;
}
.notification-params {
width: 100%;
margin-top: 10px;
background-color: #282828;
}
@@ -2669,6 +2674,14 @@ table[id^='media_info_child'] table[id^='media_info_child'] thead th {
padding-left: 10px;
width: 200px;
}
.notification-params.time-options td:first-child {
padding-left: 10px;
width: 125px;
}
.notification-params.time-options td:nth-child(2) {
padding-left: 10px;
width: 275px;
}
.notification-params td:not(:first-child) {
padding-right: 10px;
}
@@ -2759,3 +2772,21 @@ a.no-highlight:hover {
.save-button {
margin-top: 15px;
}
.nav-dashboard > li {
float: left;
}
.btn-gray.disabled,
.btn-gray.disabled:focus,
.btn-gray.disabled:hover,
.btn-gray.disabled:active {
color: #323232;
cursor: default;
}
.nav-header > li > a:focus,
.nav-header > li > a:hover {
background-color: transparent;
}
#recently-added-row-scroller,
#recently-watched-row-scroller {
position: relative;
}

View File

@@ -217,8 +217,8 @@ DOCUMENTATION :: END
% endif
<div class="dashboard-activity-progress">
<div class="dashboard-activity-progress-bar">
<div class="bufferbar" style="width: ${a['transcode_progress']}%">${a['transcode_progress']}%</div>
<div class="bar" style="width: ${a['progress_percent']}%">${a['progress_percent']}%</div>
<div class="bufferbar" style="width: ${a['transcode_progress']}%" data-toggle="tooltip" title="Transcoder Progress">${a['transcode_progress']}%</div>
<div class="bar" style="width: ${a['progress_percent']}%" data-toggle="tooltip" title="Stream Progress">${a['progress_percent']}%</div>
</div>
</div>
<div class="dashboard-activity-metadata-wrapper">
@@ -261,11 +261,7 @@ DOCUMENTATION :: END
% endif
</div>
<div class="dashboard-activity-metadata-user">
% if a['user_id']:
<a href="user?user_id=${a['user_id']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
% else:
<a href="user?user=${a['user']}" title="${a['friendly_name']}">${a['friendly_name']}</a>
% endif
</div>
</div>
</div>
@@ -289,11 +285,13 @@ DOCUMENTATION :: END
});
// Add hover class to dashboard-instance
$('.dashboard-activity-poster').hover(function() {
$('.dashboard-activity-poster, .dashboard-activity-progress-bar').hover(function() {
$(this).closest('.dashboard-instance').addClass('hover');
}, function() {
$(this).closest('.dashboard-instance').removeClass('hover');
});
$('.bar, .bufferbar').tooltip({container: 'body', placement: 'right', delay: 50});
</script>
% else:
<div class="text-muted">Nothing is currently being watched.</div><br>

View File

@@ -249,7 +249,7 @@
<script>
// Modal popup dialog
function selectHandler(selectedDate) {
function selectHandler(selectedDate, selectedSeries) {
try
{
@@ -259,10 +259,25 @@
var y = dateValue.getFullYear();
var dateString = '' + y + '-' + (m<=9 ? '0' + m : m) + '-' + (d <= 9 ? '0' + d : d);
var media_type = 'all';
var transcode_decision = null;
switch(selectedSeries) {
case "TV": media_type = 'episode'; break;
case "Movies": media_type = 'movie'; break;
case "Music": media_type = 'track'; break;
case "Direct Play": transcode_decision = 'direct play'; break;
case "Direct Stream": transcode_decision = 'copy'; break;
case "Transcode": transcode_decision = 'transcode'; break;
}
$.ajax({
"url": "history_table_modal",
url: "history_table_modal",
type: 'post',
data: { 'start_date': dateString },
data: {
start_date: dateString,
media_type: media_type,
transcode_decision: transcode_decision
},
complete: function(xhr, status) {
$('#history-modal').modal('show');
$("#history-modal").html(xhr.responseText);
@@ -271,7 +286,7 @@
}
catch(err)
{
console.log("Failed to retrieve data");
console.log("Failed to retrieve history modal data.");
}
}
</script>
@@ -299,6 +314,8 @@
var music_visible = (${config['music_logging_enable']} == 1 ? true : false);
function loadGraphsTab1(time_range, yaxis) {
$('#days-selection').show();
setGraphFormat(yaxis);
$.ajax({
@@ -382,6 +399,8 @@
}
function loadGraphsTab2(time_range, yaxis) {
$('#days-selection').show();
setGraphFormat(yaxis);
$.ajax({
@@ -460,6 +479,8 @@
}
function loadGraphsTab3(yaxis) {
$('#days-selection').hide();
setGraphFormat(yaxis);
$.ajax({

View File

@@ -22,7 +22,7 @@
</div>
</div>
<div class='table-card-back'>
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table" width="100%">
<thead>
<tr>
<th align="left" id="delete_row">Delete</th>

View File

@@ -5,12 +5,12 @@
<button type="button" class="close" data-dismiss="modal" aria-hidden="true"><i class="fa fa-remove"></i></button>
<h4 class="modal-title" id="myModalLabel">
<strong><span id="modal_header_ip_address">
<i class="fa fa-history"></i> History for <span id="date-header">${data}</span>
<i class="fa fa-history"></i> History for <span id="date-header">${data['start_date']}</span>
</span></strong>
</h4>
</div>
<div class="modal-body" id="modal-text">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table_modal" width="100%">
<thead>
<tr>
<th align="left" id="started">Started</th>
@@ -32,7 +32,7 @@
<script src="interfaces/default/js/tables/history_table_modal.js"></script>
<script>
$(document).ready(function() {
$('#date-header').html(moment('${data}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
$('#date-header').html(moment('${data["start_date"]}','YYYY-MM-DD').format('ddd MMM Do YYYY'));
history_table_modal_options.ajax = {
url: 'get_history',
type: 'post',
@@ -40,14 +40,16 @@
return {
json_data: JSON.stringify(d),
grouping: false,
start_date: '${data}'
start_date: "${data['start_date']}",
media_type: "${data.get('media_type')}",
transcode_decision: "${data.get('transcode_decision')}"
};
}
}
history_table = $('#history_table').DataTable(history_table_modal_options);
history_table = $('#history_table_modal').DataTable(history_table_modal_options);
clearSearchButton('history_table', history_table);
clearSearchButton('history_table_modal', history_table);
// Move #info-modal to parent container
if (!($('#history-modal').next().is('#info-modal'))) {

View File

@@ -23,7 +23,7 @@
<h3>Watch Statistics <small>Last ${config['home_stats_length']} days</small></h3>
</div>
<div id="home-stats" class="home-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br>
</div>
</div>
@@ -36,19 +36,27 @@
<h3>Library Statistics <small>${config['pms_name']}</small></h3>
</div>
<div id="library-stats" class="library-platforms">
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Loading stats...</div>
<br>
</div>
</div>
</div>
% endif
<div class='row'>
<div class="row">
<div class="col-md-12">
<div class="padded-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-added-page-left" class="paginate btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-added-page-right" class="paginate btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<h3>Recently Added</h3>
</div>
<div id='recentlyAdded'>
<div class='text-muted'><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
<div id="recentlyAdded" style="margin-right: -15px;">
<div class="text-muted"><i class="fa fa-refresh fa-spin"></i> Looking for new items...</div>
<br>
</div>
</div>
@@ -59,7 +67,6 @@
<%def name="javascriptIncludes()">
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script>
function currentActivityHeader() {
$.ajax({
url: 'get_current_activity_header',
@@ -71,7 +78,6 @@
});
}
currentActivityHeader();
setInterval(currentActivityHeader, 15000);
function currentActivity() {
$.ajax({
@@ -84,7 +90,12 @@
});
}
currentActivity();
setInterval(currentActivity, 15000);
setInterval(function () {
$('.bar, .bufferbar').tooltip('destroy');
currentActivityHeader();
currentActivity();
}, 15000);
function getHomeStats(days) {
$.ajax({
@@ -97,6 +108,7 @@
}
});
}
getHomeStats();
function getLibraryStats() {
$.ajax({
@@ -109,33 +121,21 @@
}
});
}
getLibraryStats();
function recentlyAdded() {
var widthVal = $('body').find(".container-fluid").width();
var tmp = (widthVal-20) / 182;
if (tmp > 0) {
containerSize = parseInt(tmp);
} else {
containerSize = 1;
}
$.ajax({
url: 'get_recently_added',
type: "GET",
async: true,
data: { count : containerSize },
data: { count : 50 },
complete: function(xhr, status) {
$("#recentlyAdded").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
$(document).ready(function () {
recentlyAdded();
$(window).resize(function() {
recentlyAdded();
});
});
var date_format = 'YYYY-MM-DD';
var time_format = 'hh:mm a';
@@ -148,10 +148,44 @@
}
});
getHomeStats();
getLibraryStats();
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("body").find(".container-fluid").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function () {
highlightAddedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("body").find(".container-fluid").width();
var scrollAmount = $(this).data("id") * parseInt((containerWidth - 15) / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
</script>
</%def>

View File

@@ -344,25 +344,41 @@ DOCUMENTATION :: END
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="row-edit-mode">
<i class="fa fa-trash-o"></i> Delete mode
</button>
% if source == 'history':
<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata">
<i class="fa fa-wrench"></i> Fix Metadata
</a>
% endif
% if data.get('poster_url'):
% if data['media_type'] == 'artist' or data['media_type'] == 'album' or data['media_type'] == 'track':
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="80" data-width="80" style="display: inline-flex;">
% else:
<span class="imgur-poster-tooltip" data-toggle="popover" data-img="${data['poster_url']}" data-height="120" data-width="80" style="display: inline-flex;">
% endif
<button class="btn btn-danger btn-edit" data-toggle="button" aria-pressed="false" autocomplete="off" id="delete-imgur-poster">
<i class="fa fa-picture-o"></i> Reset Imgur Poster
</button>
</span>
% endif
<div class="alert alert-danger alert-edit" role="alert" id="row-edit-mode-alert"><i class="fa fa-exclamation-triangle"></i>&nbspSelect rows to delete. Data is deleted upon exiting delete mode.</div>
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table-RK-${data['rating_key']}" width="100%">
<thead>
<tr>
<th align='left' id="delete">Delete</th>
<th align='left' id="time">Time</th>
<th align='left' id="friendly_name">User</th>
<th align='left' id="ip_address">IP Address</th>
<th align='left' id="platform">Platform</th>
<th align='left' id="player">Player</th>
<th align='left' id="title">Title</th>
<th align='left' id="started">Started</th>
<th align='left' id="paused_counter">Paused</th>
<th align='left' id="stopped">Stopped</th>
<th align='left' id="duration">Duration</th>
<th align='left' id="percent_complete"></th>
<th align="left" id="delete">Delete</th>
<th align="left" id="time">Time</th>
<th align="left" id="friendly_name">User</th>
<th align="left" id="ip_address">IP Address</th>
<th align="left" id="platform">Platform</th>
<th align="left" id="player">Player</th>
<th align="left" id="title">Title</th>
<th align="left" id="started">Started</th>
<th align="left" id="paused_counter">Paused</th>
<th align="left" id="stopped">Stopped</th>
<th align="left" id="duration">Duration</th>
<th align="left" id="percent_complete"></th>
</tr>
</thead>
<tbody></tbody>
@@ -458,11 +474,11 @@ DOCUMENTATION :: END
<script>
$(document).ready(function () {
get_history();
history_table = $('#history_table').DataTable(history_table_options);
history_table = $('#history_table-RK-${data["rating_key"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('div.colvis-button-bar');
clearSearchButton('history_table', history_table);
clearSearchButton('history_table-RK-${data["rating_key"]}', history_table);
$('#row-edit-mode').on('click', function() {
$('#row-edit-mode-alert').fadeIn(200);
@@ -519,10 +535,33 @@ DOCUMENTATION :: END
$("#runtime").html(millisecondsToMinutes($("#runtime").text(), true));
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
</script>
% if source == 'history':
% if data.get('poster_url'):
<script>
$('#row-edit-mode').after('<a href="update_metadata?rating_key=${data['rating_key']}&update=True" class="btn btn-danger btn-edit" id="fix-metadata"> \
<i class="fa fa-wrench"></i> Fix Metadata</a>');
$('.imgur-poster-tooltip').popover({
html: true,
container: 'body',
trigger: 'hover',
placement: 'left',
template: '<div class="popover history-thumbnail-popover" role="tooltip"><div class="arrow" style="top: 50%;"></div><div class="popover-content"></div></div>',
content: function () {
return '<div class="history-thumbnail" style="background-image: url(' + $(this).data('img') + '); height: ' + $(this).data('height') + 'px; width: ' + $(this).data('width') + 'px;" />';
}
});
$('#delete-imgur-poster').on('click', function() {
$.ajax({
url: 'delete_poster_url',
type: 'POST',
async: true,
data: { poster_url : "${data['poster_url']}" },
success: function (data) {
var msg = '<i class="fa fa-check"></i>&nbsp; Imgur poster reset';
showMsg(msg, false, true, 2000);
$('.imgur-poster-tooltip').popover('destroy');
$('#delete-imgur-poster').closest('span').remove();
}
});
});
</script>
% endif
% endif

View File

@@ -32,7 +32,8 @@
</div>
</div>
<div class="modal-footer">
<span class="text-muted">Telize service written by <a href="https://github.com/fcambus/telize" target="_blank">Frederic Cambus</a>.</span>
<% from plexpy.helpers import anon_url %>
<span class="text-muted">Telize service written by <a href="${anon_url('https://github.com/fcambus/telize')}" target="_blank">Frederic Cambus</a>.</span>
</div>
</div>
</div>

View File

@@ -29,7 +29,7 @@ var hc_plays_by_day_options = {
point: {
events: {
click: function () {
selectHandler(this.category);
selectHandler(this.category, this.series.name);
}
}
}
@@ -60,7 +60,8 @@ var hc_plays_by_day_options = {
}
},
tooltip: {
shared: true
shared: true,
crosshairs: true
},
series: [{}]
};

View File

@@ -29,7 +29,7 @@ var hc_plays_by_stream_type_options = {
point: {
events: {
click: function() {
selectHandler(this.category);
selectHandler(this.category, this.series.name);
}
}
}
@@ -60,7 +60,8 @@ var hc_plays_by_stream_type_options = {
}
},
tooltip: {
shared: true
shared: true,
crosshairs: true
},
series: [{}]
};

View File

@@ -21,8 +21,8 @@ history_table_options = {
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
"emptyTable": "No data in table"
},
"pagingType": "bootstrap",
"stateSave": false,
"pagingType": "full_numbers",
"stateSave": true,
"processing": false,
"serverSide": true,
"pageLength": 25,
@@ -107,7 +107,7 @@ history_table_options = {
}
},
"width": "10%",
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
"className": "no-wrap hidden-md hidden-sm hidden-xs"
},
{
"targets": [5],
@@ -115,11 +115,11 @@ history_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');
@@ -307,7 +307,7 @@ history_table_options = {
}
// Parent table platform modal
$('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
$('.history_table').on('click', '> tbody > tr > td.modal-control', function () {
var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@@ -327,7 +327,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control', function () {
});
// Parent table ip address modal
$('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
$('.history_table').on('click', '> tbody > tr > td.modal-control-ip', function () {
var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@@ -350,7 +350,7 @@ $('#history_table').on('click', '> tbody > tr > td.modal-control-ip', function (
});
// Parent table delete mode
$('#history_table').on('click', '> tbody > tr > td.delete-control > button', function () {
$('.history_table').on('click', '> tbody > tr > td.delete-control > button', function () {
var tr = $(this).closest('tr');
var row = history_table.row( tr );
var rowData = row.data();
@@ -399,7 +399,7 @@ $('#history_table').on('click', '> tbody > tr > td.delete-control > button', fun
});
// Parent table expand detailed history
$('#history_table').on('click', '> tbody > tr > td.expand-history a', function () {
$('.history_table').on('click', '> tbody > tr > td.expand-history a', function () {
var tr = $(this).closest('tr');
var row = history_table.row(tr);
var rowData = row.data();

View File

@@ -19,7 +19,7 @@ history_table_modal_options = {
"infoFiltered":"",
"emptyTable": "No data in table",
},
"pagingType": "bootstrap",
"pagingType": "simple_numbers",
"stateSave": false,
"processing": false,
"serverSide": true,
@@ -79,11 +79,11 @@ history_table_modal_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
@@ -148,7 +148,7 @@ history_table_modal_options = {
}
}
$('#history_table').on('click', 'td.modal-control', function () {
$('.history_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = history_table.row(tr);
var rowData = row.data();

View File

@@ -16,8 +16,8 @@ libraries_list_table_options = {
"pageLength": 10,
"order": [ 2, 'asc'],
"autoWidth": true,
"stateSave": false,
"pagingType": "bootstrap",
"stateSave": true,
"pagingType": "full_numbers",
"columnDefs": [
{
"targets": [0],

View File

@@ -2,10 +2,10 @@ var log_table_options = {
"destroy": true,
"serverSide": true,
"processing": false,
"pagingType": "bootstrap",
"pagingType": "full_numbers",
"order": [ 0, 'desc'],
"pageLength": 50,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View File

@@ -22,8 +22,8 @@ media_info_table_options = {
"infoFiltered":"<span class='hidden-md hidden-sm hidden-xs'>(filtered from _MAX_ total entries)</span>",
"emptyTable": "No data in table"
},
"pagingType": "bootstrap",
"stateSave": false,
"pagingType": "full_numbers",
"stateSave": true,
"processing": false,
"serverSide": true,
"pageLength": 25,
@@ -294,7 +294,7 @@ media_info_table_options = {
}
// Parent table expand detailed media info
$('#media_info_table').on('click', '> tbody > tr > td.expand-media-info a', function () {
$('.media_info_table').on('click', '> tbody > tr > td.expand-media-info a', function () {
var tr = $(this).closest('tr');
var row = media_info_table.row(tr);
var rowData = row.data();

View File

@@ -0,0 +1,89 @@
notification_log_table_options = {
"destroy": true,
"serverSide": true,
"processing": false,
"pagingType": "full_numbers",
"order": [ 0, 'desc'],
"pageLength": 50,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",
"emptyTable": "No log information available",
"info":"Showing _START_ to _END_ of _TOTAL_ lines",
"infoEmpty":"Showing 0 to 0 of 0 lines",
"infoFiltered":"(filtered from _MAX_ total lines)"},
"columnDefs": [
{
"targets": [0],
"data": "timestamp",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(moment(cellData, "X").format('YYYY-MM-DD HH:mm:ss'));
}
},
"width": "10%",
"className": "no-wrap hidden-xs"
},
{
"targets": [1],
"data": "agent_name",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "7%",
"className": "no-wrap hidden-sm hidden-xs"
},
{
"targets": [2],
"data": "notify_action",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "5%"
},
{
"targets": [3],
"data": "subject_text",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "20%"
},
{
"targets": [4],
"data": "body_text",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "38%"
},
{
"targets": [5],
"data": "script_args",
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== '') {
$(td).html(cellData);
}
},
"width": "20%"
}
],
"drawCallback": function (settings) {
// Jump to top of page
//$('html,body').scrollTop(0);
$('#ajaxMsg').fadeOut();
},
"preDrawCallback": function(settings) {
var msg = "<i class='fa fa-refresh fa-spin'></i>&nbspFetching rows...";
showMsg(msg, false, false, 0)
}
}

View File

@@ -2,10 +2,10 @@ var plex_log_table_options = {
"destroy": true,
"processing": false,
"serverSide": false,
"pagingType": "bootstrap",
"pagingType": "full_numbers",
"order": [ 0, 'desc'],
"pageLength": 50,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View File

@@ -1,10 +1,10 @@
sync_table_options = {
"processing": false,
"serverSide": false,
"pagingType": "bootstrap",
"pagingType": "full_numbers",
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
"pageLength": 25,
"stateSave": false,
"stateSave": true,
"language": {
"search":"Search: ",
"lengthMenu":"Show _MENU_ lines per page",

View File

@@ -8,8 +8,8 @@ user_ip_table_options = {
"infoFiltered":"(filtered from _MAX_ total entries)",
"emptyTable": "No data in table",
},
"stateSave": false,
"pagingType": "bootstrap",
"stateSave": true,
"pagingType": "full_numbers",
"processing": false,
"serverSide": true,
"pageLength": 10,
@@ -56,7 +56,7 @@ user_ip_table_options = {
}
},
"width": "15%",
"className": "no-wrap hidden-md hidden-sm hidden-xs modal-control"
"className": "no-wrap hidden-md hidden-sm hidden-xs"
},
{
"targets": [3],
@@ -64,11 +64,11 @@ user_ip_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData) {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp' + cellData + '</div></a></div>');
@@ -146,11 +146,11 @@ user_ip_table_options = {
}
}
$('#user_ip_table').on('mouseenter', 'td.modal-control span', function () {
$('.user_ip_table').on('mouseenter', 'td.modal-control span', function () {
$(this).tooltip();
});
$('#user_ip_table').on('click', 'td.modal-control', function () {
$('.user_ip_table').on('click', 'td.modal-control', function () {
var tr = $(this).parents('tr');
var row = user_ip_table.row(tr);
var rowData = row.data();
@@ -169,7 +169,7 @@ $('#user_ip_table').on('click', 'td.modal-control', function () {
showStreamDetails();
});
$('#user_ip_table').on('click', 'td.modal-control-ip', function () {
$('.user_ip_table').on('click', 'td.modal-control-ip', function () {
var tr = $(this).parents('tr');
var row = user_ip_table.row( tr );
var rowData = row.data();

View File

@@ -16,8 +16,8 @@ users_list_table_options = {
"pageLength": 10,
"order": [ 2, 'asc'],
"autoWidth": true,
"stateSave": false,
"pagingType": "bootstrap",
"stateSave": true,
"pagingType": "full_numbers",
"columnDefs": [
{
"targets": [0],
@@ -120,11 +120,11 @@ users_list_table_options = {
"createdCell": function (td, cellData, rowData, row, col) {
if (cellData !== null && cellData !== '') {
var transcode_dec = '';
if (rowData['video_decision'] === 'transcode' || rowData['audio_decision'] === 'transcode') {
if (rowData['transcode_decision'] === 'transcode') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Transcode"><i class="fa fa-server fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'copy' || rowData['audio_decision'] === 'copy') {
} else if (rowData['transcode_decision'] === 'copy') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Stream"><i class="fa fa-video-camera fa-fw"></i></span>';
} else if (rowData['video_decision'] === 'direct play' || rowData['audio_decision'] === 'direct play') {
} else if (rowData['transcode_decision'] === 'direct play') {
transcode_dec = '<span class="transcode-tooltip" data-toggle="tooltip" title="Direct Play"><i class="fa fa-play-circle fa-fw"></i></span>';
}
$(td).html('<div><a href="#" data-target="#info-modal" data-toggle="modal"><div style="float: left;">' + transcode_dec + '&nbsp;' + cellData + '</div></a></div>');

View File

@@ -118,8 +118,16 @@ DOCUMENTATION :: END
<div class="row">
<div class="col-md-12">
<div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-watched-page-left" class="paginate-watched btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-watched-page-right" class="paginate-watched btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar">
<span><i class="fa fa-history"></i> Recently Watched</span>
<span><i class="fa fa-history"></i> Recently Played</span>
</div>
</div>
<div class="table-card-back">
@@ -135,6 +143,14 @@ DOCUMENTATION :: END
<div class="row">
<div class="col-md-12">
<div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-added-page-left" class="paginate-added btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-added-page-right" class="paginate-added btn-gray disabled" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar">
<span><i class="fa fa-history"></i> Recently Added</span>
</div>
@@ -170,7 +186,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table-SID-${data['section_id']}" width="100%">
<thead>
<tr>
<th align="left" id="delete">Delete</th>
@@ -229,7 +245,7 @@ DOCUMENTATION :: END
</div>
</div>
<div class="table-card-back">
<table class="display" id="media_info_table" width="100%">
<table class="display media_info_table" id="media_info_table-SID-${data['section_id']}" width="100%">
<thead>
<tr>
<th align="left" id="added_at">Added At</th>
@@ -364,12 +380,12 @@ DOCUMENTATION :: END
};
}
}
history_table = $('#history_table').DataTable(history_table_options);
history_table = $('#history_table-SID-${data["section_id"]}').DataTable(history_table_options);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table', history_table);
clearSearchButton('history_table-SID-${data["section_id"]}', history_table);
}
function loadMediaInfoTable() {
@@ -385,12 +401,12 @@ DOCUMENTATION :: END
};
}
}
media_info_table = $('#media_info_table').DataTable(media_info_table_options);
media_info_table = $('#media_info_table-SID-${data["section_id"]}').DataTable(media_info_table_options);
var colvis = new $.fn.dataTable.ColVis(media_info_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' });
$(colvis.button()).appendTo('#button-bar-media-info');
clearSearchButton('media_info_table', media_info_table);
clearSearchButton('media_info_table-SID-${data["section_id"]}', media_info_table);
}
$( "#history-tab-btn" ).one( "click", function() {
@@ -460,61 +476,116 @@ DOCUMENTATION :: END
});
function recentlyWatched() {
var widthVal = $("#library-recently-watched").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently watched
$.ajax({
url: 'get_library_recently_watched',
async: true,
data: {
section_id: section_id,
limit: containerSize
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
function recentlyAdded() {
var widthVal = $("#library-recently-added").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently added
$.ajax({
url: 'get_library_recently_added',
async: true,
data: {
section_id: section_id,
limit: containerSize
limit: 50
},
complete: function(xhr, status) {
$("#library-recently-added").html(xhr.responseText);
highlightAddedScrollerButton();
}
});
}
recentlyWatched();
recentlyAdded();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
function highlightAddedScrollerButton() {
var scroller = $("#recently-added-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#library-recently-added").width()) {
$("#recently-added-page-right").removeClass("disabled");
} else {
$("#recently-added-page-right").addClass("disabled");
}
}
$(window).resize(function() {
recentlyWatched();
recentlyAdded();
highlightWatchedScrollerButton();
highlightAddedScrollerButton();
});
$('div.art-face').animate({ opacity: 0.2 }, { duration: 1000 });
var leftTotalWatched = 0;
$(".paginate-watched").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#library-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalWatched = Math.max(Math.min(leftTotalWatched + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalWatched }, 250);
if (leftTotalWatched == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotalWatched == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
var leftTotalAdded = 0;
$(".paginate-added").click(function (e) {
e.preventDefault();
var scroller = $("#recently-added-row-scroller");
var containerWidth = $("#library-recently-added").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotalAdded = Math.max(Math.min(leftTotalAdded + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotalAdded }, 250);
if (leftTotalAdded == 0) {
$("#recently-added-page-left").addClass("disabled").blur();
} else {
$("#recently-added-page-left").removeClass("disabled");
}
if (leftTotalAdded == leftMax) {
$("#recently-added-page-right").addClass("disabled").blur();
} else {
$("#recently-added-page-right").removeClass("disabled");
}
});
});
</script>
% endif

View File

@@ -32,6 +32,7 @@ DOCUMENTATION :: END
% if data:
<div class="dashboard-recent-media-row">
<div id="recently-added-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<li>
@@ -90,6 +91,7 @@ DOCUMENTATION :: END
% endfor
</ul>
</div>
</div>
% else:
<div class="text-muted">No stats to show.
</div><br>

View File

@@ -22,6 +22,7 @@ from plexpy import helpers
</div>
<div class="button-bar">
<button class="btn btn-dark" id="clear-logs"><i class="fa fa-trash-o"></i> Clear log</button>
<button class="btn btn-dark" id="clear-notify-logs" style="display: none;"><i class="fa fa-trash-o"></i> Clear log</button>
</div>
</div>
<div class='table-card-back'>
@@ -30,6 +31,7 @@ from plexpy import helpers
<li role="presentation" class="active"><a id="plexpy-logs-btn" href="#tabs-1" aria-controls="tabs-1" role="tab" data-toggle="tab">PlexPy Logs</a></li>
<li role="presentation"><a id="plex-logs-btn" href="#tabs-2" aria-controls="tabs-2" role="tab" data-toggle="tab">Plex Media Server Logs</a></li>
<li role="presentation"><a id="plex-scanner-logs-btn" href="#tabs-3" aria-controls="tabs-3" role="tab" data-toggle="tab">Plex Media Scanner Logs</a></li>
<li role="presentation"><a id="notification-logs-btn" href="#tabs-4" aria-controls="tabs-4" role="tab" data-toggle="tab">Notification Logs</a></li>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active" id="tabs-1">
@@ -67,8 +69,22 @@ from plexpy import helpers
<th align='left' id="plex_scanner_message">Message</th>
</tr>
</thead>
<tbody>
</tbody>
<tbody></tbody>
</table>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-4">
<table class="display" id="notification_log_table" width="100%">
<thead>
<tr>
<th align='left' id="notification_timestamp">Timestamp</th>
<th align='left' id="notification_agent_name">Agent</th>
<th align='left' id="notification_action">Action</th>
<th align='left' id="notification_poster_url">Subject Text</th>
<th align='left' id="notification_poster_url">Body Text</th>
<th align='left' id="notification_poster_url">Script Args</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
@@ -94,8 +110,10 @@ from plexpy import helpers
<script src="interfaces/default/js/jquery.dataTables.min.js"></script>
<script src="interfaces/default/js/dataTables.bootstrap.min.js"></script>
<script src="interfaces/default/js/dataTables.bootstrap.pagination.js"></script>
<script src="interfaces/default/js/moment-with-locale.js"></script>
<script src="interfaces/default/js/tables/logs.js"></script>
<script src="interfaces/default/js/tables/plex_logs.js"></script>
<script src="interfaces/default/js/tables/notification_logs.js"></script>
<script>
$(document).ready(function() {
@@ -105,43 +123,66 @@ from plexpy import helpers
function LoadPlexPyLogs() {
log_table_options.ajax = {
"url": "getLog"
url: "getLog"
}
log_table = $('#log_table').DataTable(log_table_options);
}
function LoadPlexLogs() {
plex_log_table_options.ajax = {
"url": "get_plex_log?log_type=server"
url: "get_plex_log?log_type=server"
}
plex_log_table = $('#plex_log_table').DataTable(plex_log_table_options);
}
function LoadPlexScannerLogs() {
plex_log_table_options.ajax = {
"url": "get_plex_log?log_type=scanner"
url: "get_plex_log?log_type=scanner"
}
plex_scanner_log_table = $('#plex_scanner_log_table').DataTable(plex_log_table_options);
}
function LoadNotificationLogs() {
notification_log_table_options.ajax = {
url: "get_notification_log",
type: 'post',
data: function (d) {
return {
json_data: JSON.stringify(d)
};
}
}
notification_log_table = $('#notification_log_table').DataTable(notification_log_table_options);
}
$("#plexpy-logs-btn").click(function () {
$("#clear-logs").show();
$("#clear-notify-logs").hide();
LoadPlexPyLogs();
clearSearchButton('log_table', log_table);
});
$("#plex-logs-btn").click(function () {
$("#clear-logs").hide();
$("#clear-notify-logs").hide();
LoadPlexLogs();
clearSearchButton('plex_log_table', plex_log_table);
});
$("#plex-scanner-logs-btn").click(function () {
$("#clear-logs").hide();
$("#clear-notify-logs").hide();
LoadPlexScannerLogs();
clearSearchButton('plex_scanner_log_table', plex_scanner_log_table);
});
$("#notification-logs-btn").click(function () {
$("#clear-logs").hide();
$("#clear-notify-logs").show();
LoadNotificationLogs();
clearSearchButton('notification_log_table', notification_log_table);
});
$("#clear-logs").click(function () {
var r = confirm("Are you sure you want to clear the PlexPy log?");
if (r == true) {
@@ -149,6 +190,19 @@ from plexpy import helpers
}
});
$("#clear-notify-logs").click(function () {
var r = confirm("Are you sure you want to clear the PlexPy notification log?");
if (r == true) {
$.ajax({
url: 'clearNotifyLogs',
type: 'POST',
success: function (data) {
notification_log_table.draw();
}
});
}
});
var timer;
function setRefresh()
{
@@ -164,8 +218,12 @@ from plexpy import helpers
timer = setInterval(function() {
if ($("#tabs-1").hasClass("active")) {
log_table.ajax.reload();
} else {
} else if ($("#tabs-2").hasClass("active")) {
plex_log_table.ajax.reload();
} else if ($("#tabs-3").hasClass("active")) {
plex_scanner_log_table.ajax.reload();
} else if ($("#tabs-4").hasClass("active")) {
notification_log_table.ajax.reload();
}
}, 1000*refreshrate.value);
}

View File

@@ -64,13 +64,6 @@ from plexpy import helpers
</label>
<p class="help-block">Trigger notification when a media item is added to the Plex Media Server.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_extdown" ${helpers.checked(data['on_extdown'])} class="toggle-switches">
Notify on Plex remote access down
</label>
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached externally.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_intdown" ${helpers.checked(data['on_intdown'])} class="toggle-switches">
@@ -78,6 +71,20 @@ from plexpy import helpers
</label>
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached internally.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_intup" ${helpers.checked(data['on_intup'])} class="toggle-switches">
Notify on Plex server back up
</label>
<p class="help-block">Trigger notification when the Plex Media Server can be reached internally after being down.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_extdown" ${helpers.checked(data['on_extdown'])} class="toggle-switches">
Notify on Plex remote access down
</label>
<p class="help-block">Trigger notification when the Plex Media Server cannot be reached externally.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_extup" ${helpers.checked(data['on_extup'])} class="toggle-switches">
@@ -87,10 +94,10 @@ from plexpy import helpers
</div>
<div class="checkbox">
<label>
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_intup" ${helpers.checked(data['on_intup'])} class="toggle-switches">
Notify on Plex server back up
<input type="checkbox" data-size="small" data-id="${data['id']}" data-config-name="${data['config_prefix']}_on_pmsupdate" ${helpers.checked(data['on_pmsupdate'])} class="toggle-switches">
Notify on Plex update available
</label>
<p class="help-block">Trigger notification when the Plex Media Server can be reached internally after being down.</p>
<p class="help-block">Trigger notification when an update for the Plex Media Server is available.</p>
</div>
</div>
</div>

View File

@@ -32,6 +32,7 @@ DOCUMENTATION :: END
% if data != None:
<div class="dashboard-recent-media-row">
<div id="recently-added-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<div class="dashboard-recent-media-instance">
@@ -83,6 +84,7 @@ DOCUMENTATION :: END
% endfor
</ul>
</div>
</div>
% else:
<div class="text-muted">There was an error communicating with your Plex Server. Please check your <a href="settings">settings</a>.
</div><br>

View File

@@ -1,5 +1,6 @@
<%inherit file="base.html"/>
<%!
import os
import sys
import plexpy
from plexpy import notifiers, common, versioncheck
@@ -43,7 +44,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<li role="presentation"><a href="#tabs-5" aria-controls="tabs-5" role="tab" data-toggle="tab">Plex Media Server</a></li>
<li role="presentation"><a href="#tabs-6" aria-controls="tabs-6" role="tab" data-toggle="tab">Plex.tv Account</a></li>
<li role="presentation"><a href="#tabs-7" aria-controls="tabs-7" role="tab" data-toggle="tab">Extra Settings</a></li>
<li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Monitoring</a></li>
<li role="presentation"><a href="#tabs-8" aria-controls="tabs-8" role="tab" data-toggle="tab">Activity Monitoring</a></li>
<li role="presentation"><a href="#tabs-9" aria-controls="tabs-9" role="tab" data-toggle="tab">Notifications</a></li>
<li role="presentation"><a href="#tabs-10" aria-controls="tabs-10" role="tab" data-toggle="tab">Notification Agents</a></li>
</ul>
@@ -68,7 +69,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td><a class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy/tree/%s' % plexpy.CONFIG.GIT_BRANCH)}">${plexpy.CONFIG.GIT_BRANCH}</a></td>
</tr>
<tr>
<td>Git Hash:</td>
<td>Git Commit Hash:</td>
<td><a class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy/commit/%s' % plexpy.CURRENT_VERSION)}">${plexpy.CURRENT_VERSION}</a></td>
</tr>
% endif
@@ -80,17 +81,17 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td>Database File:</td>
<td>${plexpy.DB_FILE}</td>
</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>
<td>Backup Directory:</td>
<td>${plexpy.CONFIG.BACKUP_DIR}</td>
<td>${config['backup_dir']}</td>
</tr>
<tr>
<td>Cache Directory:</td>
<td>${plexpy.CONFIG.CACHE_DIR}</td>
</tr>
<tr>
<td>Log Directory:</td>
<td>${plexpy.CONFIG.LOG_DIR}</td>
<td>${config['cache_dir']}</td>
</tr>
% if plexpy.ARGS:
<tr>
@@ -110,13 +111,21 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<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>
</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>
<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>
</tr>
<tr>
<td>Source:</td>
<td><a class="no-highlight" href="${anon_url('https://github.com/drzoidberg33/plexpy')}" target="_blank">https://github.com/drzoidberg33/plexpy</a></td>
<td>Issues:</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>
<td>Gitter Chat:</td>
@@ -142,6 +151,18 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label>
<p class="help-block">If you have Git installed, allow periodic checks for updates.</p>
</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">
<h3>Display Settings</h3>
</div>
@@ -169,6 +190,42 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label>
<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 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">
<h3>Directories</h3>
</div>
<div class="form-group">
<label for="backup_dir">Backup Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="backup_dir" name="backup_dir" value="${config['backup_dir']}">
</div>
</div>
</div>
<div class="form-group">
<label for="cache_dir">Cache Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="cache_dir" name="cache_dir" value="${config['cache_dir']}">
</div>
</div>
</div>
<div class="form-group">
<label for="log_dir">Log Directory</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control directory-settings" id="log_dir" name="log_dir" value="${config['log_dir']}">
</div>
</div>
</div>
<p><input type="button" class="btn btn-bright save-button" value="Save" data-success="Changes saved successfully"></p>
</div>
<div role="tabpanel" class="tab-pane" id="tabs-2">
@@ -437,9 +494,24 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<div role="tabpanel" class="tab-pane" id="tabs-5">
<div class="padded-header">
<h3>Plex Media Server</h3>
<h3>Plex Media Server <small style="color: #fff;">Version <span id="pms_version">unknown</span></small></h3>
</div>
<p class="help-block">If you're using websocket monitoring, any server changes require a restart of PlexPy.</p>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_pms_updates" name="monitor_pms_updates" value="1" ${config['monitor_pms_updates']}> Monitor Plex Updates
</label>
<p class="help-block">Enable to have PlexPy check if updates are available for the Plex Media Server.<br />
Note: The Plex updater is broken on certain Plex Pass version of Plex Media Server. PlexPy will automatically disable checking for Plex updates if one of these versions is found.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
</label>
<span id="remoteAccessCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down.</p>
</div>
<div class="form-group has-feedback" id="pms-ip-group">
<label for="pms_ip">Plex IP or Hostname</label>
<div class="row">
@@ -485,8 +557,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<label for="pms_logs_folder">Logs Folder</label>
<div class="row">
<div class="col-md-6">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change">
<input type="text" class="form-control" id="pms_logs_folder" name="pms_logs_folder" value="${config['pms_logs_folder']}" size="30" data-parsley-trigger="change" data-parsley-pattern="^[^\~\%]" data-parsley-errors-container="#pms_logs_folder_error" data-parsley-error-message="Shortcuts are not recognized.">
</div>
<div id="pms_logs_folder_error" class="alert alert-danger settings-alert" role="alert"></div>
</div>
<p class="help-block">Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.<br />
<a href="${anon_url('https://support.plex.tv/hc/en-us/articles/200250417-Plex-Media-Server-Log-Files')}" target="_blank">Click here</a> for help. This is required if you enable IP logging (for PMS 0.9.12 and below). </p>
@@ -571,10 +644,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="get_file_sizes" name="get_file_sizes" value="1" ${config['get_file_sizes']}> Calculate Total File Sizes [experimental]
<input type="checkbox" id="get_file_sizes" name="get_file_sizes" value="1" ${config['get_file_sizes']}> Calculate Total File Sizes <span style="color: #eb8600; padding-left: 10px;">[experimental]</span>
</label>
<p class="help-block">Enable if you want PlexPy to calculate the total file size for TV Shows/Seasons and Artists/Albums on the media info tables.<br />
This is currently experimental.</p>
<p class="help-block">Enable if you want PlexPy to calculate the total file size for TV Shows/Seasons and Artists/Albums on the media info tables.</p>
</div>
<div class="form-group">
<label for="anon_redirect">Anonymous Redirect</label>
@@ -610,17 +682,9 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</div>
<div class="checkbox">
<label>
<input type="checkbox" class="monitor-settings" id="monitoring_use_websocket" name="monitoring_use_websocket" value="1" ${config['monitoring_use_websocket']}> Use Websocket (requires restart) [experimental]
<input type="checkbox" class="monitor-settings" id="monitoring_use_websocket" name="monitoring_use_websocket" value="1" ${config['monitoring_use_websocket']}> Use Websocket (requires restart) <span style="color: #eb8600; padding-left: 10px;">[experimental]</span>
</label>
<p class="help-block">Instead of polling the server at regular intervals let the server tell us when something happens.<br />
This is currently experimental.</p>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="monitor_remote_access" name="monitor_remote_access" value="1" ${config['monitor_remote_access']}> Monitor Plex Remote Access
</label>
<span id="remoteAccessCheck" style="color: #eb8600; padding-left: 10px;"></span>
<p class="help-block">Enable to have PlexPy check if remote access to the Plex Media Server goes down.</p>
<p class="help-block">Instead of polling the server at regular intervals let the server tell PlexPy when something happens.</p>
</div>
<div class="padded-header">
@@ -715,6 +779,13 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</label>
</div>
<div class="checkbox" style="padding-top: 15px;">
<label>
<input type="checkbox" name="notify_upload_posters" id="notify_upload_posters" value="1" ${config['notify_upload_posters']}> Enable Posters in Notifications
</label>
<p class="help-block">Enable to upload Plex posters to Imgur for notifications. Disable if posters are not being used to save bandwidth.</p>
</div>
<div class="padded-header">
<h3>Current Activity Notifications</h3>
</div>
@@ -744,8 +815,14 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<label>
<input type="checkbox" name="notify_recently_added_grandparent" id="notify_recently_added_grandparent" value="1" ${config['notify_recently_added_grandparent']}> Group notifications for recently added TV Shows or Music
</label>
<p class="help-block">Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br />
Note: No Season/Episode or Album/Track metadata will be available.</p>
<p class="help-block">
Enable to only get one TV Show or Artist notification for a batch of recently added Episodes or Tracks. Movies are unaffected.<br />
% if config['notify_recently_added_grandparent'] == 'Checked':
<span id="notify_recently_added_grandparent_note" style="color: #eb8600;">Note: No Season/Episode or Album/Track metadata will be available.</span>
% else:
<span id="notify_recently_added_grandparent_note">Note: No Season/Episode or Album/Track metadata will be available.</span>
% endif
</p>
</div>
<div class="form-group">
<label for="notify_recently_added_delay">Notification Delay</label>
@@ -892,23 +969,6 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Remote Access Down<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_extdown_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_extdown_subject_text" name="notify_on_extdown_subject_text" value="${config['notify_on_extdown_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_extdown_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_extdown_body_text" name="notify_on_extdown_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_extdown_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Server Down<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
@@ -926,6 +986,40 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Server Back Up<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_intup_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_intup_subject_text" name="notify_on_intup_subject_text" value="${config['notify_on_intup_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_intup_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_intup_body_text" name="notify_on_intup_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_intup_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Remote Access Down<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_extdown_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_extdown_subject_text" name="notify_on_extdown_subject_text" value="${config['notify_on_extdown_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_extdown_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_extdown_body_text" name="notify_on_extdown_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_extdown_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Remote Access Back Up<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
@@ -944,17 +1038,17 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</ul>
</li>
<li>
<div class="link"><i class="fa fa-server fa-fw"></i>&nbsp;Plex Server Back Up<i class="fa fa-chevron-down"></i></div>
<div class="link"><i class="fa fa-refresh fa-fw"></i>&nbsp;Plex Update Available<i class="fa fa-chevron-down"></i></div>
<ul class="submenu">
<li>
<div class="form-group">
<label for="notify_on_intup_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_intup_subject_text" name="notify_on_intup_subject_text" value="${config['notify_on_intup_subject_text']}" data-parsley-trigger="change" required>
<label for="notify_on_pmsupdate_subject_text">Subject Line</label>
<input class="form-control" type="text" id="notify_on_pmsupdate_subject_text" name="notify_on_pmsupdate_subject_text" value="${config['notify_on_pmsupdate_subject_text']}" data-parsley-trigger="change" required>
<p class="help-block">Set a custom subject line.</p>
</div>
<div class="form-group">
<label for="notify_on_intup_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_intup_body_text" name="notify_on_intup_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_intup_body_text']}</textarea>
<label for="notify_on_extup_body_text">Message Body</label>
<textarea class="form-control" id="notify_on_pmsupdate_body_text" name="notify_on_pmsupdate_body_text" data-parsley-trigger="change" data-autoresize required>${config['notify_on_pmsupdate_body_text']}</textarea>
<p class="help-block">Set a custom body.</p>
</div>
</li>
@@ -1020,24 +1114,45 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<h4 class="modal-title">Date &amp; Time Format Options</h4>
</div>
<div class="modal-body">
<table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Year
</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center" colspan="3"><h5>Year</h5></td>
</tr>
<tr>
<td><strong>YYYY</strong></td>
<td>Numeric, 4 digits</td>
<td>Eg., 1999, 2003</td>
<td>Numeric, four digits</td>
<td>E.g. 1999, 2003</td>
</tr>
<tr>
<td><strong>YY</strong></td>
<td>Numeric, 2 digits</td>
<td>Eg., 99, 03</td>
<td>Numeric, two digits</td>
<td>E.g. 99, 03</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td align="center" colspan="3"><h5>Month</h5></td>
<th>
Month
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>MMMM</strong></td>
<td>Textual, full</td>
<td>January-December</td>
</tr>
<tr>
<td><strong>MMM</strong></td>
<td>Textual, three letters</td>
<td>Jan-Dec</td>
</tr>
<tr>
<td><strong>MM</strong></td>
@@ -1049,23 +1164,41 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td>Numeric, without leading zeros</td>
<td>1-12</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td><strong>MMMM</strong></td>
<td>Textual full</td>
<td>January-December</td>
<th>
Day of the Year
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>DDDD</strong></td>
<td>Numeric, with leading zeros</td>
<td>001-365</td>
</tr>
<tr>
<td><strong>MMM</strong></td>
<td>Textual three letters</td>
<td>Jan-Dec</td>
<td><strong>DDD</strong></td>
<td>Numeric, without leading zeros</td>
<td>1-365</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td align="center" colspan="3"><h5>Day</h5></td>
<th>
Day of the Month
</th>
</tr>
</thead>
<tbody>
<tr>
<td width="100"><strong>DD</strong></td>
<td width="300">Numeric, with leading zeros</td>
<td><strong>DD</strong></td>
<td>Numeric, with leading zeros</td>
<td>01-31</td>
</tr>
<tr>
@@ -1075,57 +1208,165 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>Do</strong></td>
<td>The English suffix for the day of the month</td>
<td>st, nd or th in the 1st, 2nd or 15th.</td>
<td>Numeric, with suffix</td>
<td>E.g. 1st, 2nd ... 31st.</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td align="center" colspan="3"><h5>Time</h5></td>
<th>
Day of the Week
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>dddd</strong></td>
<td>Textual, full</td>
<td>Sunday-Saturday</td>
</tr>
<tr>
<td><strong>a</strong></td>
<td width="300">am/pm Lowercase</td>
<td>am, pm</td>
<td><strong>ddd</strong></td>
<td>Textual, three letters</td>
<td>Sun-Sat</td>
</tr>
<tr>
<td><strong>A</strong></td>
<td>AM/PM Uppercase</td>
<td>AM, PM</td>
<td><strong>d</strong></td>
<td>Numeric</td>
<td>0-6</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<td><strong>h</strong></td>
<td>Hour, 12-hour, without leading zeros</td>
<td>1-12</td>
</tr>
<tr>
<td><strong>hh</strong></td>
<td>Hour, 12-hour, with leading zeros</td>
<td>01-12</td>
</tr>
<tr>
<td><strong>H</strong></td>
<td>Hour, 24-hour, without leading zeros</td>
<td>0-23</td>
<th>
Hour
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>HH</strong></td>
<td>Hour, 24-hour, with leading zeros</td>
<td>24-hour, with leading zeros</td>
<td>00-23</td>
</tr>
<tr>
<td><strong>H</strong></td>
<td>24-hour, without leading zeros</td>
<td>0-23</td>
</tr>
<tr>
<td><strong>hh</strong></td>
<td>12-hour, with leading zeros</td>
<td>01-12</td>
</tr>
<tr>
<td><strong>h</strong></td>
<td>12-hour, without leading zeros</td>
<td>1-12</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Minute
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>mm</strong></td>
<td>Minutes, with leading zeros</td>
<td>Numeric, with leading zeros</td>
<td>00-59</td>
</tr>
<tr>
<td><strong>m</strong></td>
<td>Numeric, without leading zeros</td>
<td>0-59</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Second
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ss</strong></td>
<td>Seconds, with leading zeros</td>
<td>Numeric, with leading zeros</td>
<td>00-59</td>
</tr>
<tr>
<td><strong>zz</strong></td>
<td>Timezone abbreviation</td>
<td>Eg., EST, MDT ...</td>
<td><strong>s</strong></td>
<td>Numeric, without leading zeros</td>
<td>0-59</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
AM / PM
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>A</strong></td>
<td>AM/PM uppercase</td>
<td>AM, PM</td>
</tr>
<tr>
<td><strong>a</strong></td>
<td width="300">am/pm lowercase</td>
<td>am, pm</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Timezone
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>ZZ</strong></td>
<td>UTC offset</td>
<td>E.g. +0100, -0700</td>
</tr>
<tr>
<td><strong>Z</strong></td>
<td>UTC offset</td>
<td>E.g. +01:00, -07:00</td>
</tr>
</tbody>
</table>
<table class="notification-params time-options">
<thead>
<tr>
<th>
Timestamp
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>X</strong></td>
<td>Unix timestamp</td>
<td>E.g. 1456887825</td>
</tr>
</tbody>
</table>
@@ -1210,6 +1451,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
<td><strong>{server_uptime}</strong></td>
<td>The uptime (in days, hours, mins, secs) of your Plex Server.</td>
</tr>
<tr>
<td><strong>{server_version}</strong></td>
<td>The current version of your Plex Server.</td>
</tr>
<tr>
<td><strong>{action}</strong></td>
<td>The action that triggered the notification.</td>
@@ -1402,7 +1647,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>{artist_name}</strong></td>
<td>The name of the artistd.</td>
<td>The name of the artist.</td>
</tr>
<tr>
<td><strong>{album_name}</strong></td>
@@ -1482,8 +1727,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>{poster_url}</strong></td>
<td>A URL for the movie or TV show poster.
<p class="small-muted">(PMS agent must be Freebase or TheTVDB)</p></td>
<td>A URL for the movie, TV show, or album poster.</td>
</tr>
<tr>
<td><strong>{imdb_id}</strong></td>
@@ -1517,7 +1761,7 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
<tr>
<td><strong>{lastfm_url}</strong></td>
<td>The last.fm URL for the album.
<td>The Last.fm URL for the album.
<p class="small-muted">(PMS agent must be Last.fm)</p></td>
</tr>
<tr>
@@ -1542,6 +1786,29 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</tr>
</tbody>
</table>
<table class="notification-params">
<thead>
<tr>
<th>
Plex Update Available
</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>{update_version}</strong></td>
<td>The available update version for your Plex Server.</td>
</tr>
<tr>
<td><strong>{update_url}</strong></td>
<td>The available update download URL.</td>
</tr>
<tr>
<td><strong>{update_changelog}</strong></td>
<td>The changelog for the available update.</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="modal-footer"></div>
@@ -1619,6 +1886,26 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
</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>
</%def>
@@ -1646,10 +1933,11 @@ $(document).ready(function() {
authChanged = false;
httpChanged = false;
monitorChanged = false;
directoryChanged = false;
// Alert the user that their changes require a restart.
function postSaveChecks() {
if ((serverChanged && $('#monitoring_use_websocket').is(":checked")) || authChanged || httpChanged || monitorChanged) {
if ((serverChanged && $('#monitoring_use_websocket').is(":checked")) || authChanged || httpChanged || monitorChanged || directoryChanged) {
$('#restart-modal').modal('show');
}
}
@@ -1750,6 +2038,20 @@ $(document).ready(function() {
}
});
if ($("#check_github").is(":checked")) {
$("#git_update_options").show();
} else {
$("#git_update_options").hide();
}
$("#check_github").click(function(){
if ($("#check_github").is(":checked")) {
$("#git_update_options").slideDown();
} else {
$("#git_update_options").slideUp();
}
});
$( ".http-settings" ).change(function() {
httpChanged = true;
});
@@ -1762,6 +2064,10 @@ $(document).ready(function() {
monitorChanged = true;
});
$( ".directory-settings" ).change(function() {
directoryChanged = true;
});
$( ".pms-settings" ).change(function() {
serverChanged = true;
$("#pms_identifier").val("");
@@ -1895,49 +2201,61 @@ $(document).ready(function() {
$.get("/osxnotifyregister", { 'app': osx_notify_app }, function (data) { showMsg("<div class='msg'><span class='ui-icon ui-icon-check'></span>" + data + "</div>", false, true, 3000); });
})
pms_version = false;
pms_logs_debug = false;
pms_logs = false;
// Checks to see if PMS server version is >= 0.9.14 with automaatically logged IP addresses
$.ajax({
url: 'get_server_identity',
async: true,
success: function(data) {
var version = data.version.split('.')
if (parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) {
if (data.version){ $("#pms_version").text(data.version); }
var version = (data.version ? data.version.split('.') : null);
if (version && parseInt(version[0]) >= 0 && parseInt(version[1]) >= 9 && parseInt(version[2]) >= 14) {
$("#debugLogCheck").html("IP address is automatically logged for PMS version 0.9.14 and above.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", true);
pms_version = true;
checkLogsPath();
} else {
// Check to see if debug logs are enabled on the PMS.
$.ajax({
url: 'get_server_pref',
data: { pref: 'logDebug' },
async: true,
success: function(data) {
if (data !== 'true') {
$("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/201643703-Reporting-issues-with-Plex-Media-Server')}'>Click here</a> for help.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
pms_logs_debug = (data == 'true' ? true : false);
// Check to see if our logs folder is set before allowing IP logging to be enabled.
checkLogsPath();
}
});
}
}
});
// Check to see if our logs folder is set before allowing IP logging to be enabled.
checkLogsPath();
$("#pms_logs_folder").change(function() {
checkLogsPath();
});
function checkLogsPath() {
if ($("#pms_logs_folder").val() == '') {
$("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab.");
pms_logs = ($("#pms_logs_folder").val() == '' ? false : true);
// Toggle IP logging checkbox depending on debug logs, and logs path
if (!(pms_version)) {
if (pms_logs_debug && pms_logs) {
$("#ip_logging_enable").attr("disabled", false);
$("#debugLogCheck").html("");
} else if (!(pms_logs_debug)) {
$("#debugLogCheck").html("Debug logging must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/201643703-Reporting-issues-with-Plex-Media-Server')}'>Click here</a> for help.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
} else {
$("#ip_logging_enable").attr("disabled", false);
$("#debugLogCheck").html("");
$("#debugLogCheck").html("You must first define your Plex Server Logs folder path under the Plex Media Server tab.");
$("#ip_logging_enable").attr("disabled", true);
$("#ip_logging_enable").attr("checked", false);
}
}
}
}
});
$.ajax({
url: 'get_server_pref',
@@ -1947,6 +2265,7 @@ $(document).ready(function() {
if (data !== 'true') {
$("#remoteAccessCheck").html("Remote access must be enabled on your Plex Server. <a target='_blank' href='${anon_url('https://support.plex.tv/hc/en-us/articles/200484543-Enabling-Remote-Access-for-a-Server')}'>Click here</a> for help.");
$("#monitor_remote_access").attr("disabled", true);
$("#monitor_remote_access").attr("checked", false);
}
}
});
@@ -2050,6 +2369,21 @@ $(document).ready(function() {
}
getSchedulerTable();
$("#notify_recently_added_grandparent").change(function () {
var c = this.checked ? '#eb8600' : '#737373';
$('#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>
</%def>

View File

@@ -112,8 +112,16 @@ from plexpy import helpers
<div class="row">
<div class="col-md-12">
<div class="table-card-header">
<ul class="nav nav-header nav-dashboard pull-right">
<li>
<a href="#" id="recently-watched-page-left" class="paginate btn-gray disabled" data-id="+1"><i class="fa fa-lg fa-chevron-left"></i></a>
</li>
<li>
<a href="#" id="recently-watched-page-right" class="paginate btn-gray" data-id="-1"><i class="fa fa-lg fa-chevron-right"></i></a>
</li>
</ul>
<div class="header-bar">
<span><i class="fa fa-history"></i> Recently Watched</span>
<span><i class="fa fa-history"></i> Recently Played</span>
</div>
</div>
<div class="table-card-back">
@@ -140,7 +148,7 @@ from plexpy import helpers
</div>
</div>
<div class="table-card-back">
<table id="user_ip_table" class="display" width="100%">
<table class="display user_ip_table" id="user_ip_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left">Last Seen</th>
@@ -178,7 +186,7 @@ from plexpy import helpers
</div>
</div>
<div class="table-card-back">
<table class="display" id="history_table" width="100%">
<table class="display history_table" id="history_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left" id="delete">Delete</th>
@@ -218,7 +226,7 @@ from plexpy import helpers
</div>
</div>
<div class="table-card-back">
<table class="display" id="sync_table" width="100%">
<table class="display" id="sync_table-UID-${data['user_id']}" width="100%">
<thead>
<tr>
<th align="left" id="state">State</th>
@@ -348,13 +356,13 @@ from plexpy import helpers
};
}
}
history_table = $('#history_table').DataTable(history_table_options);
history_table = $('#history_table-UID-${data["user_id"]}').DataTable(history_table_options);
history_table.column(2).visible(false);
var colvis = new $.fn.dataTable.ColVis(history_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark', exclude: [0, 11] });
$(colvis.button()).appendTo('#button-bar-history');
clearSearchButton('history_table', history_table);
clearSearchButton('history_table-UID-${data["user_id"]}', history_table);
$('#history_table_filter').prepend('<div class="btn-group" data-toggle="buttons" id="media_type-selection" style="padding-right: 15px;"> \
<label class="btn btn-dark active"> \
@@ -397,9 +405,9 @@ from plexpy import helpers
};
}
}
user_ip_table = $('#user_ip_table').DataTable(user_ip_table_options);
user_ip_table = $('#user_ip_table-UID-${data["user_id"]}').DataTable(user_ip_table_options);
clearSearchButton('user_ip_table', user_ip_table);
clearSearchButton('user_ip_table-UID-${data["user_id"]}', user_ip_table);
});
$( "#sync-tab-btn" ).one( "click", function() {
@@ -410,13 +418,13 @@ from plexpy import helpers
d.user_id = user_id;
}
}
sync_table = $('#sync_table').DataTable(sync_table_options);
sync_table = $('#sync_table-UID-${data["user_id"]}').DataTable(sync_table_options);
sync_table.column(1).visible(false);
var colvis_sync = new $.fn.dataTable.ColVis( sync_table, { buttonText: '<i class="fa fa-columns"></i> Select columns', buttonClass: 'btn btn-dark' } );
$( colvis_sync.button() ).appendTo('#button-bar-sync');
clearSearchButton('sync_table', sync_table);
clearSearchButton('sync_table-UID-${data["user_id"]}', sync_table);
});
// Load edit user modal
@@ -471,32 +479,60 @@ from plexpy import helpers
});
function recentlyWatched() {
var widthVal = $("#user-recently-watched").width();
var tmp = (widthVal-25) / 175;
if (tmp > 0) {
var containerSize = parseInt(tmp);
} else {
var containerSize = 1;
}
// Populate recently watched
$.ajax({
url: 'get_user_recently_watched',
async: true,
data: {
user_id: user_id,
limit: containerSize
limit: 50
},
complete: function(xhr, status) {
$("#user-recently-watched").html(xhr.responseText);
highlightWatchedScrollerButton();
}
});
}
recentlyWatched();
function highlightWatchedScrollerButton() {
var scroller = $("#recently-watched-row-scroller");
var numElems = scroller.find("li").length;
scroller.width(numElems * 175);
if (scroller.width() > $("#user-recently-watched").width()) {
$("#recently-watched-page-right").removeClass("disabled");
} else {
$("#recently-watched-page-right").addClass("disabled");
}
}
$(window).resize(function() {
recentlyWatched();
highlightWatchedScrollerButton();
});
var leftTotal = 0;
$(".paginate").click(function (e) {
e.preventDefault();
var scroller = $("#recently-watched-row-scroller");
var containerWidth = $("#user-recently-watched").width();
var scrollAmount = $(this).data("id") * parseInt(containerWidth / 175) * 175;
var leftMax = Math.min(-parseInt(scroller.width()) + Math.abs(scrollAmount), 0);
leftTotal = Math.max(Math.min(leftTotal + scrollAmount, 0), leftMax);
scroller.animate({ left: leftTotal }, 250);
if (leftTotal == 0) {
$("#recently-watched-page-left").addClass("disabled").blur();
} else {
$("#recently-watched-page-left").removeClass("disabled");
}
if (leftTotal == leftMax) {
$("#recently-watched-page-right").addClass("disabled").blur();
} else {
$("#recently-watched-page-right").removeClass("disabled");
}
});
});
</script>

View File

@@ -28,6 +28,7 @@ DOCUMENTATION :: END
% if data:
<div class="dashboard-recent-media-row">
<div id="recently-watched-row-scroller" style="left: 0;">
<ul class="dashboard-recent-media list-unstyled">
% for item in data:
<li>
@@ -79,6 +80,7 @@ DOCUMENTATION :: END
% endfor
</ul>
</div>
</div>
% else:
<div class="text-muted">No stats to show.</div><br>
% endif

View File

@@ -33,12 +33,13 @@ load_rc_config ${name}
: ${plexpy_dir:="/usr/local/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""}
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="/usr/sbin/daemon"
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
command="${plexpy_dir}/PlexPy.py"
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
# Ensure user is root when running this script.
if [ `id -u` != "0" ]; then

5
init-scripts/init.freenas Normal file → Executable file
View File

@@ -33,12 +33,13 @@ load_rc_config ${name}
: ${plexpy_dir:="/usr/local/share/plexpy"}
: ${plexpy_chdir:="${plexpy_dir}"}
: ${plexpy_pid:="${plexpy_dir}/plexpy.pid"}
: ${plexpy_flags:=""}
status_cmd="${name}_status"
stop_cmd="${name}_stop"
command="/usr/sbin/daemon"
command_args="python2 ${plexpy_dir}/PlexPy.py --daemon --pidfile ${plexpy_pid} --quiet --nolaunch"
command="${plexpy_dir}/PlexPy.py"
command_args="--daemon --pidfile ${plexpy_pid} --quiet --nolaunch ${plexpy_flags}"
# Ensure user is root when running this script.
if [ `id -u` != "0" ]; then

View File

@@ -74,6 +74,8 @@ UMASK = None
POLLING_FAILOVER = False
DEV = False
def initialize(config_file):
with INIT_LOCK:
@@ -103,7 +105,7 @@ def initialize(config_file):
if not CONFIG.HTTPS_KEY:
CONFIG.HTTPS_KEY = os.path.join(DATA_DIR, 'server.key')
if not CONFIG.LOG_DIR.startswith(os.path.abspath(DATA_DIR)):
if not CONFIG.LOG_DIR:
CONFIG.LOG_DIR = os.path.join(DATA_DIR, 'logs')
if not os.path.exists(CONFIG.LOG_DIR):
@@ -120,8 +122,7 @@ def initialize(config_file):
logger.initLogger(console=not QUIET, log_dir=CONFIG.LOG_DIR,
verbose=VERBOSE)
if not CONFIG.BACKUP_DIR.startswith(os.path.abspath(DATA_DIR)):
# Put the backup dir in the data dir for now
if not CONFIG.BACKUP_DIR:
CONFIG.BACKUP_DIR = os.path.join(DATA_DIR, 'backups')
if not os.path.exists(CONFIG.BACKUP_DIR):
try:
@@ -129,14 +130,13 @@ def initialize(config_file):
except OSError as e:
logger.error("Could not create backup dir '%s': %s", BACKUP_DIR, e)
if not CONFIG.CACHE_DIR.startswith(os.path.abspath(DATA_DIR)):
# Put the cache dir in the data dir for now
if not CONFIG.CACHE_DIR:
CONFIG.CACHE_DIR = os.path.join(DATA_DIR, 'cache')
if not os.path.exists(CONFIG.CACHE_DIR):
try:
os.makedirs(CONFIG.CACHE_DIR)
except OSError as e:
logger.error("Could not create cache dir '%s': %s", DATA_DIR, e)
logger.error("Could not create cache dir '%s': %s", CACHE_DIR, e)
# Initialize the database
logger.info('Checking to see if the database has all tables....')
@@ -306,6 +306,13 @@ def initialize_scheduler():
schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=0)
if CONFIG.MONITOR_PMS_UPDATES:
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=12, minutes=0, seconds=0)
else:
schedule_job(activity_pinger.check_server_updates, 'Check for Plex updates',
hours=0, minutes=0, seconds=0)
if CONFIG.MONITOR_REMOTE_ACCESS:
schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
hours=0, minutes=0, seconds=seconds)
@@ -390,7 +397,7 @@ def dbcheck():
# sessions table :: This is a temp table that logs currently active sessions
c_db.execute(
'CREATE TABLE IF NOT EXISTS sessions (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'session_key INTEGER, rating_key INTEGER, section_id INTEGER, media_type TEXT, started INTEGER, '
'session_key INTEGER, 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, '
'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, '
@@ -413,8 +420,8 @@ def dbcheck():
# session_history_media_info table :: This is a table which logs each session's media info
c_db.execute(
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, '
'rating_key INTEGER, video_decision TEXT, audio_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
'CREATE TABLE IF NOT EXISTS session_history_media_info (id INTEGER PRIMARY KEY, rating_key INTEGER, '
'video_decision TEXT, audio_decision TEXT, transcode_decision TEXT, duration INTEGER DEFAULT 0, width INTEGER, '
'height INTEGER, container TEXT, video_codec TEXT, audio_codec TEXT, bitrate INTEGER, video_resolution TEXT, '
'video_framerate TEXT, aspect_ratio TEXT, audio_channels INTEGER, transcode_protocol TEXT, '
'transcode_container TEXT, transcode_video_codec TEXT, transcode_audio_codec TEXT, '
@@ -443,10 +450,10 @@ def dbcheck():
# notify_log table :: This is a table which logs notifications sent
c_db.execute(
'CREATE TABLE IF NOT EXISTS notify_log (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'session_key INTEGER, rating_key INTEGER, user_id INTEGER, user TEXT, '
'agent_id INTEGER, agent_name TEXT, on_play INTEGER, on_stop INTEGER, on_watched INTEGER, '
'on_pause INTEGER, on_resume INTEGER, on_buffer INTEGER, on_created INTEGER)'
'CREATE TABLE IF NOT EXISTS notify_log (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, '
'session_key INTEGER, rating_key INTEGER, parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'user_id INTEGER, user TEXT, agent_id INTEGER, agent_name TEXT, notify_action TEXT, '
'subject_text TEXT, body_text TEXT, script_args TEXT, poster_url TEXT)'
)
# library_sections table :: This table keeps record of the servers library sections
@@ -614,6 +621,15 @@ def dbcheck():
'ALTER TABLE sessions ADD COLUMN section_id INTEGER'
)
# Upgrade sessions table from earlier versions
try:
c_db.execute('SELECT stopped FROM sessions')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table sessions.")
c_db.execute(
'ALTER TABLE sessions ADD COLUMN stopped INTEGER'
)
# Upgrade session_history table from earlier versions
try:
c_db.execute('SELECT reference_id FROM session_history')
@@ -664,6 +680,21 @@ def dbcheck():
'ALTER TABLE session_history_metadata ADD COLUMN section_id INTEGER'
)
# Upgrade session_history_media_info table from earlier versions
try:
c_db.execute('SELECT transcode_decision FROM session_history_media_info')
except sqlite3.OperationalError:
logger.debug(u"Altering database. Updating database table session_history_media_info.")
c_db.execute(
'ALTER TABLE session_history_media_info ADD COLUMN transcode_decision TEXT'
)
c_db.execute(
'UPDATE session_history_media_info SET transcode_decision = (CASE '
'WHEN video_decision = "transcode" OR audio_decision = "transcode" THEN "transcode" '
'WHEN video_decision = "copy" OR audio_decision = "copy" THEN "copy" '
'WHEN video_decision = "direct play" OR audio_decision = "direct play" THEN "direct play" END)'
)
# Upgrade users table from earlier versions
try:
c_db.execute('SELECT do_notify FROM users')
@@ -702,26 +733,49 @@ def dbcheck():
# Upgrade notify_log table from earlier versions
try:
c_db.execute('SELECT on_pause FROM notify_log')
c_db.execute('SELECT poster_url 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'
'ALTER TABLE notify_log ADD COLUMN poster_url TEXT'
)
# Upgrade notify_log table from earlier versions
# Upgrade notify_log table from earlier versions (populate table with data from notify_log)
try:
c_db.execute('SELECT on_created FROM notify_log')
c_db.execute('SELECT timestamp 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'
'CREATE TABLE IF NOT EXISTS notify_log_temp (id INTEGER PRIMARY KEY AUTOINCREMENT, timestamp INTEGER, '
'session_key INTEGER, rating_key INTEGER, parent_rating_key INTEGER, grandparent_rating_key INTEGER, '
'user_id INTEGER, user TEXT, agent_id INTEGER, agent_name TEXT, notify_action TEXT, '
'subject_text TEXT, body_text TEXT, script_args TEXT, poster_url TEXT)'
)
c_db.execute(
'INSERT INTO notify_log_temp (session_key, rating_key, user_id, user, agent_id, agent_name, '
'poster_url, timestamp, notify_action) '
'SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, timestamp, '
'notify_action FROM notify_log_temp '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_play, "play" FROM notify_log WHERE on_play '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_stop, "stop" FROM notify_log WHERE on_stop '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_watched, "watched" FROM notify_log WHERE on_watched '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_pause, "pause" FROM notify_log WHERE on_pause '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_resume, "resume" FROM notify_log WHERE on_resume '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_buffer, "buffer" FROM notify_log WHERE on_buffer '
'UNION ALL SELECT session_key, rating_key, user_id, user, agent_id, agent_name, poster_url, '
'on_created, "created" FROM notify_log WHERE on_created '
'ORDER BY timestamp ')
c_db.execute(
'DROP TABLE notify_log'
)
c_db.execute(
'ALTER TABLE notify_log_temp RENAME TO notify_log'
)
# Upgrade library_sections table from earlier versions (remove UNIQUE constraint on section_id)
@@ -843,6 +897,12 @@ def shutdown(restart=False, update=False):
if '--nolaunch' not in args:
args += ['--nolaunch']
logger.info('Restarting PlexPy with %s', args)
# os.execv fails with spaced names on Windows
# https://bugs.python.org/issue19066
if os.name == 'nt':
subprocess.Popen(args, cwd=os.getcwd())
else:
os.execv(exe, args)
os._exit(0)

View File

@@ -16,7 +16,7 @@
import time
import plexpy
from plexpy import logger, pmsconnect, activity_processor, threading, notification_handler, helpers
from plexpy import logger, pmsconnect, activity_processor, threading, notification_handler, helpers, notifiers
class ActivityHandler(object):
@@ -57,6 +57,8 @@ class ActivityHandler(object):
if self.is_valid_session() and self.get_live_session():
logger.debug(u"PlexPy ActivityHandler :: Session %s has started." % str(self.get_session_key()))
# Check if any notification agents have notifications enabled
if any(d['on_play'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=self.get_live_session(), notify_action='play')).start()
@@ -77,11 +79,14 @@ class ActivityHandler(object):
if not force_stop:
ap.set_session_state(session_key=self.get_session_key(),
state=self.timeline['state'],
view_offset=self.timeline['viewOffset'])
view_offset=self.timeline['viewOffset'],
stopped=int(time.time()))
# Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Check if any notification agents have notifications enabled
if any(d['on_stop'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='stop')).start()
@@ -91,6 +96,7 @@ class ActivityHandler(object):
monitor_proc.write_session_history(session=db_session)
# Remove the session from our temp session table
logger.debug(u"PlexPy ActivityHandler :: Removing session %s from session queue" % str(self.get_session_key()))
ap.delete_session(session_key=self.get_session_key())
def on_pause(self):
@@ -109,6 +115,8 @@ class ActivityHandler(object):
# Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Check if any notification agents have notifications enabled
if any(d['on_pause'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='pause')).start()
@@ -129,6 +137,8 @@ class ActivityHandler(object):
# Retrieve the session data from our temp table
db_session = ap.get_session_by_key(session_key=self.get_session_key())
# Check if any notification agents have notifications enabled
if any(d['on_resume'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='resume')).start()
@@ -159,6 +169,9 @@ class ActivityHandler(object):
if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \
time_since_last_trigger == 0 or time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT):
ap.set_session_buffer_trigger_time(session_key=self.get_session_key())
# Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
@@ -202,10 +215,17 @@ class ActivityHandler(object):
# Monitor if the stream has reached the watch percentage for notifications
# The only purpose of this is for notifications
# Check if any notification agents have notifications enabled
notify_agents = [d['id'] for d in notifiers.available_notification_agents() if d['on_watched']]
# Get the current states for notifications from our db
notified_agents = [d['agent_id'] for d in notification_handler.get_notify_state(session=db_session)
if d['notify_action'] == 'watched'] if notify_agents else []
if any(a not in notified_agents for a in notify_agents):
progress_percent = helpers.get_percent(self.timeline['viewOffset'], db_session['duration'])
if progress_percent >= plexpy.CONFIG.NOTIFY_WATCHED_PERCENT and this_state != 'buffering':
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=db_session, notify_action='watched')).start()
# Rather not put this on it's own thread so we know it completes before our next event.
notification_handler.notify(stream_data=db_session, notify_action='watched')
else:
# We don't have this session in our table yet, start a new one.

View File

@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor, libraries
from plexpy import logger, pmsconnect, plextv, notification_handler, database, helpers, activity_processor, libraries, notifiers
import threading
import plexpy
@@ -38,6 +38,9 @@ def check_active_sessions(ws_request=False):
if session_list:
if int_ping_count >= 3:
logger.info(u"PlexPy Monitor :: The Plex Media Server is back up.")
# Check if any notification agents have notifications enabled
if any(d['on_intup'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intup')).start()
@@ -58,12 +61,20 @@ def check_active_sessions(ws_request=False):
# Here we can check the play states
if session['state'] != stream['state']:
if session['state'] == 'paused':
logger.debug(u"PlexPy Monitor :: Session %s has been paused." % stream['session_key'])
# Check if any notification agents have notifications enabled
if any(d['on_pause'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='pause')).start()
if session['state'] == 'playing' and stream['state'] == 'paused':
logger.debug(u"PlexPy Monitor :: Session %s has been resumed." % stream['session_key'])
# Check if any notification agents have notifications enabled
if any(d['on_resume'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
@@ -105,6 +116,10 @@ def check_active_sessions(ws_request=False):
'WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
# Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
else:
@@ -119,11 +134,16 @@ def check_active_sessions(ws_request=False):
'WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
# Check if any notification agents have notifications enabled
if any(d['on_buffer'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='buffer')).start()
logger.debug(u"PlexPy Monitor :: Stream buffering. Count is now %s. Last triggered %s."
% (buffer_values[0]['buffer_count'],
logger.debug(u"PlexPy Monitor :: Session %s is buffering. Count is now %s. Last triggered %s."
% (stream['session_key'],
buffer_values[0]['buffer_count'],
buffer_values[0]['buffer_last_triggered']))
# Check if the user has reached the offset in the media we defined as the "watched" percent
@@ -132,6 +152,8 @@ def check_active_sessions(ws_request=False):
if session['view_offset'] and session['duration'] and session['state'] != 'buffering':
if helpers.get_percent(session['view_offset'],
session['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Check if any notification agents have notifications enabled
if any(d['on_watched'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
@@ -139,30 +161,52 @@ def check_active_sessions(ws_request=False):
else:
# The user has stopped playing a stream
logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue"
% (stream['session_key'], stream['rating_key']))
monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
if stream['state'] != 'stopped':
logger.debug(u"PlexPy Monitor :: Session %s has stopped." % stream['session_key'])
# Set the stream stop time
stream['stopped'] = int(time.time())
monitor_db.action('UPDATE sessions SET stopped = ?, state = ? '
'WHERE session_key = ? AND rating_key = ?',
[stream['stopped'], 'stopped', stream['session_key'], stream['rating_key']])
# Check if the user has reached the offset in the media we defined as the "watched" percent
if stream['view_offset'] and stream['duration']:
if helpers.get_percent(stream['view_offset'],
stream['duration']) > plexpy.CONFIG.NOTIFY_WATCHED_PERCENT:
# Check if any notification agents have notifications enabled
if any(d['on_watched'] for d in notifiers.available_notification_agents()):
# Push any notifications -
# Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='watched')).start()
# Check if any notification agents have notifications enabled
if any(d['on_stop'] for d in notifiers.available_notification_agents()):
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=stream, notify_action='stop')).start()
# Write the item history on playback stop
monitor_process.write_session_history(session=stream)
success = monitor_process.write_session_history(session=stream)
if success:
# If session is written to the databaase successfully, remove the session from the session table
logger.debug(u"PlexPy Monitor :: Removing sessionKey %s ratingKey %s from session queue"
% (stream['session_key'], stream['rating_key']))
monitor_db.action('DELETE FROM sessions WHERE session_key = ? AND rating_key = ?',
[stream['session_key'], stream['rating_key']])
else:
logger.warn(u"PlexPy Monitor :: Failed to write sessionKey %s ratingKey %s to the database. " \
"Will try again on the next pass." % (stream['session_key'], stream['rating_key']))
# Process the newly received session data
for session in media_container:
monitor_process.write_session(session)
new_session = monitor_process.write_session(session)
if new_session:
logger.debug(u"PlexPy Monitor :: Session %s has started." % session['session_key'])
else:
logger.debug(u"PlexPy Monitor :: Unable to read session list.")
@@ -171,6 +215,8 @@ def check_active_sessions(ws_request=False):
% str(int_ping_count))
if int_ping_count == 3:
# Check if any notification agents have notifications enabled
if any(d['on_intdown'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='intdown')).start()
@@ -225,6 +271,9 @@ def check_recently_added():
if 0 < time_threshold - int(item['added_at']) <= time_interval:
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
# Check if any notification agents have notifications enabled
if any(d['on_created'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
@@ -243,6 +292,9 @@ def check_recently_added():
% str(item['rating_key']))
logger.debug(u"PlexPy Monitor :: Library item %s has been added to Plex." % str(item['rating_key']))
# Check if any notification agents have notifications enabled
if any(d['on_created'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(timeline_data=item, notify_action='created')).start()
@@ -275,12 +327,44 @@ def check_server_response():
else:
if ext_ping_count >= 3:
logger.info(u"PlexPy Monitor :: Plex remote access is back up.")
# Check if any notification agents have notifications enabled
if any(d['on_extup'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extup')).start()
ext_ping_count = 0
if ext_ping_count == 3:
# Check if any notification agents have notifications enabled
if any(d['on_extdown'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='extdown')).start()
def check_server_updates():
with monitor_lock:
logger.info(u"PlexPy Monitor :: Checking for PMS updates...")
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = pms_connect.get_update_staus()
if server_identity and update_status:
version = server_identity['version']
logger.info(u"PlexPy Monitor :: Current PMS version: %s", version)
if update_status['state'] == 'available':
update_version = update_status['version']
logger.info(u"PlexPy Monitor :: PMS update available version: %s", update_version)
# Check if any notification agents have notifications enabled
if any(d['on_pmsupdate'] for d in notifiers.available_notification_agents()):
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
kwargs=dict(notify_action='pmsupdate')).start()
else:
logger.info(u"PlexPy Monitor :: No PMS update available.")

View File

@@ -13,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, pmsconnect, notification_handler, log_reader, database
from plexpy import logger, pmsconnect, notification_handler, log_reader, database, notifiers
import threading
import plexpy
@@ -78,9 +78,10 @@ class ActivityProcessor(object):
result = self.db.upsert('sessions', values, keys)
if result == 'insert':
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
if notify:
# Check if any notification agents have notifications enabled
if notify and any(d['on_play'] for d in notifiers.available_notification_agents()):
values.update({'ip_address': session['ip_address']})
# Push any notifications - Push it on it's own thread so we don't hold up our db actions
threading.Thread(target=notification_handler.notify,
kwargs=dict(stream_data=values, notify_action='play')).start()
@@ -97,17 +98,24 @@ class ActivityProcessor(object):
ip_address = {'ip_address': ip_address}
self.db.upsert('sessions', ip_address, keys)
return True
def write_session_history(self, session=None, import_metadata=None, is_import=False, import_ignore_interval=0):
from plexpy import users, libraries
section_id = session['section_id'] if not is_import else import_metadata['section_id']
if not is_import:
user_data = users.Users()
user_details = user_data.get_details(user_id=session['user_id'])
section_id = session['section_id'] if not is_import else import_metadata['section_id']
library_data = libraries.Libraries()
library_details = library_data.get_details(section_id=section_id)
# Return false if failed to retrieve user or library details
if not user_details or not library_details:
return False
if session:
logging_enabled = False
@@ -116,8 +124,13 @@ class ActivityProcessor(object):
stopped = int(session['stopped'])
else:
stopped = int(time.time())
elif session['stopped']:
stopped = int(session['stopped'])
else:
stopped = int(time.time())
self.set_session_state(session_key=session['session_key'],
state='stopped',
stopped=stopped)
if plexpy.CONFIG.MOVIE_LOGGING_ENABLE and str(session['rating_key']).isdigit() and \
session['media_type'] == 'movie':
@@ -137,14 +150,14 @@ class ActivityProcessor(object):
else:
real_play_time = stopped - session['started']
if plexpy.CONFIG.LOGGING_IGNORE_INTERVAL and not is_import:
if not is_import and plexpy.CONFIG.LOGGING_IGNORE_INTERVAL:
if (session['media_type'] == 'movie' or session['media_type'] == 'episode') and \
(real_play_time < int(plexpy.CONFIG.LOGGING_IGNORE_INTERVAL)):
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." %
(session['rating_key'], str(real_play_time), plexpy.CONFIG.LOGGING_IGNORE_INTERVAL))
if session['media_type'] == 'track' and not is_import:
if not is_import and session['media_type'] == 'track':
if real_play_time < 15 and session['duration'] >= 30:
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs, "
@@ -156,17 +169,29 @@ class ActivityProcessor(object):
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: Play duration for ratingKey %s is %s secs which is less than %s "
u"seconds, so we're not logging it." %
(session['rating_key'], str(real_play_time),
import_ignore_interval))
(session['rating_key'], str(real_play_time), import_ignore_interval))
if not user_details['keep_history'] and not is_import:
if not is_import and not user_details['keep_history']:
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: History logging for user '%s' is disabled." % user_details['username'])
elif not library_details['keep_history'] and not is_import:
elif not is_import and not library_details['keep_history']:
logging_enabled = False
logger.debug(u"PlexPy ActivityProcessor :: History logging for library '%s' is disabled." % library_details['section_name'])
if logging_enabled:
# Fetch metadata first so we can return false if it fails
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
if result:
metadata = result['metadata']
else:
return False
else:
metadata = import_metadata
# logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history table...")
query = 'INSERT INTO session_history (started, stopped, rating_key, parent_rating_key, ' \
'grandparent_rating_key, media_type, user_id, user, ip_address, paused_counter, player, ' \
@@ -218,13 +243,22 @@ class ActivityProcessor(object):
# % last_id)
# Write the session_history_media_info table
# Generate a combined transcode decision value
if session['video_decision'] == 'transcode' or session['audio_decision'] == 'transcode':
transcode_decision = 'transcode'
elif session['video_decision'] == 'copy' or session['audio_decision'] == 'copy':
transcode_decision = 'copy'
else:
transcode_decision = 'direct play'
# logger.debug(u"PlexPy ActivityProcessor :: Attempting to write to session_history_media_info table...")
query = 'INSERT INTO session_history_media_info (id, rating_key, video_decision, audio_decision, ' \
'duration, width, height, container, video_codec, audio_codec, bitrate, video_resolution, ' \
'video_framerate, aspect_ratio, audio_channels, transcode_protocol, transcode_container, ' \
'transcode_video_codec, transcode_audio_codec, transcode_audio_channels, transcode_width, ' \
'transcode_height) VALUES ' \
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
'transcode_height, transcode_decision) VALUES ' \
'(last_insert_rowid(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)'
args = [session['rating_key'], session['video_decision'], session['audio_decision'],
session['duration'], session['width'], session['height'], session['container'],
@@ -232,19 +266,12 @@ class ActivityProcessor(object):
session['video_resolution'], session['video_framerate'], session['aspect_ratio'],
session['audio_channels'], session['transcode_protocol'], session['transcode_container'],
session['transcode_video_codec'], session['transcode_audio_codec'],
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height']]
session['transcode_audio_channels'], session['transcode_width'], session['transcode_height'],
transcode_decision]
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_media_info transaction...")
self.db.action(query=query, args=args)
if not is_import:
logger.debug(u"PlexPy ActivityProcessor :: Fetching metadata for item ratingKey %s" % session['rating_key'])
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=str(session['rating_key']))
metadata = result['metadata']
else:
metadata = import_metadata
# Write the session_history_metadata table
directors = ";".join(metadata['directors'])
writers = ";".join(metadata['writers'])
@@ -280,6 +307,9 @@ class ActivityProcessor(object):
# logger.debug(u"PlexPy ActivityProcessor :: Writing session_history_metadata transaction...")
self.db.action(query=query, args=args)
# Return true when the session is successfully written to the database
return True
def find_session_ip(self, rating_key=None, machine_id=None):
logger.debug(u"PlexPy ActivityProcessor :: Requesting log lines...")
@@ -351,12 +381,16 @@ class ActivityProcessor(object):
return None
def set_session_state(self, session_key=None, state=None, view_offset=0):
if str(session_key).isdigit() and str(view_offset).isdigit():
values = {'view_offset': int(view_offset)}
def set_session_state(self, session_key=None, state=None, **kwargs):
if str(session_key).isdigit():
values = {}
if state:
values['state'] = state
for k,v in kwargs.iteritems():
values[k] = v
keys = {'session_key': session_key}
result = self.db.upsert('sessions', values, keys)

View File

@@ -62,6 +62,7 @@ MEDIA_FLAGS_VIDEO = {'avc1': 'h264',
SCHEDULER_LIST = ['Check GitHub for updates',
'Check for active sessions',
'Check for recently added items',
'Check for Plex updates',
'Check for Plex remote access',
'Refresh users list',
'Refresh libraries list',

View File

@@ -48,6 +48,7 @@ _CONFIG_DEFINITIONS = {
'BOXCAR_ON_INTDOWN': (int, 'Boxcar', 0),
'BOXCAR_ON_EXTUP': (int, 'Boxcar', 0),
'BOXCAR_ON_INTUP': (int, 'Boxcar', 0),
'BOXCAR_ON_PMSUPDATE': (int, 'Boxcar', 0),
'BUFFER_THRESHOLD': (int, 'Monitoring', 3),
'BUFFER_WAIT': (int, 'Monitoring', 900),
'BACKUP_DIR': (str, 'General', ''),
@@ -81,6 +82,7 @@ _CONFIG_DEFINITIONS = {
'EMAIL_ON_INTDOWN': (int, 'Email', 0),
'EMAIL_ON_EXTUP': (int, 'Email', 0),
'EMAIL_ON_INTUP': (int, 'Email', 0),
'EMAIL_ON_PMSUPDATE': (int, 'Email', 0),
'ENABLE_HTTPS': (int, 'General', 0),
'FACEBOOK_ENABLED': (int, 'Facebook', 0),
'FACEBOOK_REDIRECT_URI': (str, 'Facebook', ''),
@@ -88,6 +90,7 @@ _CONFIG_DEFINITIONS = {
'FACEBOOK_APP_SECRET': (str, 'Facebook', ''),
'FACEBOOK_TOKEN': (str, 'Facebook', ''),
'FACEBOOK_GROUP': (str, 'Facebook', ''),
'FACEBOOK_INCL_PMSLINK': (int, 'Facebook', 0),
'FACEBOOK_INCL_POSTER': (int, 'Facebook', 1),
'FACEBOOK_INCL_SUBJECT': (int, 'Facebook', 1),
'FACEBOOK_ON_PLAY': (int, 'Facebook', 0),
@@ -101,12 +104,14 @@ _CONFIG_DEFINITIONS = {
'FACEBOOK_ON_INTDOWN': (int, 'Facebook', 0),
'FACEBOOK_ON_EXTUP': (int, 'Facebook', 0),
'FACEBOOK_ON_INTUP': (int, 'Facebook', 0),
'FACEBOOK_ON_PMSUPDATE': (int, 'Facebook', 0),
'FIRST_RUN_COMPLETE': (int, 'General', 0),
'FREEZE_DB': (int, 'General', 0),
'GET_FILE_SIZES': (int, 'General', 0),
'GET_FILE_SIZES_HOLD': (dict, 'General', {'section_ids': [], 'rating_keys': []}),
'GIT_BRANCH': (str, 'General', 'master'),
'GIT_PATH': (str, 'General', ''),
'GIT_TOKEN': (str, 'General', ''),
'GIT_USER': (str, 'General', 'drzoidberg33'),
'GRAPH_TYPE': (str, 'General', 'plays'),
'GRAPH_DAYS': (int, 'General', 30),
@@ -126,6 +131,7 @@ _CONFIG_DEFINITIONS = {
'GROWL_ON_INTDOWN': (int, 'Growl', 0),
'GROWL_ON_EXTUP': (int, 'Growl', 0),
'GROWL_ON_INTUP': (int, 'Growl', 0),
'GROWL_ON_PMSUPDATE': (int, 'Growl', 0),
'HOME_LIBRARY_CARDS': (list, 'General', ['first_run']),
'HOME_STATS_LENGTH': (int, 'General', 30),
'HOME_STATS_TYPE': (int, 'General', 0),
@@ -137,6 +143,7 @@ _CONFIG_DEFINITIONS = {
'HTTPS_KEY': (str, 'General', ''),
'HTTPS_DOMAIN': (str, 'General', 'localhost'),
'HTTPS_IP': (str, 'General', '127.0.0.1'),
'HTTP_ENVIRONMENT': (str, 'General', 'production'),
'HTTP_HOST': (str, 'General', '0.0.0.0'),
'HTTP_PASSWORD': (str, 'General', ''),
'HTTP_PORT': (int, 'General', 8181),
@@ -159,8 +166,10 @@ _CONFIG_DEFINITIONS = {
'IFTTT_ON_INTDOWN': (int, 'IFTTT', 0),
'IFTTT_ON_EXTUP': (int, 'IFTTT', 0),
'IFTTT_ON_INTUP': (int, 'IFTTT', 0),
'IFTTT_ON_PMSUPDATE': (int, 'IFTTT', 0),
'JOURNAL_MODE': (str, 'Advanced', 'wal'),
'LAUNCH_BROWSER': (int, 'General', 1),
'LOG_BLACKLIST': (int, 'General', 1),
'LOG_DIR': (str, 'General', ''),
'LOGGING_IGNORE_INTERVAL': (int, 'Monitoring', 120),
'MOVIE_LOGGING_ENABLE': (int, 'Monitoring', 1),
@@ -173,6 +182,7 @@ _CONFIG_DEFINITIONS = {
'MUSIC_NOTIFY_ON_START': (int, 'Monitoring', 1),
'MUSIC_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
'MUSIC_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
'MONITOR_PMS_UPDATES': (int, 'Monitoring', 0),
'MONITOR_REMOTE_ACCESS': (int, 'Monitoring', 0),
'MONITORING_INTERVAL': (int, 'Monitoring', 60),
'MONITORING_USE_WEBSOCKET': (int, 'Monitoring', 0),
@@ -190,7 +200,9 @@ _CONFIG_DEFINITIONS = {
'NMA_ON_INTDOWN': (int, 'NMA', 0),
'NMA_ON_EXTUP': (int, 'NMA', 0),
'NMA_ON_INTUP': (int, 'NMA', 0),
'NMA_ON_PMSUPDATE': (int, 'NMA', 0),
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
'NOTIFY_UPLOAD_POSTERS': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60),
@@ -217,6 +229,8 @@ _CONFIG_DEFINITIONS = {
'NOTIFY_ON_EXTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is back up.'),
'NOTIFY_ON_INTUP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_INTUP_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is back up.'),
'NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
'NOTIFY_ON_PMSUPDATE_BODY_TEXT': (unicode, 'Monitoring', 'An update is available for the Plex Media Server (version {update_version}).'),
'NOTIFY_SCRIPTS_ARGS_TEXT': (unicode, 'Monitoring', ''),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
@@ -231,6 +245,7 @@ _CONFIG_DEFINITIONS = {
'OSX_NOTIFY_ON_INTDOWN': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_EXTUP': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_INTUP': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PMSUPDATE': (int, 'OSX_Notify', 0),
'PLEX_CLIENT_HOST': (str, 'Plex', ''),
'PLEX_ENABLED': (int, 'Plex', 0),
'PLEX_PASSWORD': (str, 'Plex', ''),
@@ -246,6 +261,7 @@ _CONFIG_DEFINITIONS = {
'PLEX_ON_INTDOWN': (int, 'Plex', 0),
'PLEX_ON_EXTUP': (int, 'Plex', 0),
'PLEX_ON_INTUP': (int, 'Plex', 0),
'PLEX_ON_PMSUPDATE': (int, 'Plex', 0),
'PROWL_ENABLED': (int, 'Prowl', 0),
'PROWL_KEYS': (str, 'Prowl', ''),
'PROWL_PRIORITY': (int, 'Prowl', 0),
@@ -260,6 +276,7 @@ _CONFIG_DEFINITIONS = {
'PROWL_ON_INTDOWN': (int, 'Prowl', 0),
'PROWL_ON_EXTUP': (int, 'Prowl', 0),
'PROWL_ON_INTUP': (int, 'Prowl', 0),
'PROWL_ON_PMSUPDATE': (int, 'Prowl', 0),
'PUSHALOT_APIKEY': (str, 'Pushalot', ''),
'PUSHALOT_ENABLED': (int, 'Pushalot', 0),
'PUSHALOT_ON_PLAY': (int, 'Pushalot', 0),
@@ -273,6 +290,7 @@ _CONFIG_DEFINITIONS = {
'PUSHALOT_ON_INTDOWN': (int, 'Pushalot', 0),
'PUSHALOT_ON_EXTUP': (int, 'Pushalot', 0),
'PUSHALOT_ON_INTUP': (int, 'Pushalot', 0),
'PUSHALOT_ON_PMSUPDATE': (int, 'Pushalot', 0),
'PUSHBULLET_APIKEY': (str, 'PushBullet', ''),
'PUSHBULLET_DEVICEID': (str, 'PushBullet', ''),
'PUSHBULLET_CHANNEL_TAG': (str, 'PushBullet', ''),
@@ -288,8 +306,10 @@ _CONFIG_DEFINITIONS = {
'PUSHBULLET_ON_INTDOWN': (int, 'PushBullet', 0),
'PUSHBULLET_ON_EXTUP': (int, 'PushBullet', 0),
'PUSHBULLET_ON_INTUP': (int, 'PushBullet', 0),
'PUSHBULLET_ON_PMSUPDATE': (int, 'PushBullet', 0),
'PUSHOVER_APITOKEN': (str, 'Pushover', ''),
'PUSHOVER_ENABLED': (int, 'Pushover', 0),
'PUSHOVER_HTML_SUPPORT': (int, 'Pushover', 1),
'PUSHOVER_KEYS': (str, 'Pushover', ''),
'PUSHOVER_PRIORITY': (int, 'Pushover', 0),
'PUSHOVER_SOUND': (str, 'Pushover', ''),
@@ -304,6 +324,7 @@ _CONFIG_DEFINITIONS = {
'PUSHOVER_ON_INTDOWN': (int, 'Pushover', 0),
'PUSHOVER_ON_EXTUP': (int, 'Pushover', 0),
'PUSHOVER_ON_INTUP': (int, 'Pushover', 0),
'PUSHOVER_ON_PMSUPDATE': (int, 'Pushover', 0),
'REFRESH_LIBRARIES_INTERVAL': (int, 'Monitoring', 12),
'REFRESH_LIBRARIES_ON_STARTUP': (int, 'Monitoring', 1),
'REFRESH_USERS_INTERVAL': (int, 'Monitoring', 12),
@@ -325,6 +346,7 @@ _CONFIG_DEFINITIONS = {
'SLACK_ON_INTDOWN': (int, 'Slack', 0),
'SLACK_ON_EXTUP': (int, 'Slack', 0),
'SLACK_ON_INTUP': (int, 'Slack', 0),
'SLACK_ON_PMSUPDATE': (int, 'Slack', 0),
'SCRIPTS_ENABLED': (int, 'Scripts', 0),
'SCRIPTS_FOLDER': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PLAY': (int, 'Scripts', 0),
@@ -338,6 +360,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_EXTUP': (int, 'Scripts', 0),
'SCRIPTS_ON_INTDOWN': (int, 'Scripts', 0),
'SCRIPTS_ON_INTUP': (int, 'Scripts', 0),
'SCRIPTS_ON_PMSUPDATE': (int, 'Scripts', 0),
'SCRIPTS_ON_PLAY_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_STOP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PAUSE_SCRIPT': (unicode, 'Scripts', ''),
@@ -349,6 +372,7 @@ _CONFIG_DEFINITIONS = {
'SCRIPTS_ON_EXTUP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_INTDOWN_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_INTUP_SCRIPT': (unicode, 'Scripts', ''),
'SCRIPTS_ON_PMSUPDATE_SCRIPT': (unicode, 'Scripts', ''),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
@@ -364,6 +388,7 @@ _CONFIG_DEFINITIONS = {
'TELEGRAM_ON_INTDOWN': (int, 'Telegram', 0),
'TELEGRAM_ON_EXTUP': (int, 'Telegram', 0),
'TELEGRAM_ON_INTUP': (int, 'Telegram', 0),
'TELEGRAM_ON_PMSUPDATE': (int, 'Telegram', 0),
'TV_LOGGING_ENABLE': (int, 'Monitoring', 1),
'TV_NOTIFY_ENABLE': (int, 'Monitoring', 0),
'TV_NOTIFY_ON_START': (int, 'Monitoring', 1),
@@ -386,6 +411,7 @@ _CONFIG_DEFINITIONS = {
'TWITTER_ON_INTDOWN': (int, 'Twitter', 0),
'TWITTER_ON_EXTUP': (int, 'Twitter', 0),
'TWITTER_ON_INTUP': (int, 'Twitter', 0),
'TWITTER_ON_PMSUPDATE': (int, 'Twitter', 0),
'UPDATE_DB_INTERVAL': (int, 'General', 24),
'UPDATE_SECTION_IDS': (int, 'General', 1),
'VERIFY_SSL_CERT': (bool_int, 'Advanced', 1),
@@ -404,9 +430,13 @@ _CONFIG_DEFINITIONS = {
'XBMC_ON_EXTDOWN': (int, 'XBMC', 0),
'XBMC_ON_INTDOWN': (int, 'XBMC', 0),
'XBMC_ON_EXTUP': (int, 'XBMC', 0),
'XBMC_ON_INTUP': (int, 'XBMC', 0)
'XBMC_ON_INTUP': (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
# it might be nice to refactor for fewer instance variables
@@ -420,6 +450,19 @@ class Config(object):
for key in _CONFIG_DEFINITIONS.keys():
self.check_setting(key)
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):
key = name.upper()
@@ -479,6 +522,8 @@ class Config(object):
except IOError as e:
plexpy.logger.error("Error writing configuration file: %s", e)
self._blacklist()
def __getattr__(self, name):
"""
Returns something from the ini unless it is a real property

View File

@@ -18,6 +18,7 @@ import os
import sqlite3
import shutil
import threading
import time
import logger
import plexpy
@@ -37,9 +38,21 @@ def clear_history_tables():
monitor_db.action('DELETE FROM session_history')
monitor_db.action('DELETE FROM session_history_media_info')
monitor_db.action('DELETE FROM session_history_metadata')
monitor_db.action('VACUUM;')
monitor_db.action('VACUUM')
def delete_sessions():
logger.debug(u"PlexPy Database :: Clearing temporary sessions from database.")
monitor_db = MonitorDatabase()
try:
monitor_db.action('DELETE FROM sessions')
monitor_db.action('VACUUM')
return 'Cleared temporary sessions.'
except Exception as e:
logger.warn(u"PlexPy Database :: Unable to clear temporary sessions from database: %s." % e)
return 'Unable to clear temporary sessions.'
def db_filename(filename="plexpy.db"):
""" Returns the filepath to the db """

View File

@@ -58,11 +58,10 @@ class DataFactory(object):
'session_history_metadata.thumb',
'session_history_metadata.parent_thumb',
'session_history_metadata.grandparent_thumb',
'MAX((CASE WHEN view_offset IS NULL THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 \
ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
'MAX((CASE WHEN (view_offset IS NULL OR view_offset = "") THEN 0.1 ELSE view_offset * 1.0 END) / \
(CASE WHEN (session_history_metadata.duration IS NULL OR session_history_metadata.duration = "") \
THEN 1.0 ELSE session_history_metadata.duration * 1.0 END) * 100) AS percent_complete',
'session_history_media_info.transcode_decision',
'COUNT(*) AS group_count',
'GROUP_CONCAT(session_history.id) AS group_ids'
]
@@ -138,8 +137,7 @@ class DataFactory(object):
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'thumb': thumb,
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'transcode_decision': item['transcode_decision'],
'percent_complete': int(round(item['percent_complete'])),
'watched_status': watched_status,
'group_count': item['group_count'],
@@ -626,24 +624,21 @@ class DataFactory(object):
title = 'Concurrent Transcodes'
query = base_query \
+ 'AND (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") '
+ 'AND session_history_media_info.transcode_decision = "transcode" '
result = monitor_db.select(query)
if result:
most_concurrent.append(calc_most_concurrent(title, result))
title = 'Concurrent Direct Streams'
query = base_query \
+ 'AND (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") '
+ 'AND session_history_media_info.transcode_decision = "copy" '
result = monitor_db.select(query)
if result:
most_concurrent.append(calc_most_concurrent(title, result))
title = 'Concurrent Direct Plays'
query = base_query \
+ 'AND (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") '
+ 'AND session_history_media_info.transcode_decision = "direct play" '
result = monitor_db.select(query)
if result:
most_concurrent.append(calc_most_concurrent(title, result))
@@ -828,6 +823,7 @@ class DataFactory(object):
'SUM(CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) AS total_duration ' \
'FROM session_history ' \
'JOIN session_history_metadata ON session_history_metadata.id = session_history.id ' \
'JOIN session_history_media_info ON session_history_media_info.id = session_history.id ' \
'%s ' % where
result = monitor_db.select(query)
except Exception as e:
@@ -843,19 +839,67 @@ class DataFactory(object):
def get_session_ip(self, session_key=''):
monitor_db = database.MonitorDatabase()
ip_address = 'N/A'
if session_key:
try:
query = 'SELECT ip_address FROM sessions WHERE session_key = %d' % int(session_key)
result = monitor_db.select(query)
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_session_ip: %s." % e)
return ip_address
else:
return None
ip_address = 'N/A'
return ip_address
for item in result:
ip_address = item['ip_address']
return ip_address
def get_poster_url(self, rating_key='', metadata=None):
monitor_db = database.MonitorDatabase()
poster_url = ''
poster_key = ''
if rating_key:
poster_key = rating_key
elif metadata:
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show' or \
metadata['media_type'] == 'artist' or metadata['media_type'] == 'album':
poster_key = metadata['rating_key']
elif metadata['media_type'] == 'episode':
poster_key = metadata['grandparent_rating_key']
elif metadata['media_type'] == 'season' or metadata['media_type'] == 'track':
poster_key = metadata['parent_rating_key']
if poster_key:
try:
query = 'SELECT id, poster_url FROM notify_log ' \
'WHERE rating_key = %d OR parent_rating_key = %d OR grandparent_rating_key = %d ' \
'ORDER BY id DESC LIMIT 1' % (int(poster_key), int(poster_key), int(poster_key))
result = monitor_db.select(query)
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_poster_url: %s." % e)
return poster_url
else:
return poster_url
for item in result:
poster_url = item['poster_url']
return poster_url
def delete_poster_url(self, poster_url=''):
monitor_db = database.MonitorDatabase()
if poster_url:
logger.info(u"PlexPy DataFactory :: Deleting poster_url %s from the notify log database." % poster_url)
monitor_db.upsert('notify_log', {'poster_url': None}, {'poster_url': poster_url})
return 'Deleted poster_url %s.' % poster_url
else:
return 'Unable to delete poster_url.'
def get_search_query(self, rating_key=''):
monitor_db = database.MonitorDatabase()
@@ -1095,3 +1139,83 @@ class DataFactory(object):
old_rating_key]
monitor_db.action(query=query, args=args)
def get_notification_log(self, kwargs=None):
data_tables = datatables.DataTables()
columns = ['notify_log.id',
'notify_log.timestamp',
'notify_log.session_key',
'notify_log.rating_key',
'notify_log.user_id',
'notify_log.user',
'notify_log.agent_id',
'notify_log.agent_name',
'notify_log.notify_action',
'notify_log.subject_text',
'notify_log.body_text',
'notify_log.script_args',
'notify_log.poster_url',
]
try:
query = data_tables.ssp_query(table_name='notify_log',
columns=columns,
custom_where=[],
group_by=[],
join_types=[],
join_tables=[],
join_evals=[],
kwargs=kwargs)
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for get_notification_log: %s." % e)
return {'recordsFiltered': 0,
'recordsTotal': 0,
'draw': 0,
'data': 'null',
'error': 'Unable to execute database query.'}
notifications = query['result']
rows = []
for item in notifications:
if item['body_text']:
body_text = item['body_text'].replace('\r\n', '<br />').replace('\n', '<br />')
else:
body_text = ''
row = {'id': item['id'],
'timestamp': item['timestamp'],
'session_key': item['session_key'],
'rating_key': item['rating_key'],
'user_id': item['user_id'],
'user': item['user'],
'agent_id': item['agent_id'],
'agent_name': item['agent_name'],
'notify_action': item['notify_action'],
'subject_text': item['subject_text'],
'body_text': body_text,
'script_args': item['script_args'],
'poster_url': item['poster_url']
}
rows.append(row)
dict = {'recordsFiltered': query['filteredCount'],
'recordsTotal': query['totalCount'],
'data': rows,
'draw': query['draw']
}
return dict
def delete_notification_log(self):
monitor_db = database.MonitorDatabase()
try:
logger.info(u"PlexPy DataFactory :: Clearing notification logs from database.")
monitor_db.action('DELETE FROM notify_log')
monitor_db.action('VACUUM')
return 'Cleared notification logs.'
except Exception as e:
logger.warn(u"PlexPy DataFactory :: Unable to execute database query for delete_notification_log: %s." % e)
return 'Unable to clear notification logs.'

View File

@@ -490,40 +490,39 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE (datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime")) AND ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \
'session_history.media_type = "track") ' \
'GROUP BY date_played ' \
'ORDER BY started ASC' % time_range
result = monitor_db.select(query)
else:
query = 'SELECT date(session_history.started, "unixepoch", "localtime") AS date_played, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
'WHERE datetime(session_history.stopped, "unixepoch", "localtime") >= ' \
'datetime("now", "-%s days", "localtime") AND ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR session_history.media_type = "track") ' \
'(session_history.media_type = "episode" OR session_history.media_type = "movie" OR ' \
'session_history.media_type = "track") ' \
'GROUP BY date_played ' \
'ORDER BY started ASC' % time_range
@@ -583,12 +582,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT session_history_media_info.video_resolution AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@@ -602,16 +601,13 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT session_history_media_info.video_resolution AS resolution,' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
@@ -671,12 +667,12 @@ class Graphs(object):
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" '\
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" '\
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@@ -700,16 +696,13 @@ class Graphs(object):
'WHEN session_history_media_info.transcode_height <= 1440 THEN "QHD" ' \
'WHEN session_history_media_info.transcode_height <= 2160 THEN "4K" ' \
'ELSE "unknown" END) ELSE session_history_media_info.video_resolution END) AS resolution, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN stopped > 0 THEN (stopped - started) ' \
@@ -759,12 +752,12 @@ class Graphs(object):
try:
if y_axis == 'plays':
query = 'SELECT session_history.platform AS platform, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN session_history_media_info ON session_history.id = session_history_media_info.id ' \
@@ -777,16 +770,13 @@ class Graphs(object):
result = monitor_db.select(query)
else:
query = 'SELECT session_history.platform AS platform, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'AND session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history.stopped > 0 ' \
@@ -838,12 +828,12 @@ class Graphs(object):
if y_axis == 'plays':
query = 'SELECT ' \
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'OR session_history_media_info.audio_decision = "transcode") THEN 1 ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'THEN 1 ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'THEN 1 ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'THEN 1 ELSE 0 END) AS tc_count, ' \
'COUNT(session_history.id) AS total_count ' \
'FROM session_history ' \
'JOIN users ON session_history.user_id = users.user_id ' \
@@ -858,16 +848,13 @@ class Graphs(object):
else:
query = 'SELECT ' \
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE users.friendly_name END) AS username, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "direct play" ' \
'OR session_history_media_info.audio_decision = "direct play") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "direct play" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS dp_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision != "transcode" ' \
'AND session_history_media_info.audio_decision = "copy") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "copy" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS ds_count, ' \
'SUM(CASE WHEN (session_history_media_info.video_decision = "transcode" ' \
'AND session_history_media_info.audio_decision = "transcode") ' \
'SUM(CASE WHEN session_history_media_info.transcode_decision = "transcode" ' \
'AND session_history.stopped > 0 THEN (session_history.stopped - session_history.started) ' \
' - (CASE WHEN paused_counter IS NULL THEN 0 ELSE paused_counter END) ELSE 0 END) AS tc_count, ' \
'SUM(CASE WHEN session_history.stopped > 0 ' \

View File

@@ -543,19 +543,23 @@ def uploadToImgur(imgPath, imgTitle=''):
data = {'type': 'base64',
'image': base64.b64encode(img)}
if imgTitle:
data['title'] = imgTitle
data['name'] = imgTitle + '.jpg'
data['title'] = imgTitle.encode('utf-8')
data['name'] = imgTitle.encode('utf-8') + '.jpg'
try:
request = urllib2.Request('https://api.imgur.com/3/image', headers=headers, data=urllib.urlencode(data))
response = urllib2.urlopen(request)
response = json.loads(response.read())
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', '')
elif response.get('status') >= 400 and response.get('status') < 500:
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur: %s" % response.reason)
else:
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur.")
except (urllib2.HTTPError, urllib2.URLError) as e:
logger.warn(u"PlexPy Helpers :: Unable to upload image to Imgur: %s" % e)
return img_url

View File

@@ -148,7 +148,6 @@ class Libraries(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'library_sections.do_notify',
'library_sections.do_notify_created',
'library_sections.keep_history'
@@ -540,7 +539,7 @@ class Libraries(object):
def get_details(self, section_id=None):
from plexpy import pmsconnect
default_return = {'section_id': None,
default_return = {'section_id': 0,
'section_name': 'Local',
'section_type': '',
'library_thumb': common.DEFAULT_COVER_THUMB,
@@ -550,7 +549,7 @@ class Libraries(object):
'child_count': 0,
'do_notify': 0,
'do_notify_created': 0,
'keep_history': 0
'keep_history': 1
}
if not section_id:
@@ -603,7 +602,8 @@ class Libraries(object):
return library_details
else:
logger.warn(u"PlexPy Libraries :: Unable to retrieve library from local database. Requesting library list refresh.")
logger.warn(u"PlexPy Libraries :: Unable to retrieve library %s from database. Requesting library list refresh."
% section_id)
# Let's first refresh the libraries list to make sure the library isn't newly added and not in the db yet
pmsconnect.refresh_libraries()
@@ -613,9 +613,9 @@ class Libraries(object):
return library_details
else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Returning 'Local' library.")
logger.warn(u"PlexPy Users :: Unable to retrieve library %s from database. Returning 'Local' library."
% section_id)
# If there is no library data we must return something
# Use "Local" library to retain compatibility with PlexWatch database value
return default_return
def get_watch_time_stats(self, section_id=None):

View File

@@ -27,12 +27,15 @@ import logging
import errno
import sys
import os
import re
# These settings are for file logging only
FILENAME = "plexpy.log"
MAX_SIZE = 1000000 # 1 MB
MAX_FILES = 5
_BLACKLIST_WORDS = []
# PlexPy logger
logger = logging.getLogger("plexpy")
@@ -62,6 +65,62 @@ class NoThreadFilter(logging.Filter):
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
def listener():
"""
@@ -160,7 +219,7 @@ def initLogger(console=False, log_dir=False, verbose=False):
if log_dir:
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.setLevel(logging.DEBUG)
file_handler.setFormatter(file_formatter)
@@ -169,13 +228,21 @@ def initLogger(console=False, log_dir=False, verbose=False):
# Setup console logger
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.setFormatter(console_formatter)
console_handler.setLevel(logging.DEBUG)
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
initHooks()

View File

@@ -20,7 +20,7 @@ import re
import time
import urllib
from plexpy import logger, config, notifiers, database, helpers, plextv, pmsconnect
from plexpy import logger, config, notifiers, database, helpers, plextv, pmsconnect, datafactory
import plexpy
@@ -50,168 +50,234 @@ def notify(stream_data=None, notify_action=None):
for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_stop'] and notify_action == 'stop' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < plexpy.CONFIG.NOTIFY_WATCHED_PERCENT):
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
# Set the notification state in the db
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_pause'] and notify_action == 'pause' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
# Set the notification state in the db
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_resume'] and notify_action == 'resume' \
and (plexpy.CONFIG.NOTIFY_CONSECUTIVE or progress_percent < 99):
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
# Set the notification state in the db
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_buffer'] and notify_action == 'buffer':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
script_args=notify_strings[2],
notify_action=notify_action,
script_args=notify_strings[2])
metadata=metadata)
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
# Set the notification state in the db
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_watched'] and notify_action == 'watched':
# Get the current states for notifications from our db
notify_states = get_notify_state(session=stream_data)
# If there is nothing in the notify_log for our agent id but it is enabled we should notify
if not any(d['agent_id'] == agent['id'] for d in notify_states):
if not any(d['agent_id'] == agent['id'] and d['notify_action'] == notify_action for d in notify_states):
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
else:
# Check in our notify log if the notification has already been sent
for notify_state in notify_states:
if not notify_state['on_watched'] and (notify_state['agent_id'] == agent['id']):
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
set_notify_state(session=stream_data,
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif (stream_data['media_type'] == 'track' and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE):
for agent in notifiers.available_notification_agents():
if agent['on_play'] and notify_action == 'play':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_stop'] and notify_action == 'stop':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_pause'] and notify_action == 'pause':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_resume'] and notify_action == 'resume':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif agent['on_buffer'] and notify_action == 'buffer':
# Build and send notification
notify_strings = build_notify_text(session=stream_data, state=notify_action)
notify_strings, metadata = build_notify_text(session=stream_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=stream_data, state=notify_action, agent_info=agent)
set_notify_state(session=stream_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif stream_data['media_type'] == 'clip':
pass
@@ -227,137 +293,168 @@ def notify_timeline(timeline_data=None, notify_action=None):
for agent in notifiers.available_notification_agents():
if agent['on_created'] and notify_action == 'created':
# Build and send notification
notify_strings = build_notify_text(timeline=timeline_data, state=notify_action)
notify_strings, metadata = build_notify_text(timeline=timeline_data,
notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2],
metadata=notify_strings[3])
notify_action=notify_action,
metadata=metadata)
# Set the notification state in the db
set_notify_state(session=timeline_data, state=notify_action, agent_info=agent)
set_notify_state(session=timeline_data,
notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings,
metadata=metadata)
elif not timeline_data and notify_action:
for agent in notifiers.available_notification_agents():
if agent['on_extdown'] and notify_action == 'extdown':
# Build and send notification
notify_strings = build_server_notify_text(state=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2])
script_args=notify_strings[2],
notify_action=notify_action)
# Set the notification state in the db
set_notify_state(notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings)
if agent['on_intdown'] and notify_action == 'intdown':
# Build and send notification
notify_strings = build_server_notify_text(state=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2])
script_args=notify_strings[2],
notify_action=notify_action)
# Set the notification state in the db
set_notify_state(notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings)
if agent['on_extup'] and notify_action == 'extup':
# Build and send notification
notify_strings = build_server_notify_text(state=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2])
script_args=notify_strings[2],
notify_action=notify_action)
# Set the notification state in the db
set_notify_state(notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings)
if agent['on_intup'] and notify_action == 'intup':
# Build and send notification
notify_strings = build_server_notify_text(state=notify_action)
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
notify_action=notify_action,
script_args=notify_strings[2])
script_args=notify_strings[2],
notify_action=notify_action)
# Set the notification state in the db
set_notify_state(notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings)
if agent['on_pmsupdate'] and notify_action == 'pmsupdate':
# Build and send notification
notify_strings = build_server_notify_text(notify_action=notify_action,
agent_id=agent['id'])
notifiers.send_notification(agent_id=agent['id'],
subject=notify_strings[0],
body=notify_strings[1],
script_args=notify_strings[2],
notify_action=notify_action)
# Set the notification state in the db
set_notify_state(notify_action=notify_action,
agent_info=agent,
notify_strings=notify_strings)
else:
logger.debug(u"PlexPy NotificationHandler :: Notify timeline called but incomplete data received.")
def get_notify_state(session):
monitor_db = database.MonitorDatabase()
result = monitor_db.select('SELECT on_play, on_stop, on_pause, on_resume, on_buffer, on_watched, agent_id '
result = monitor_db.select('SELECT timestamp, notify_action, agent_id '
'FROM notify_log '
'WHERE session_key = ? '
'AND rating_key = ? '
'AND user = ? '
'AND user_id = ? '
'ORDER BY id DESC',
args=[session['session_key'], session['rating_key'], session['user']])
args=[session['session_key'], session['rating_key'], session['user_id']])
notify_states = []
for item in result:
notify_state = {'on_play': item['on_play'],
'on_stop': item['on_stop'],
'on_pause': item['on_pause'],
'on_resume': item['on_resume'],
'on_buffer': item['on_buffer'],
'on_watched': item['on_watched'],
notify_state = {'timestamp': item['timestamp'],
'notify_action': item['notify_action'],
'agent_id': item['agent_id']}
notify_states.append(notify_state)
return notify_states
def get_notify_state_timeline(timeline):
monitor_db = database.MonitorDatabase()
result = monitor_db.select('SELECT on_created, agent_id '
'FROM notify_log '
'WHERE rating_key = ? '
'ORDER BY id DESC',
args=[timeline['rating_key']])
notify_states = []
for item in result:
notify_state = {'on_created': item['on_created'],
'agent_id': item['agent_id']}
notify_states.append(notify_state)
def set_notify_state(notify_action, agent_info, notify_strings, session=None, metadata=None):
return notify_states
def set_notify_state(session, state, agent_info):
if session and state and agent_info:
if notify_action and agent_info:
monitor_db = database.MonitorDatabase()
if state == 'play':
values = {'on_play': int(time.time())}
elif state == 'stop':
values = {'on_stop': int(time.time())}
elif state == 'pause':
values = {'on_pause': int(time.time())}
elif state == 'resume':
values = {'on_resume': int(time.time())}
elif state == 'buffer':
values = {'on_buffer': int(time.time())}
elif state == 'watched':
values = {'on_watched': int(time.time())}
elif state == 'created':
values = {'on_created': int(time.time())}
else:
return
session = session or {}
metadata = metadata or {}
if state == 'created':
keys = {'rating_key': session['rating_key'],
'agent_id': agent_info['id'],
'agent_name': agent_info['name']}
if notify_strings[2]:
script_args = '[' + ', '.join(notify_strings[2]) + ']'
else:
keys = {'session_key': session['session_key'],
'rating_key': session['rating_key'],
'user_id': session['user_id'],
'user': session['user'],
script_args = None
keys = {'timestamp': int(time.time()),
'session_key': session.get('session_key', None),
'rating_key': session.get('rating_key', None),
'user_id': session.get('user_id', None),
'agent_id': agent_info['id'],
'agent_name': agent_info['name']}
'notify_action': notify_action}
values = {'parent_rating_key': session.get('parent_rating_key', None),
'grandparent_rating_key': session.get('grandparent_rating_key', None),
'user': session.get('user', None),
'agent_name': agent_info['name'],
'subject_text': notify_strings[0],
'body_text': notify_strings[1],
'script_args': script_args,
'poster_url': metadata.get('poster_url', None)}
monitor_db.upsert(table_name='notify_log', key_dict=keys, value_dict=values)
else:
logger.error(u"PlexPy NotificationHandler :: Unable to set notify state.")
def build_notify_text(session=None, timeline=None, state=None):
def build_notify_text(session=None, timeline=None, notify_action=None, agent_id=None):
# Get time formats
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','').replace('a','').replace('A','')
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
duration_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('a','').replace('A','')
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
@@ -366,6 +463,10 @@ def build_notify_text(session=None, timeline=None, state=None):
plex_tv = plextv.PlexTV()
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:
updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
@@ -388,18 +489,18 @@ def build_notify_text(session=None, timeline=None, state=None):
metadata = metadata_list['metadata']
else:
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
if metadata['media_type'] == 'movie':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
pattern = re.compile(r'<tv>[^>]+.<\/tv>|<music>[^>]+.<\/music>', re.IGNORECASE | re.DOTALL)
elif metadata['media_type'] == 'show' or metadata['media_type'] == 'episode':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
pattern = re.compile(r'<movie>[^>]+.<\/movie>|<music>[^>]+.<\/music>', re.IGNORECASE | re.DOTALL)
elif metadata['media_type'] == 'artist' or metadata['media_type'] == 'track':
# Regex pattern to remove the text in the tags we don't want
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*', re.IGNORECASE | re.DOTALL)
pattern = re.compile(r'<tv>[^>]+.<\/tv>|<movie>[^>]+.<\/movie>', re.IGNORECASE | re.DOTALL)
else:
pattern = None
@@ -408,37 +509,37 @@ def build_notify_text(session=None, timeline=None, state=None):
or metadata['media_type'] == 'artist' or metadata['media_type'] == 'track' \
and pattern:
# Remove the unwanted tags and strip any unmatch tags too.
on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT))
on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT))
on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT))
on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT))
on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT))
on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT))
on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT))
on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT))
on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT))
on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT))
on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT))
on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT))
on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT))
on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT))
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT))
on_start_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT), agent_id)
on_start_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT), agent_id)
on_stop_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT), agent_id)
on_stop_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT), agent_id)
on_pause_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT), agent_id)
on_pause_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT), agent_id)
on_resume_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT), agent_id)
on_resume_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT), agent_id)
on_buffer_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT), agent_id)
on_buffer_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT), agent_id)
on_watched_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT), agent_id)
on_watched_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT), agent_id)
on_created_subject = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT), agent_id)
on_created_body = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT), agent_id)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
else:
on_start_subject = plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT
on_start_body = plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT
on_stop_subject = plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT
on_stop_body = plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT
on_pause_subject = plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT
on_pause_body = plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT
on_resume_subject = plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT
on_resume_body = plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT
on_buffer_subject = plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT
on_buffer_body = plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT
on_watched_subject = plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT
on_watched_body = plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT
on_created_subject = plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT
on_created_body = plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
on_start_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_SUBJECT_TEXT, agent_id)
on_start_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_START_BODY_TEXT, agent_id)
on_stop_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_SUBJECT_TEXT, agent_id)
on_stop_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_STOP_BODY_TEXT, agent_id)
on_pause_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_SUBJECT_TEXT, agent_id)
on_pause_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PAUSE_BODY_TEXT, agent_id)
on_resume_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_SUBJECT_TEXT, agent_id)
on_resume_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_RESUME_BODY_TEXT, agent_id)
on_buffer_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_SUBJECT_TEXT, agent_id)
on_buffer_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_BUFFER_BODY_TEXT, agent_id)
on_watched_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_SUBJECT_TEXT, agent_id)
on_watched_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_WATCHED_BODY_TEXT, agent_id)
on_created_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_SUBJECT_TEXT, agent_id)
on_created_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_CREATED_BODY_TEXT, agent_id)
script_args_text = strip_tag(plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT, agent_id)
# Create a title
if metadata['media_type'] == 'episode' or metadata['media_type'] == 'track':
@@ -459,7 +560,7 @@ def build_notify_text(session=None, timeline=None, state=None):
else:
transcode_decision = 'Direct Play'
if state != 'play':
if notify_action != 'play':
stream_duration = int((time.time() -
helpers.cast_to_int(session.get('started', 0)) -
helpers.cast_to_int(session.get('paused_counter', 0))) / 60)
@@ -504,22 +605,39 @@ def build_notify_text(session=None, timeline=None, state=None):
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show' or metadata['media_type'] == 'artist':
thumb = metadata['thumb']
poster_key = metadata['rating_key']
poster_title = metadata['title']
elif metadata['media_type'] == 'episode':
thumb = metadata['grandparent_thumb']
poster_key = metadata['grandparent_rating_key']
poster_title = metadata['grandparent_title']
elif metadata['media_type'] == 'track':
thumb = metadata['parent_thumb']
poster_key = metadata['parent_rating_key']
poster_title = metadata['parent_title']
else:
thumb = None
if thumb:
# Try to retrieve a poster_url from the database
data_factory = datafactory.DataFactory()
poster_url = data_factory.get_poster_url(rating_key=poster_key)
# If no previous poster_url
if not poster_url and plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS:
try:
# Retrieve the poster from Plex and cache to file
urllib.urlretrieve(plexpy.CONFIG.PMS_URL + thumb + '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN,
os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'))
# Upload thumb to Imgur and get link
metadata['poster_url'] = helpers.uploadToImgur(os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'), full_title)
poster_url = helpers.uploadToImgur(os.path.join(plexpy.CONFIG.CACHE_DIR, 'cache-poster.jpg'), poster_title)
except Exception as e:
logger.error(u"PlexPy Notifier :: Unable to retrieve poster for rating_key %s: %s." % (str(rating_key), e))
metadata['poster_url'] = poster_url
# Fix metadata params for notify recently added grandparent
if state == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
if notify_action == 'created' and plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
show_name = metadata['title']
episode_name = ''
artist_name = metadata['title']
@@ -535,7 +653,8 @@ def build_notify_text(session=None, timeline=None, state=None):
available_params = {# Global paramaters
'server_name': server_name,
'server_uptime': server_uptime,
'action': state.title(),
'server_version': server_identity.get('version',''),
'action': notify_action.title(),
'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format),
# Stream parameters
@@ -628,7 +747,7 @@ def build_notify_text(session=None, timeline=None, state=None):
except Exception as e:
logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e)
if state == 'play':
if notify_action == 'play':
# Default body text
body_text = '%s (%s) started playing %s' % (session['friendly_name'],
session['player'],
@@ -649,10 +768,10 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
elif state == 'stop':
return [subject_text, body_text, script_args], metadata
elif notify_action == 'stop':
# Default body text
body_text = '%s (%s) has stopped %s' % (session['friendly_name'],
session['player'],
@@ -673,10 +792,10 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
elif state == 'pause':
return [subject_text, body_text, script_args], metadata
elif notify_action == 'pause':
# Default body text
body_text = '%s (%s) has paused %s' % (session['friendly_name'],
session['player'],
@@ -697,10 +816,10 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
elif state == 'resume':
return [subject_text, body_text, script_args], metadata
elif notify_action == 'resume':
# Default body text
body_text = '%s (%s) has resumed %s' % (session['friendly_name'],
session['player'],
@@ -721,10 +840,10 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
elif state == 'buffer':
return [subject_text, body_text, script_args], metadata
elif notify_action == 'buffer':
# Default body text
body_text = '%s (%s) is buffering %s' % (session['friendly_name'],
session['player'],
@@ -745,10 +864,10 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
elif state == 'watched':
return [subject_text, body_text, script_args], metadata
elif notify_action == 'watched':
# Default body text
body_text = '%s (%s) has watched %s' % (session['friendly_name'],
session['player'],
@@ -769,10 +888,10 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
elif state == 'created':
return [subject_text, body_text, script_args], metadata
elif notify_action == 'created':
# Default body text
body_text = '%s was recently added to Plex.' % full_title
@@ -791,17 +910,17 @@ def build_notify_text(session=None, timeline=None, state=None):
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return [subject_text, body_text, script_args, metadata]
return [subject_text, body_text, script_args], metadata
else:
return None
return [None, None, None], None
def build_server_notify_text(state=None):
def build_server_notify_text(notify_action=None, agent_id=None):
# Get time formats
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz','')
date_format = plexpy.CONFIG.DATE_FORMAT.replace('Do','')
time_format = plexpy.CONFIG.TIME_FORMAT.replace('Do','')
# Get the server name
server_name = plexpy.CONFIG.PMS_NAME
@@ -810,6 +929,14 @@ def build_server_notify_text(state=None):
plex_tv = plextv.PlexTV()
server_times = plex_tv.get_server_times()
# Get the server version
pms_connect = pmsconnect.PmsConnect()
server_identity = pms_connect.get_server_identity()
update_status = {}
if notify_action == 'pmsupdate':
update_status = pms_connect.get_update_staus()
if server_times:
updated_at = server_times[0]['updated_at']
server_uptime = helpers.human_duration(int(time.time() - helpers.cast_to_int(updated_at)))
@@ -817,22 +944,31 @@ def build_server_notify_text(state=None):
logger.error(u"PlexPy NotificationHandler :: Unable to retrieve server uptime.")
server_uptime = 'N/A'
on_extdown_subject = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT
on_extdown_body = plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT
on_intdown_subject = plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT
on_intdown_body = plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT
on_extup_subject = plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT
on_extup_body = plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT
on_intup_subject = plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT
on_intup_body = plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT
script_args_text = plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT
pattern = re.compile('\n*<tv>[^>]+.</tv>\n*|\n*<movie>[^>]+.</movie>\n*|\n*?<music>[^>]+.</music>\n*', re.IGNORECASE | re.DOTALL)
on_extdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_SUBJECT_TEXT, agent_id)
on_extdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTDOWN_BODY_TEXT, agent_id)
on_intdown_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_SUBJECT_TEXT, agent_id)
on_intdown_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTDOWN_BODY_TEXT, agent_id)
on_extup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_SUBJECT_TEXT, agent_id)
on_extup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT, agent_id)
on_intup_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT, agent_id)
on_intup_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT, agent_id)
on_pmsupdate_subject = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT, agent_id)
on_pmsupdate_body = strip_tag(plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT, agent_id)
script_args_text = strip_tag(re.sub(pattern, '', plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT), agent_id)
available_params = {# Global paramaters
'server_name': server_name,
'server_uptime': server_uptime,
'action': state.title(),
'server_version': server_identity.get('version',''),
'action': notify_action.title(),
'datestamp': arrow.now().format(date_format),
'timestamp': arrow.now().format(time_format)}
'timestamp': arrow.now().format(time_format),
# Update parameters
'update_version': update_status.get('version',''),
'update_url': update_status.get('download_url',''),
'update_changelog': update_status.get('changelog','')}
# Default text
subject_text = 'PlexPy (%s)' % server_name
@@ -848,7 +984,7 @@ def build_server_notify_text(state=None):
except Exception as e:
logger.error(u"PlexPy Notifier :: Unable to parse custom script arguments %s. Using fallback." % e)
if state == 'extdown':
if notify_action == 'extdown':
# Default body text
body_text = 'The Plex Media Server remote access is down.'
@@ -871,7 +1007,7 @@ def build_server_notify_text(state=None):
else:
return [subject_text, body_text, script_args]
elif state == 'intdown':
elif notify_action == 'intdown':
# Default body text
body_text = 'The Plex Media Server is down.'
@@ -893,7 +1029,7 @@ def build_server_notify_text(state=None):
return [subject_text, body_text, script_args]
else:
return [subject_text, body_text, script_args]
if state == 'extup':
if notify_action == 'extup':
# Default body text
body_text = 'The Plex Media Server remote access is back up.'
@@ -915,7 +1051,7 @@ def build_server_notify_text(state=None):
return [subject_text, body_text, script_args]
else:
return [subject_text, body_text, script_args]
elif state == 'intup':
elif notify_action == 'intup':
# Default body text
body_text = 'The Plex Media Server is back up.'
@@ -938,10 +1074,39 @@ def build_server_notify_text(state=None):
else:
return [subject_text, body_text, script_args]
elif notify_action == 'pmsupdate':
# Default body text
body_text = 'An update is available for the Plex Media Server (version {update_version}).'
if on_pmsupdate_subject and on_pmsupdate_body:
try:
subject_text = unicode(on_pmsupdate_subject).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification subject. Using fallback." % e)
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification subject. Using fallback.")
try:
body_text = unicode(on_pmsupdate_body).format(**available_params)
except LookupError, e:
logger.error(u"PlexPy NotificationHandler :: Unable to parse field %s in notification body. Using fallback." % e)
except:
logger.error(u"PlexPy NotificationHandler :: Unable to parse custom notification body. Using fallback.")
return [subject_text, body_text, script_args]
else:
return [subject_text, body_text, script_args]
else:
return None
def strip_tag(data):
p = re.compile(r'<.*?>')
def strip_tag(data, agent_id=None):
# Allow tags b, i, u, a[href], font[color] for Pushover
if agent_id == 7:
p = re.compile(r'<(?!/?(b>|i>|u>)|(a\shref=\"[^\"\'\s]+\"|/a>|font\scolor=\"[^\"\'\s]+\"|/font>)).*?>',
re.IGNORECASE | re.DOTALL)
else:
p = re.compile(r'<.*?>', re.IGNORECASE | re.DOTALL)
return p.sub('', data)

View File

@@ -75,7 +75,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.GROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.GROWL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.GROWL_ON_EXTUP,
'on_intup': plexpy.CONFIG.GROWL_ON_INTUP
'on_intup': plexpy.CONFIG.GROWL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.GROWL_ON_PMSUPDATE
},
{'name': 'Prowl',
'id': AGENT_IDS['Prowl'],
@@ -92,7 +93,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PROWL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PROWL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PROWL_ON_EXTUP,
'on_intup': plexpy.CONFIG.PROWL_ON_INTUP
'on_intup': plexpy.CONFIG.PROWL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PROWL_ON_PMSUPDATE
},
{'name': 'XBMC',
'id': AGENT_IDS['XBMC'],
@@ -109,25 +111,27 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.XBMC_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.XBMC_ON_INTDOWN,
'on_extup': plexpy.CONFIG.XBMC_ON_EXTUP,
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP
},
{'name': 'Plex',
'id': AGENT_IDS['Plex'],
'config_prefix': 'plex',
'has_config': True,
'state': checked(plexpy.CONFIG.PLEX_ENABLED),
'on_play': plexpy.CONFIG.PLEX_ON_PLAY,
'on_stop': plexpy.CONFIG.PLEX_ON_STOP,
'on_pause': plexpy.CONFIG.PLEX_ON_PAUSE,
'on_resume': plexpy.CONFIG.PLEX_ON_RESUME,
'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER,
'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
'on_intup': plexpy.CONFIG.PLEX_ON_INTUP
'on_intup': plexpy.CONFIG.XBMC_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.XBMC_ON_PMSUPDATE
},
#{'name': 'Plex',
# 'id': AGENT_IDS['Plex'],
# 'config_prefix': 'plex',
# 'has_config': True,
# 'state': checked(plexpy.CONFIG.PLEX_ENABLED),
# 'on_play': plexpy.CONFIG.PLEX_ON_PLAY,
# 'on_stop': plexpy.CONFIG.PLEX_ON_STOP,
# 'on_pause': plexpy.CONFIG.PLEX_ON_PAUSE,
# 'on_resume': plexpy.CONFIG.PLEX_ON_RESUME,
# 'on_buffer': plexpy.CONFIG.PLEX_ON_BUFFER,
# 'on_watched': plexpy.CONFIG.PLEX_ON_WATCHED,
# 'on_created': plexpy.CONFIG.PLEX_ON_CREATED,
# 'on_extdown': plexpy.CONFIG.PLEX_ON_EXTDOWN,
# 'on_intdown': plexpy.CONFIG.PLEX_ON_INTDOWN,
# 'on_extup': plexpy.CONFIG.PLEX_ON_EXTUP,
# 'on_intup': plexpy.CONFIG.PLEX_ON_INTUP,
# 'on_pmsupdate': plexpy.CONFIG.PLEX_ON_PMSUPDATE
# },
{'name': 'NotifyMyAndroid',
'id': AGENT_IDS['NMA'],
'config_prefix': 'nma',
@@ -143,7 +147,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.NMA_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.NMA_ON_INTDOWN,
'on_extup': plexpy.CONFIG.NMA_ON_EXTUP,
'on_intup': plexpy.CONFIG.NMA_ON_INTUP
'on_intup': plexpy.CONFIG.NMA_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.NMA_ON_PMSUPDATE
},
{'name': 'Pushalot',
'id': AGENT_IDS['Pushalot'],
@@ -160,7 +165,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHALOT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHALOT_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHALOT_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP
'on_intup': plexpy.CONFIG.PUSHALOT_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHALOT_ON_PMSUPDATE
},
{'name': 'Pushbullet',
'id': AGENT_IDS['Pushbullet'],
@@ -177,7 +183,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHBULLET_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHBULLET_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHBULLET_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP
'on_intup': plexpy.CONFIG.PUSHBULLET_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHBULLET_ON_PMSUPDATE
},
{'name': 'Pushover',
'id': AGENT_IDS['Pushover'],
@@ -194,7 +201,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.PUSHOVER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.PUSHOVER_ON_INTDOWN,
'on_extup': plexpy.CONFIG.PUSHOVER_ON_EXTUP,
'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP
'on_intup': plexpy.CONFIG.PUSHOVER_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.PUSHOVER_ON_PMSUPDATE
},
{'name': 'Boxcar2',
'id': AGENT_IDS['Boxcar2'],
@@ -211,7 +219,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.BOXCAR_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.BOXCAR_ON_INTDOWN,
'on_extup': plexpy.CONFIG.BOXCAR_ON_EXTUP,
'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP
'on_intup': plexpy.CONFIG.BOXCAR_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.BOXCAR_ON_PMSUPDATE
},
{'name': 'E-mail',
'id': AGENT_IDS['Email'],
@@ -228,7 +237,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.EMAIL_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.EMAIL_ON_INTDOWN,
'on_extup': plexpy.CONFIG.EMAIL_ON_EXTUP,
'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP
'on_intup': plexpy.CONFIG.EMAIL_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.EMAIL_ON_PMSUPDATE
},
{'name': 'Twitter',
'id': AGENT_IDS['Twitter'],
@@ -245,7 +255,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.TWITTER_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TWITTER_ON_INTDOWN,
'on_extup': plexpy.CONFIG.TWITTER_ON_EXTUP,
'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP
'on_intup': plexpy.CONFIG.TWITTER_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.TWITTER_ON_PMSUPDATE
},
{'name': 'IFTTT',
'id': AGENT_IDS['IFTTT'],
@@ -262,7 +273,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.IFTTT_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.IFTTT_ON_INTDOWN,
'on_extup': plexpy.CONFIG.IFTTT_ON_EXTUP,
'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP
'on_intup': plexpy.CONFIG.IFTTT_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.IFTTT_ON_PMSUPDATE
},
{'name': 'Telegram',
'id': AGENT_IDS['Telegram'],
@@ -279,7 +291,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.TELEGRAM_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.TELEGRAM_ON_INTDOWN,
'on_extup': plexpy.CONFIG.TELEGRAM_ON_EXTUP,
'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP
'on_intup': plexpy.CONFIG.TELEGRAM_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.TELEGRAM_ON_PMSUPDATE
},
{'name': 'Slack',
'id': AGENT_IDS['Slack'],
@@ -296,7 +309,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.SLACK_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.SLACK_ON_INTDOWN,
'on_extup': plexpy.CONFIG.SLACK_ON_EXTUP,
'on_intup': plexpy.CONFIG.SLACK_ON_INTUP
'on_intup': plexpy.CONFIG.SLACK_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.SLACK_ON_PMSUPDATE
},
{'name': 'Scripts',
'id': AGENT_IDS['Scripts'],
@@ -313,7 +327,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN,
'on_extup': plexpy.CONFIG.SCRIPTS_ON_EXTUP,
'on_intdown': plexpy.CONFIG.SCRIPTS_ON_INTDOWN,
'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP
'on_intup': plexpy.CONFIG.SCRIPTS_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE
},
{'name': 'Facebook',
'id': AGENT_IDS['Facebook'],
@@ -330,7 +345,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.FACEBOOK_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.FACEBOOK_ON_INTDOWN,
'on_extup': plexpy.CONFIG.FACEBOOK_ON_EXTUP,
'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP
'on_intup': plexpy.CONFIG.FACEBOOK_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.FACEBOOK_ON_PMSUPDATE
}
]
@@ -352,7 +368,8 @@ def available_notification_agents():
'on_extdown': plexpy.CONFIG.OSX_NOTIFY_ON_EXTDOWN,
'on_intdown': plexpy.CONFIG.OSX_NOTIFY_ON_INTDOWN,
'on_extup': plexpy.CONFIG.OSX_NOTIFY_ON_EXTUP,
'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP
'on_intup': plexpy.CONFIG.OSX_NOTIFY_ON_INTUP,
'on_pmsupdate': plexpy.CONFIG.OSX_NOTIFY_ON_PMSUPDATE
})
return agents
@@ -419,7 +436,7 @@ def get_notification_agent_config(agent_id):
return []
def send_notification(agent_id, subject, body, **kwargs):
def send_notification(agent_id, subject, body, notify_action, **kwargs):
if str(agent_id).isdigit():
agent_id = int(agent_id)
@@ -461,7 +478,7 @@ def send_notification(agent_id, subject, body, **kwargs):
tweet.notify(subject=subject, message=body)
elif agent_id == 12:
iftttClient = IFTTT()
iftttClient.notify(subject=subject, message=body)
iftttClient.notify(subject=subject, message=body, action=notify_action)
elif agent_id == 13:
telegramClient = TELEGRAM()
telegramClient.notify(message=body, event=subject)
@@ -1054,6 +1071,7 @@ class PUSHOVER(object):
self.keys = plexpy.CONFIG.PUSHOVER_KEYS
self.priority = plexpy.CONFIG.PUSHOVER_PRIORITY
self.sound = plexpy.CONFIG.PUSHOVER_SOUND
self.html_support = plexpy.CONFIG.PUSHOVER_HTML_SUPPORT
def conf(self, options):
return cherrypy.config['config'].get('Pushover', options)
@@ -1069,6 +1087,7 @@ class PUSHOVER(object):
'title': event.encode("utf-8"),
'message': message.encode("utf-8"),
'sound': plexpy.CONFIG.PUSHOVER_SOUND,
'html': plexpy.CONFIG.PUSHOVER_HTML_SUPPORT,
'priority': plexpy.CONFIG.PUSHOVER_PRIORITY}
http_handler.request("POST",
@@ -1095,11 +1114,12 @@ class PUSHOVER(object):
# For uniformity reasons not removed
return
def test(self, keys, priority, sound):
def test(self, keys, priority, sound, html_support):
self.enabled = True
self.keys = keys
self.priority = priority
self.sound = sound
self.html_support = html_support
self.notify('Main Screen Activate', 'Test Message')
@@ -1151,6 +1171,12 @@ class PUSHOVER(object):
'description': 'Set the notification sound. Leave blank for the default sound.',
'input_type': 'select',
'select_options': self.get_sounds()
},
{'label': 'Enable HTML Support',
'value': self.html_support,
'name': 'pushover_html_support',
'description': 'Style your messages using these HTML Tags: b, i, u, a[href], font[color]',
'input_type': 'checkbox'
}
]
@@ -1257,7 +1283,7 @@ class TwitterNotifier(object):
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': 'Step 1: Visit <a href="https://apps.twitter.com/" target="_blank"> \
'description': 'Step 1: Visit <a href="' + helpers.anon_url('https://apps.twitter.com') + '" target="_blank"> \
Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>\
Step 2: Go to <strong>Keys and Access Tokens</strong> and click \
<strong>Create my access token</strong>.<br>\
@@ -1292,7 +1318,7 @@ class TwitterNotifier(object):
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'twitter_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
@@ -1578,10 +1604,11 @@ class IFTTT(object):
self.apikey = plexpy.CONFIG.IFTTT_KEY
self.event = plexpy.CONFIG.IFTTT_EVENT
def notify(self, message, subject):
def notify(self, message, subject, action):
if not message or not subject:
return
event = unicode(self.event).format(action=action)
http_handler = HTTPSConnection("maker.ifttt.com")
data = {'value1': subject.encode("utf-8"),
@@ -1590,7 +1617,7 @@ class IFTTT(object):
# logger.debug(u"Ifttt SENDING: %s" % json.dumps(data))
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"},
body=json.dumps(data))
response = http_handler.getresponse()
@@ -1616,13 +1643,16 @@ class IFTTT(object):
config_option = [{'label': 'Ifttt Maker Channel Key',
'value': self.apikey,
'name': 'ifttt_key',
'description': 'Your Ifttt key. You can get a key from <a href="https://ifttt.com/maker" target="_blank">here</a>.',
'description': 'Your Ifttt key. You can get a key from'
' <a href="' + helpers.anon_url('https://ifttt.com/maker') + '" target="_blank">here</a>.',
'input_type': 'text'
},
{'label': 'Ifttt Event',
'value': self.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.',
'input_type': 'text'
}
@@ -1690,19 +1720,23 @@ class TELEGRAM(object):
config_option = [{'label': 'Telegram Bot Token',
'value': self.bot_token,
'name': 'telegram_bot_token',
'description': 'Your Telegram bot token. Contact <a href="http://telegram.me/BotFather" target="_blank">@BotFather</a> on Telegram to get one.',
'description': 'Your Telegram bot token. '
'Contact <a href="' + helpers.anon_url('https://telegram.me/BotFather') + '" target="_blank">@BotFather</a>'
' on Telegram to get one.',
'input_type': 'text'
},
{'label': 'Telegram Chat ID, Group ID, or Channel Username',
'value': self.chat_id,
'name': 'telegram_chat_id',
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. Contact <a href="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.',
'description': 'Your Telegram Chat ID, Group ID, or @channelusername. '
'Contact <a href="' + helpers.anon_url('https://telegram.me/myidbot') + '" target="_blank">@myidbot</a>'
' on Telegram to get an ID.',
'input_type': 'text'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'telegram_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
@@ -1800,7 +1834,7 @@ class SLACK(object):
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'slack_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]
@@ -1871,14 +1905,14 @@ class Scripts(object):
elif notify_action == 'resume':
script = plexpy.CONFIG.SCRIPTS_ON_RESUME_SCRIPT
elif notify_action == 'watched':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
elif notify_action == 'buffer':
script = plexpy.CONFIG.SCRIPTS_ON_BUFFER_SCRIPT
elif notify_action == 'extdown':
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'created':
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
elif notify_action == 'intdown':
script = plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT
@@ -1886,11 +1920,14 @@ class Scripts(object):
elif notify_action == 'intup':
script = plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT
elif notify_action == 'created':
script = plexpy.CONFIG.SCRIPTS_ON_CREATED_SCRIPT
elif notify_action == 'extdown':
script = plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT
elif notify_action == 'watched':
script = plexpy.CONFIG.SCRIPTS_ON_WATCHED_SCRIPT
elif notify_action == 'extup':
script = plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT
elif notify_action == 'pmsupdate':
script = plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT
else:
# For manual scripts
@@ -1972,12 +2009,11 @@ class Scripts(object):
logger.error(u"PlexPy Notifiers :: Failed to run script: %s" % e)
def return_config_options(self):
config_option = [{'label': 'Warning',
'description': '<strong>Script notifications are currently experimental!</strong><br><br>\
Supported file types: ' + ', '.join(self.script_exts),
config_option = [{'label': 'Supported File Types',
'description': ', '.join(self.script_exts),
'input_type': 'help'
},
{'label': 'Script folder',
{'label': 'Script Folder',
'value': plexpy.CONFIG.SCRIPTS_FOLDER,
'name': 'scripts_folder',
'description': 'Add your script folder.',
@@ -2032,13 +2068,6 @@ class Scripts(object):
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Down',
'value': plexpy.CONFIG.SCRIPTS_ON_INTDOWN_SCRIPT,
'name': 'scripts_on_intdown_script',
@@ -2046,6 +2075,20 @@ class Scripts(object):
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT,
'name': 'scripts_on_intup_script',
'description': 'Choose the script for Plex server back up.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Down',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTDOWN_SCRIPT,
'name': 'scripts_on_extdown_script',
'description': 'Choose the script for Plex remote access down.',
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Remote Access Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_EXTUP_SCRIPT,
'name': 'scripts_on_extup_script',
@@ -2053,10 +2096,10 @@ class Scripts(object):
'input_type': 'select',
'select_options': self.list_scripts()
},
{'label': 'Plex Server Back Up',
'value': plexpy.CONFIG.SCRIPTS_ON_INTUP_SCRIPT,
'name': 'scripts_on_intup_script',
'description': 'Choose the script for Plex server back up.',
{'label': 'Plex Update Available',
'value': plexpy.CONFIG.SCRIPTS_ON_PMSUPDATE_SCRIPT,
'name': 'scripts_on_pmsupdate_script',
'description': 'Choose the script for Plex update available.',
'input_type': 'select',
'select_options': self.list_scripts()
}
@@ -2073,6 +2116,7 @@ class FacebookNotifier(object):
self.app_id = plexpy.CONFIG.FACEBOOK_APP_ID
self.app_secret = plexpy.CONFIG.FACEBOOK_APP_SECRET
self.group_id = plexpy.CONFIG.FACEBOOK_GROUP
self.incl_pmslink = plexpy.CONFIG.FACEBOOK_INCL_PMSLINK
self.incl_poster = plexpy.CONFIG.FACEBOOK_INCL_POSTER
self.incl_subject = plexpy.CONFIG.FACEBOOK_INCL_SUBJECT
@@ -2128,35 +2172,79 @@ class FacebookNotifier(object):
if self.incl_poster and 'metadata' in kwargs:
metadata = kwargs['metadata']
poster_url = metadata.get('poster_url','')
poster_link = ''
caption = ''
if poster_url:
if metadata['media_type'] == 'movie' or metadata['media_type'] == 'show':
title = metadata['title']
subtitle = metadata['year']
# Use default posters if no poster_url
if not poster_url:
if metadata['media_type'] in ['artist', 'track']:
poster_url = 'https://raw.githubusercontent.com/drzoidberg33/plexpy/master/data/interfaces/default/images/cover.png'
else:
poster_url = 'https://raw.githubusercontent.com/drzoidberg33/plexpy/master/data/interfaces/default/images/poster.png'
if metadata['media_type'] == 'movie':
title = '%s (%s)' % (metadata['title'], metadata['year'])
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' % (metadata['grandparent_title'], metadata['title'])
subtitle = 'S%s %s E%s' % (metadata['parent_media_index'],
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 = ''
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']
caption = 'View in Plex Web.'
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
@@ -2176,13 +2264,12 @@ class FacebookNotifier(object):
def return_config_options(self):
config_option = [{'label': 'Instructions',
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
Step 1: Visit <a href="https://developers.facebook.com/apps/" target="_blank"> \
'description': '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>\
Step 2: Go to <strong>Settings > Basic</strong> and fill in a \
<strong>Contact Email</strong>.<br>\
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 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>\
@@ -2193,7 +2280,8 @@ class FacebookNotifier(object):
{'label': 'PlexPy URL',
'value': self.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'
},
{'label': 'Facebook App ID',
@@ -2223,13 +2311,20 @@ class FacebookNotifier(object):
{'label': 'Include Poster Image',
'value': self.incl_poster,
'name': 'facebook_incl_poster',
'description': 'Include a poster and link in the notifications.',
'description': 'Include a poster with the notifications.',
'input_type': 'checkbox'
},
{'label': 'Include Link to Plex Web',
'value': self.incl_pmslink,
'name': 'facebook_incl_pmslink',
'description': 'Include a link to the media in Plex Web with the notifications.<br>'
'If disabled, the link will go to IMDB, TVDB, TMDb, or Last.fm instead, if available.',
'input_type': 'checkbox'
},
{'label': 'Include Subject Line',
'value': self.incl_subject,
'name': 'facebook_incl_subject',
'description': 'Include the subject line in the notifications.',
'description': 'Include the subject line with the notifications.',
'input_type': 'checkbox'
}
]

View File

@@ -251,7 +251,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
hours=0, minutes=0, seconds=0)
plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
hours=0, minutes=0, seconds=0)
plexpy.schedule_job(activity_pinger.check_server_response, 'Check for server response',
plexpy.schedule_job(activity_pinger.check_server_response, 'Check for Plex remote access',
hours=0, minutes=0, seconds=0)
ap = activity_processor.ActivityProcessor()
@@ -302,7 +302,7 @@ def import_from_plexwatch(database=None, table_name=None, import_ignore_interval
# Skip line if we don't have a ratingKey to work with
if not row['rating_key']:
logger.error(u"PlexPy Importer :: Skipping record due to null ratingRey.")
logger.error(u"PlexPy Importer :: Skipping record due to null ratingKey.")
continue
# If the user_id no longer exists in the friends list, pull it from the xml.

View File

@@ -389,6 +389,38 @@ class PmsConnect(object):
return request
def put_updater(self, output_format=''):
"""
Refresh updater status.
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/updater/check?download=0'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='PUT',
output_format=output_format)
return request
def get_updater(self, output_format=''):
"""
Return updater status.
Optional parameters: output_format { dict, json }
Output: array
"""
uri = '/updater/status'
request = self.request_handler.make_request(uri=uri,
proto=self.protocol,
request_type='GET',
output_format=output_format)
return request
def get_recently_added_details(self, section_id='', count='0'):
"""
Return processed and validated list of recently added items.
@@ -1008,7 +1040,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -1130,7 +1162,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -1188,7 +1220,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -1246,7 +1278,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -1337,7 +1369,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['user_thumb'],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-1],
'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split('::ffff:')[-1],
'player': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'title'),
'platform': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'platform'),
'machine_id': machine_id,
@@ -1479,7 +1511,7 @@ class PmsConnect(object):
xml_head = identity.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_local_server_identity: %s." % e)
return []
return {}
server_identity = {}
for a in xml_head:
@@ -1995,3 +2027,40 @@ class PmsConnect(object):
}
return server_response
def get_update_staus(self):
# Refresh the Plex updater status first
self.put_updater()
updater_status = self.get_updater(output_format='xml')
try:
xml_head = updater_status.getElementsByTagName('MediaContainer')
except Exception as e:
logger.warn(u"PlexPy Pmsconnect :: Unable to parse XML for get_update_staus: %s." % e)
# Catch the malformed XML on certain PMX version.
# XML parser helper returns empty list if there is an error parsing XML
if updater_status == []:
logger.warn(u"Plex API updater XML is broken on the current PMS version. Please update your PMS manually.")
logger.info(u"PlexPy is unable to check for Plex updates. Disabling check for Plex updates.")
# Disable check for Plex updates
plexpy.CONFIG.MONITOR_PMS_UPDATES = 0
plexpy.initialize_scheduler()
plexpy.CONFIG.write()
return {}
updater_info = {}
for a in xml_head:
if a.getElementsByTagName('Release'):
release = a.getElementsByTagName('Release')
for item in release:
updater_info = {'can_install': helpers.get_xml_attr(a, 'canInstall'),
'download_url': helpers.get_xml_attr(a, 'downloadURL'),
'version': helpers.get_xml_attr(item, 'version'),
'state': helpers.get_xml_attr(item, 'state'),
'changelog': helpers.get_xml_attr(item, 'fixed')
}
return updater_info

View File

@@ -50,8 +50,7 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
'session_history_media_info.transcode_decision',
'users.do_notify as do_notify',
'users.keep_history as keep_history'
]
@@ -117,8 +116,7 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'transcode_decision': item['transcode_decision'],
'do_notify': helpers.checked(item['do_notify']),
'keep_history': helpers.checked(item['keep_history'])
}
@@ -154,8 +152,7 @@ class Users(object):
'session_history_metadata.year',
'session_history_metadata.media_index',
'session_history_metadata.parent_media_index',
'session_history_media_info.video_decision',
'session_history_media_info.audio_decision',
'session_history_media_info.transcode_decision',
'session_history.user',
'session_history.user_id as custom_user_id',
'(CASE WHEN users.friendly_name IS NULL THEN users.username ELSE \
@@ -213,8 +210,7 @@ class Users(object):
'year': item['year'],
'media_index': item['media_index'],
'parent_media_index': item['parent_media_index'],
'video_decision': item['video_decision'],
'audio_decision': item['audio_decision'],
'transcode_decision': item['transcode_decision'],
'friendly_name': item['friendly_name']
}
@@ -245,7 +241,7 @@ class Users(object):
def get_details(self, user_id=None, user=None):
from plexpy import plextv
default_return = {'user_id': None,
default_return = {'user_id': 0,
'username': 'Local',
'friendly_name': 'Local',
'user_thumb': common.DEFAULT_USER_THUMB,
@@ -254,7 +250,7 @@ class Users(object):
'is_allow_sync': 0,
'is_restricted': 0,
'do_notify': 0,
'keep_history': 0
'keep_history': 1
}
if not user_id and not user:
@@ -316,7 +312,8 @@ class Users(object):
return user_details
else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Requesting user list refresh.")
logger.warn(u"PlexPy Users :: Unable to retrieve user %s from database. Requesting user list refresh."
% user_id if user_id else user)
# Let's first refresh the user list to make sure the user isn't newly added and not in the db yet
plextv.refresh_users()
@@ -326,7 +323,8 @@ class Users(object):
return user_details
else:
logger.warn(u"PlexPy Users :: Unable to retrieve user from local database. Returning 'Local' user.")
logger.warn(u"PlexPy Users :: Unable to retrieve user %s from database. Returning 'Local' user."
% user_id if user_id else user)
# If there is no user data we must return something
# Use "Local" user to retain compatibility with PlexWatch database value
return default_return

View File

@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
PLEXPY_RELEASE_VERSION = "1.3.7"
PLEXPY_RELEASE_VERSION = "1.3.13"

View File

@@ -124,6 +124,7 @@ def checkGithub():
# Get the latest version available 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)
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)
if version is None:
@@ -144,6 +145,7 @@ def checkGithub():
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)
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)
if commits is None:

View File

@@ -25,6 +25,7 @@ import websocket
name = 'websocket'
opcode_data = (websocket.ABNF.OPCODE_TEXT, websocket.ABNF.OPCODE_BINARY)
ws_reconnect = False
def start_thread():
@@ -34,34 +35,43 @@ def start_thread():
threading.Thread(target=run).start()
def reconnect():
global ws_reconnect
ws_reconnect = True
def run():
from websocket import create_connection
if plexpy.CONFIG.PMS_SSL and plexpy.CONFIG.PMS_URL[:5] == 'https':
uri = plexpy.CONFIG.PMS_URL.replace('https://', 'wss://') + '/:/websockets/notifications'
secure = ' secure'
else:
uri = 'ws://%s:%s/:/websockets/notifications' % (
plexpy.CONFIG.PMS_IP,
plexpy.CONFIG.PMS_PORT
)
secure = ''
# Set authentication token (if one is available)
if plexpy.CONFIG.PMS_TOKEN:
uri += '?X-Plex-Token=' + plexpy.CONFIG.PMS_TOKEN
global ws_reconnect
ws_reconnect = False
ws_connected = False
reconnects = 0
# Try an open the websocket connection - if it fails after 15 retries fallback to polling
while not ws_connected and reconnects <= 15:
try:
logger.info(u'PlexPy WebSocket :: Opening websocket, connection attempt %s.' % str(reconnects + 1))
logger.info(u"PlexPy WebSocket :: Opening%s websocket, connection attempt %s." % (secure, str(reconnects + 1)))
ws = create_connection(uri)
reconnects = 0
ws_connected = True
logger.info(u'PlexPy WebSocket :: Ready')
logger.info(u"PlexPy WebSocket :: Ready")
except IOError, e:
logger.error(u'PlexPy WebSocket :: %s.' % e)
logger.error(u"PlexPy WebSocket :: %s." % e)
reconnects += 1
time.sleep(5)
@@ -79,22 +89,30 @@ def run():
if reconnects > 1:
time.sleep(5)
logger.warn(u'PlexPy WebSocket :: Connection has closed, reconnecting...')
logger.warn(u"PlexPy WebSocket :: Connection has closed, reconnecting...")
try:
ws = create_connection(uri)
except IOError, e:
logger.info(u'PlexPy WebSocket :: %s.' % e)
logger.info(u"PlexPy WebSocket :: %s." % e)
else:
ws.shutdown()
ws_connected = False
break
if not ws_connected:
logger.error(u'PlexPy WebSocket :: Connection unavailable, falling back to polling.')
# Check if we recieved a restart notification and close websocket connection cleanly
if ws_reconnect:
logger.info(u"PlexPy WebSocket :: Reconnecting websocket...")
ws.shutdown()
ws_connected = False
start_thread()
if not ws_connected and not ws_reconnect:
logger.error(u"PlexPy WebSocket :: Connection unavailable, falling back to polling.")
plexpy.POLLING_FAILOVER = True
plexpy.initialize_scheduler()
logger.debug(u'PlexPy WebSocket :: Leaving thread.')
logger.debug(u"PlexPy WebSocket :: Leaving thread.")
def receive(ws):
@@ -122,7 +140,7 @@ def process(opcode, data):
try:
info = json.loads(data)
except Exception as ex:
logger.warn(u'PlexPy WebSocket :: Error decoding message from websocket: %s' % ex)
logger.warn(u"PlexPy WebSocket :: Error decoding message from websocket: %s" % ex)
logger.debug(data)
return False

View File

@@ -13,7 +13,8 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, libraries, database
from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, \
datafactory, graphs, users, libraries, database, web_socket
from plexpy.helpers import checked, addtoapi, get_ip, create_https_certificates
from mako.lookup import TemplateLookup
@@ -253,6 +254,18 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_recently_added.")
return serve_template(templatename="recently_added.html", data=None)
@cherrypy.expose
def delete_temp_sessions(self):
result = database.delete_sessions()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
##### Libraries #####
@@ -776,6 +789,10 @@ class WebInterface(object):
media_type = kwargs.get('media_type', "")
if media_type != 'all':
custom_where.append(['session_history.media_type', media_type])
if 'transcode_decision' in kwargs:
transcode_decision = kwargs.get('transcode_decision', "")
if transcode_decision:
custom_where.append(['session_history_media_info.transcode_decision', transcode_decision])
data_factory = datafactory.DataFactory()
history = data_factory.get_datatables_history(kwargs=kwargs, custom_where=custom_where, grouping=grouping, watched_percent=watched_percent)
@@ -989,9 +1006,9 @@ class WebInterface(object):
logger.warn(u"Unable to retrieve data for get_stream_type_by_top_10_platforms.")
@cherrypy.expose
def history_table_modal(self, start_date=None, **kwargs):
def history_table_modal(self, **kwargs):
return serve_template(templatename="history_table_modal.html", title="History Data", data=start_date)
return serve_template(templatename="history_table_modal.html", title="History Data", data=kwargs)
##### Sync #####
@@ -1082,6 +1099,28 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(log_lines)
@cherrypy.expose
@addtoapi()
def get_notification_log(self, **kwargs):
data_factory = datafactory.DataFactory()
notifications = data_factory.get_notification_log(kwargs=kwargs)
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps(notifications)
@cherrypy.expose
@addtoapi()
def clearNotifyLogs(self, **kwargs):
data_factory = datafactory.DataFactory()
result = data_factory.delete_notification_log()
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
@cherrypy.expose
def clearLogs(self):
plexpy.LOG_LIST = []
@@ -1106,6 +1145,15 @@ class WebInterface(object):
line))
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 #####
@cherrypy.expose
@@ -1138,8 +1186,10 @@ class WebInterface(object):
"api_key": plexpy.CONFIG.API_KEY,
"update_db_interval": plexpy.CONFIG.UPDATE_DB_INTERVAL,
"freeze_db": checked(plexpy.CONFIG.FREEZE_DB),
"log_dir": plexpy.CONFIG.LOG_DIR,
"backup_dir": plexpy.CONFIG.BACKUP_DIR,
"cache_dir": plexpy.CONFIG.CACHE_DIR,
"log_dir": plexpy.CONFIG.LOG_DIR,
"log_blacklist": checked(plexpy.CONFIG.LOG_BLACKLIST),
"check_github": checked(plexpy.CONFIG.CHECK_GITHUB),
"interface_list": interface_list,
"cache_sizemb": plexpy.CONFIG.CACHE_SIZEMB,
@@ -1169,6 +1219,7 @@ class WebInterface(object):
"tv_notify_on_pause": checked(plexpy.CONFIG.TV_NOTIFY_ON_PAUSE),
"movie_notify_on_pause": checked(plexpy.CONFIG.MOVIE_NOTIFY_ON_PAUSE),
"music_notify_on_pause": checked(plexpy.CONFIG.MUSIC_NOTIFY_ON_PAUSE),
"monitor_pms_updates": checked(plexpy.CONFIG.MONITOR_PMS_UPDATES),
"monitor_remote_access": checked(plexpy.CONFIG.MONITOR_REMOTE_ACCESS),
"monitoring_interval": plexpy.CONFIG.MONITORING_INTERVAL,
"monitoring_use_websocket": checked(plexpy.CONFIG.MONITORING_USE_WEBSOCKET),
@@ -1183,6 +1234,7 @@ class WebInterface(object):
"logging_ignore_interval": plexpy.CONFIG.LOGGING_IGNORE_INTERVAL,
"pms_is_remote": checked(plexpy.CONFIG.PMS_IS_REMOTE),
"notify_consecutive": checked(plexpy.CONFIG.NOTIFY_CONSECUTIVE),
"notify_upload_posters": checked(plexpy.CONFIG.NOTIFY_UPLOAD_POSTERS),
"notify_recently_added": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED),
"notify_recently_added_grandparent": checked(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT),
"notify_recently_added_delay": plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_DELAY,
@@ -1209,6 +1261,8 @@ class WebInterface(object):
"notify_on_extup_body_text": plexpy.CONFIG.NOTIFY_ON_EXTUP_BODY_TEXT,
"notify_on_intup_subject_text": plexpy.CONFIG.NOTIFY_ON_INTUP_SUBJECT_TEXT,
"notify_on_intup_body_text": plexpy.CONFIG.NOTIFY_ON_INTUP_BODY_TEXT,
"notify_on_pmsupdate_subject_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_SUBJECT_TEXT,
"notify_on_pmsupdate_body_text": plexpy.CONFIG.NOTIFY_ON_PMSUPDATE_BODY_TEXT,
"notify_scripts_args_text": plexpy.CONFIG.NOTIFY_SCRIPTS_ARGS_TEXT,
"home_stats_length": plexpy.CONFIG.HOME_STATS_LENGTH,
"home_stats_type": checked(plexpy.CONFIG.HOME_STATS_TYPE),
@@ -1217,7 +1271,8 @@ class WebInterface(object):
"home_library_cards": json.dumps(plexpy.CONFIG.HOME_LIBRARY_CARDS),
"buffer_threshold": plexpy.CONFIG.BUFFER_THRESHOLD,
"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)
@@ -1235,17 +1290,20 @@ class WebInterface(object):
"tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause",
"refresh_libraries_on_startup", "refresh_users_on_startup",
"ip_logging_enable", "movie_logging_enable", "tv_logging_enable", "music_logging_enable",
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive",
"notify_recently_added", "notify_recently_added_grandparent", "monitor_remote_access", "get_file_sizes"
"pms_is_remote", "home_stats_type", "group_history_tables", "notify_consecutive", "notify_upload_posters",
"notify_recently_added", "notify_recently_added_grandparent",
"monitor_pms_updates", "monitor_remote_access", "get_file_sizes", "log_blacklist"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
# checked items should be zero or one. if they were not sent then the item was not checked
kwargs[checked_config] = '0'
kwargs[checked_config] = 0
else:
kwargs[checked_config] = 1
# If http password exists in config, do not overwrite when blank value received
if kwargs.get('http_password'):
if kwargs['http_password'].strip() == '' and plexpy.CONFIG.HTTP_PASSWORD != '':
if kwargs['http_password'] == ' ' and plexpy.CONFIG.HTTP_PASSWORD != '':
kwargs['http_password'] = plexpy.CONFIG.HTTP_PASSWORD
for plain_config, use_config in [(x[4:], x) for x in kwargs if x.startswith('use_')]:
@@ -1261,16 +1319,18 @@ class WebInterface(object):
refresh_users = False
# 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_users_interval') != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL) or \
kwargs.get('notify_recently_added') != str(plexpy.CONFIG.NOTIFY_RECENTLY_ADDED) or \
kwargs.get('monitor_remote_access') != str(plexpy.CONFIG.MONITOR_REMOTE_ACCESS):
kwargs.get('notify_recently_added') != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED or \
kwargs.get('monitor_pms_updates') != plexpy.CONFIG.MONITOR_PMS_UPDATES or \
kwargs.get('monitor_remote_access') != plexpy.CONFIG.MONITOR_REMOTE_ACCESS:
reschedule = True
# If we change the SSL setting for PMS or PMS remote setting, make sure we grab the new url.
if kwargs.get('pms_ssl') != str(plexpy.CONFIG.PMS_SSL) or \
kwargs.get('pms_is_remote') != str(plexpy.CONFIG.PMS_IS_REMOTE):
if kwargs.get('pms_ssl') != plexpy.CONFIG.PMS_SSL or \
kwargs.get('pms_is_remote') != plexpy.CONFIG.PMS_IS_REMOTE:
server_changed = True
# If we change the HTTPS setting, make sure we generate a new certificate.
@@ -1317,6 +1377,7 @@ class WebInterface(object):
if server_changed:
plextv.get_real_pms_url()
pmsconnect.get_server_friendly_name()
web_socket.reconnect()
# Reconfigure scheduler if intervals changed
if reschedule:
@@ -1405,7 +1466,7 @@ class WebInterface(object):
if this_agent:
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."
else:
logger.debug(u"Unable to send test notification, invalid notification agent ID %s." % agent_id)
@@ -1620,11 +1681,16 @@ class WebInterface(object):
if source == 'history':
data_factory = datafactory.DataFactory()
metadata = data_factory.get_metadata_details(rating_key=rating_key)
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
else:
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_metadata_details(rating_key=rating_key, get_media_info=True)
if result:
metadata = result['metadata']
data_factory = datafactory.DataFactory()
poster_url = data_factory.get_poster_url(metadata=metadata)
metadata['poster_url'] = poster_url
if metadata:
return serve_template(templatename="info.html", data=metadata, title="Info", config=config, source=source)
@@ -1671,6 +1737,22 @@ class WebInterface(object):
return None
@cherrypy.expose
def delete_poster_url(self, poster_url=''):
if poster_url:
data_factory = datafactory.DataFactory()
result = data_factory.delete_poster_url(poster_url=poster_url)
else:
result = None
if result:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': result})
else:
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
##### Search #####
@@ -2152,11 +2234,18 @@ class WebInterface(object):
'I need your clothes, your boots and your motorcycle.',
'No, it\'s not a tumor. It\'s not a tumor!',
'I LIED!',
'See you at the party, Richter!',
'Are you Sarah Conner?',
'Are you Sarah Connor?',
'I\'m a cop you idiot!',
'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)
@@ -2173,3 +2262,9 @@ class WebInterface(object):
a = Api()
a.checkParams(*args, **kwargs)
return a.fetchData()
@cherrypy.expose
def check_pms_updater(self):
pms_connect = pmsconnect.PmsConnect()
result = pms_connect.get_update_staus()
return json.dumps(result)

View File

@@ -46,12 +46,11 @@ def initialize(options):
options_dict = {
'server.socket_port': options['http_port'],
'server.socket_host': options['http_host'],
'environment': options['http_environment'],
'server.thread_pool': 10,
'tools.encode.on': True,
'tools.encode.encoding': 'utf-8',
'tools.decode.on': True,
'log.screen': False,
'engine.autoreload.on': False,
}
if enable_https:
@@ -61,6 +60,10 @@ def initialize(options):
else:
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,
options['http_host'], options['http_port'])
cherrypy.config.update(options_dict)
@@ -119,7 +122,12 @@ def initialize(options):
try:
cherrypy.process.servers.check_port(str(options['http_host']), options['http_port'])
if not plexpy.DEV:
cherrypy.server.start()
else:
cherrypy.engine.signals.subscribe()
cherrypy.engine.start()
cherrypy.engine.block()
except IOError:
sys.stderr.write('Failed to start on port: %i. Is something else running?\n' % (options['http_port']))
sys.exit(1)