mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-22 22:23:36 -07:00
Merge branch 'dev' into progress_time_formatted
Conflicts: plexpy/notification_handler.py
This commit is contained in:
commit
4d849e73e9
19 changed files with 201 additions and 118 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -1,5 +1,18 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.3.3 (2016-01-26)
|
||||||
|
|
||||||
|
* Fix: Plays by Month graph not loading.
|
||||||
|
* Change: Disable caching for datatables.
|
||||||
|
* Change: Improved updating library data in the database again.
|
||||||
|
|
||||||
|
|
||||||
|
## v1.3.2 (2016-01-24)
|
||||||
|
|
||||||
|
* Fix: 'datestamp' and 'timestamp' for server notifications.
|
||||||
|
* Change: New method for updating library data in database.
|
||||||
|
|
||||||
|
|
||||||
## v1.3.1 (2016-01-23)
|
## v1.3.1 (2016-01-23)
|
||||||
|
|
||||||
* Fix: Notifiers authorization popups for reverse proxies.
|
* Fix: Notifiers authorization popups for reverse proxies.
|
||||||
|
@ -7,8 +20,8 @@
|
||||||
* Fix: Star rating overlapping text.
|
* Fix: Star rating overlapping text.
|
||||||
* Fix: Unable to startup when library refresh fails.
|
* Fix: Unable to startup when library refresh fails.
|
||||||
* Fix: Unable to parse 'datestamp' and 'timestamp' format.
|
* Fix: Unable to parse 'datestamp' and 'timestamp' format.
|
||||||
* Change: Rename "Last Watched" to "Last Played"
|
* Change: Rename "Last Watched" to "Last Played".
|
||||||
* Change: More descriptive libraries updating message
|
* Change: More descriptive libraries updating message.
|
||||||
|
|
||||||
|
|
||||||
## v1.3.0 (2016-01-23)
|
## v1.3.0 (2016-01-23)
|
||||||
|
|
|
@ -22,7 +22,7 @@ history_table_options = {
|
||||||
"emptyTable": "No data in table"
|
"emptyTable": "No data in table"
|
||||||
},
|
},
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
"pageLength": 25,
|
"pageLength": 25,
|
||||||
|
|
|
@ -16,7 +16,7 @@ libraries_list_table_options = {
|
||||||
"pageLength": 10,
|
"pageLength": 10,
|
||||||
"order": [ 2, 'asc'],
|
"order": [ 2, 'asc'],
|
||||||
"autoWidth": true,
|
"autoWidth": true,
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -5,7 +5,7 @@ var log_table_options = {
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"order": [ 0, 'desc'],
|
"order": [ 0, 'desc'],
|
||||||
"pageLength": 50,
|
"pageLength": 50,
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"language": {
|
"language": {
|
||||||
"search":"Search: ",
|
"search":"Search: ",
|
||||||
"lengthMenu":"Show _MENU_ lines per page",
|
"lengthMenu":"Show _MENU_ lines per page",
|
||||||
|
|
|
@ -5,7 +5,7 @@ var plex_log_table_options = {
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"order": [ 0, 'desc'],
|
"order": [ 0, 'desc'],
|
||||||
"pageLength": 50,
|
"pageLength": 50,
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"language": {
|
"language": {
|
||||||
"search":"Search: ",
|
"search":"Search: ",
|
||||||
"lengthMenu":"Show _MENU_ lines per page",
|
"lengthMenu":"Show _MENU_ lines per page",
|
||||||
|
|
|
@ -4,7 +4,7 @@ sync_table_options = {
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
"order": [ [ 0, 'desc'], [ 1, 'asc'], [2, 'asc'] ],
|
||||||
"pageLength": 25,
|
"pageLength": 25,
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"language": {
|
"language": {
|
||||||
"search":"Search: ",
|
"search":"Search: ",
|
||||||
"lengthMenu":"Show _MENU_ lines per page",
|
"lengthMenu":"Show _MENU_ lines per page",
|
||||||
|
|
|
@ -8,7 +8,7 @@ user_ip_table_options = {
|
||||||
"infoFiltered":"(filtered from _MAX_ total entries)",
|
"infoFiltered":"(filtered from _MAX_ total entries)",
|
||||||
"emptyTable": "No data in table",
|
"emptyTable": "No data in table",
|
||||||
},
|
},
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"processing": false,
|
"processing": false,
|
||||||
"serverSide": true,
|
"serverSide": true,
|
||||||
|
|
|
@ -16,7 +16,7 @@ users_list_table_options = {
|
||||||
"pageLength": 10,
|
"pageLength": 10,
|
||||||
"order": [ 2, 'asc'],
|
"order": [ 2, 'asc'],
|
||||||
"autoWidth": true,
|
"autoWidth": true,
|
||||||
"stateSave": true,
|
"stateSave": false,
|
||||||
"pagingType": "bootstrap",
|
"pagingType": "bootstrap",
|
||||||
"columnDefs": [
|
"columnDefs": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
<div id="update_section_ids_message" style="text-align: center; margin-top: 20px;">
|
<div id="update_section_ids_message" style="text-align: center; margin-top: 20px;">
|
||||||
<i class="fa fa-refresh fa-spin"></i> PlexPy is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.
|
<i class="fa fa-refresh fa-spin"></i> PlexPy is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.
|
||||||
<br />
|
<br />
|
||||||
You may leave this page and come back later. Note: All monitoring has been disabled while this update is in progress.
|
You may leave this page and come back later.
|
||||||
</div>
|
</div>
|
||||||
% endif
|
% endif
|
||||||
<div class='table-card-header'>
|
<div class='table-card-header'>
|
||||||
|
@ -177,7 +177,7 @@
|
||||||
$("#refresh-libraries-list").click(function () {
|
$("#refresh-libraries-list").click(function () {
|
||||||
if ("${config['update_section_ids']}" == "1") {
|
if ("${config['update_section_ids']}" == "1") {
|
||||||
$('#update_section_ids_message').html(
|
$('#update_section_ids_message').html(
|
||||||
'<i class="fa fa-refresh fa-spin"></i> PlexPy is updating library IDs in the database. This could take a few minutes depending on the size of your database.' +
|
'<i class="fa fa-refresh fa-spin"></i> PlexPy is updating library IDs in the database. This could take a few minutes to hours depending on the size of your database.' +
|
||||||
'<br />' +
|
'<br />' +
|
||||||
'You may leave this page and come back later.');
|
'You may leave this page and come back later.');
|
||||||
$(this).prop('disabled', true);
|
$(this).prop('disabled', true);
|
||||||
|
|
|
@ -1228,6 +1228,10 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<td><strong>{transcode_audio_channels}</strong></td>
|
<td><strong>{transcode_audio_channels}</strong></td>
|
||||||
<td>The audio channels of the transcoded media.</td>
|
<td>The audio channels of the transcoded media.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{user_id}</strong></td>
|
||||||
|
<td>The unique identifier for the user.</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<table class="notification-params">
|
<table class="notification-params">
|
||||||
|
@ -1327,6 +1331,22 @@ available_notification_agents = sorted(notifiers.available_notification_agents()
|
||||||
<td><strong>{duration}</strong></td>
|
<td><strong>{duration}</strong></td>
|
||||||
<td>The duration (in minutes) for the item.</td>
|
<td>The duration (in minutes) for the item.</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{section_id}</strong></td>
|
||||||
|
<td>The unique identifier for the library.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{rating_key}</strong></td>
|
||||||
|
<td>The unique identifier for the item.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{parent_rating_key}</strong></td>
|
||||||
|
<td>The unique identifier for the item parent (season or album).</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>{grandparent_rating_key}</strong></td>
|
||||||
|
<td>The unique identifier for the item grandparent (TV show or artist).</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -739,7 +739,7 @@ def dbcheck():
|
||||||
'ALTER TABLE library_sections_temp RENAME TO library_sections'
|
'ALTER TABLE library_sections_temp RENAME TO library_sections'
|
||||||
)
|
)
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
logger.debug(u"Unable to remove section_id unique constraint from library_sections.")
|
logger.warn(u"Unable to remove section_id unique constraint from library_sections.")
|
||||||
try:
|
try:
|
||||||
c_db.execute(
|
c_db.execute(
|
||||||
'DROP TABLE library_sections_temp'
|
'DROP TABLE library_sections_temp'
|
||||||
|
@ -747,6 +747,17 @@ def dbcheck():
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# Upgrade library_sections table from earlier versions (remove duplicated libraries)
|
||||||
|
try:
|
||||||
|
result = c_db.execute('SELECT * FROM library_sections WHERE server_id = ""')
|
||||||
|
if result.rowcount > 0:
|
||||||
|
logger.debug(u"Altering database. Removing duplicate libraries from library_sections table.")
|
||||||
|
c_db.execute(
|
||||||
|
'DELETE FROM library_sections WHERE server_id = ""'
|
||||||
|
)
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
logger.warn(u"Unable to remove duplicate libraries from library_sections table.")
|
||||||
|
|
||||||
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
# Upgrade users table from earlier versions (remove UNIQUE constraint on username)
|
||||||
try:
|
try:
|
||||||
result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_users_2")')
|
result = c_db.execute('PRAGMA index_xinfo("sqlite_autoindex_users_2")')
|
||||||
|
@ -773,7 +784,7 @@ def dbcheck():
|
||||||
'ALTER TABLE users_temp RENAME TO users'
|
'ALTER TABLE users_temp RENAME TO users'
|
||||||
)
|
)
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
logger.debug(u"Unable to remove username unique constraint from users.")
|
logger.warn(u"Unable to remove username unique constraint from users.")
|
||||||
try:
|
try:
|
||||||
c_db.execute(
|
c_db.execute(
|
||||||
'DROP TABLE users_temp'
|
'DROP TABLE users_temp'
|
||||||
|
|
|
@ -156,8 +156,8 @@ class ActivityHandler(object):
|
||||||
(self.get_session_key(), buffer_last_triggered))
|
(self.get_session_key(), buffer_last_triggered))
|
||||||
time_since_last_trigger = int(time.time()) - int(buffer_last_triggered)
|
time_since_last_trigger = int(time.time()) - int(buffer_last_triggered)
|
||||||
|
|
||||||
if current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and time_since_last_trigger == 0 or \
|
if plexpy.CONFIG.BUFFER_THRESHOLD > 0 and (current_buffer_count >= plexpy.CONFIG.BUFFER_THRESHOLD and \
|
||||||
time_since_last_trigger >= plexpy.CONFIG.BUFFER_WAIT:
|
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())
|
ap.set_session_buffer_trigger_time(session_key=self.get_session_key())
|
||||||
threading.Thread(target=notification_handler.notify,
|
threading.Thread(target=notification_handler.notify,
|
||||||
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
|
kwargs=dict(stream_data=db_stream, notify_action='buffer')).start()
|
||||||
|
|
|
@ -360,9 +360,10 @@ _CONFIG_DEFINITIONS = {
|
||||||
'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
'TV_NOTIFY_ON_STOP': (int, 'Monitoring', 0),
|
||||||
'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
|
'TV_NOTIFY_ON_PAUSE': (int, 'Monitoring', 0),
|
||||||
'TWITTER_ENABLED': (int, 'Twitter', 0),
|
'TWITTER_ENABLED': (int, 'Twitter', 0),
|
||||||
'TWITTER_PASSWORD': (str, 'Twitter', ''),
|
'TWITTER_ACCESS_TOKEN': (str, 'Twitter', ''),
|
||||||
'TWITTER_PREFIX': (str, 'Twitter', 'PlexPy'),
|
'TWITTER_ACCESS_TOKEN_SECRET': (str, 'Twitter', ''),
|
||||||
'TWITTER_USERNAME': (str, 'Twitter', ''),
|
'TWITTER_CONSUMER_KEY': (str, 'Twitter', ''),
|
||||||
|
'TWITTER_CONSUMER_SECRET': (str, 'Twitter', ''),
|
||||||
'TWITTER_ON_PLAY': (int, 'Twitter', 0),
|
'TWITTER_ON_PLAY': (int, 'Twitter', 0),
|
||||||
'TWITTER_ON_STOP': (int, 'Twitter', 0),
|
'TWITTER_ON_STOP': (int, 'Twitter', 0),
|
||||||
'TWITTER_ON_PAUSE': (int, 'Twitter', 0),
|
'TWITTER_ON_PAUSE': (int, 'Twitter', 0),
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
# along with PlexPy. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from plexpy import logger, database, helpers, common
|
from plexpy import logger, database, helpers, common
|
||||||
|
import plexpy
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import locale
|
|
||||||
|
|
||||||
|
|
||||||
class Graphs(object):
|
class Graphs(object):
|
||||||
|
@ -321,7 +321,7 @@ class Graphs(object):
|
||||||
dt = datetime.datetime(*month_item[:6])
|
dt = datetime.datetime(*month_item[:6])
|
||||||
date_string = dt.strftime('%Y-%m')
|
date_string = dt.strftime('%Y-%m')
|
||||||
|
|
||||||
categories.append(dt.strftime('%b %Y').decode(locale.getlocale()[1]))
|
categories.append(dt.strftime('%b %Y').decode(plexpy.SYS_ENCODING, 'replace'))
|
||||||
series_1_value = 0
|
series_1_value = 0
|
||||||
series_2_value = 0
|
series_2_value = 0
|
||||||
series_3_value = 0
|
series_3_value = 0
|
||||||
|
|
|
@ -18,25 +18,28 @@ import plexpy
|
||||||
|
|
||||||
def update_section_ids():
|
def update_section_ids():
|
||||||
from plexpy import pmsconnect, activity_pinger
|
from plexpy import pmsconnect, activity_pinger
|
||||||
import threading
|
#import threading
|
||||||
|
|
||||||
plexpy.CONFIG.UPDATE_SECTION_IDS = -1
|
plexpy.CONFIG.UPDATE_SECTION_IDS = -1
|
||||||
|
|
||||||
logger.info(u"PlexPy Libraries :: Updating section_id's in database.")
|
logger.info(u"PlexPy Libraries :: Updating section_id's in database.")
|
||||||
|
|
||||||
logger.debug(u"PlexPy Libraries :: Disabling monitoring while update in progress.")
|
#logger.debug(u"PlexPy Libraries :: Disabling monitoring while update in progress.")
|
||||||
plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions',
|
#plexpy.schedule_job(activity_pinger.check_active_sessions, 'Check for active sessions',
|
||||||
hours=0, minutes=0, seconds=0)
|
# hours=0, minutes=0, seconds=0)
|
||||||
plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
|
#plexpy.schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
|
||||||
hours=0, minutes=0, seconds=0)
|
# 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 server response',
|
||||||
hours=0, minutes=0, seconds=0)
|
# hours=0, minutes=0, seconds=0)
|
||||||
|
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
query = 'SELECT id, rating_key FROM session_history_metadata WHERE section_id IS NULL'
|
query = 'SELECT id, rating_key, grandparent_rating_key, media_type ' \
|
||||||
result = monitor_db.select(query=query)
|
'FROM session_history_metadata WHERE section_id IS NULL'
|
||||||
|
history_results = monitor_db.select(query=query)
|
||||||
|
query = 'SELECT section_id, section_type FROM library_sections'
|
||||||
|
library_results = monitor_db.select(query=query)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn(u"PlexPy Libraries :: Unable to execute database query for update_section_ids: %s." % e)
|
logger.warn(u"PlexPy Libraries :: Unable to execute database query for update_section_ids: %s." % e)
|
||||||
|
|
||||||
|
@ -44,36 +47,52 @@ def update_section_ids():
|
||||||
plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 1)
|
plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 1)
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.")
|
#logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.")
|
||||||
plexpy.initialize_scheduler()
|
#plexpy.initialize_scheduler()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Add thread filter to the logger
|
# Add thread filter to the logger
|
||||||
logger.debug(u"PlexPy Libraries :: Disabling logging in the current thread while update in progress.")
|
#logger.debug(u"PlexPy Libraries :: Disabling logging in the current thread while update in progress.")
|
||||||
thread_filter = logger.NoThreadFilter(threading.current_thread().name)
|
#thread_filter = logger.NoThreadFilter(threading.current_thread().name)
|
||||||
for handler in logger.logger.handlers:
|
#for handler in logger.logger.handlers:
|
||||||
handler.addFilter(thread_filter)
|
# handler.addFilter(thread_filter)
|
||||||
|
|
||||||
|
# Get rating_key: section_id mapping pairs
|
||||||
|
key_mappings = {}
|
||||||
|
|
||||||
pms_connect = pmsconnect.PmsConnect()
|
pms_connect = pmsconnect.PmsConnect()
|
||||||
|
for library in library_results:
|
||||||
|
section_id = library['section_id']
|
||||||
|
section_type = library['section_type']
|
||||||
|
|
||||||
|
if section_type != 'photo':
|
||||||
|
library_children = pms_connect.get_library_children_details(section_id=section_id,
|
||||||
|
section_type=section_type)
|
||||||
|
if library_children:
|
||||||
|
children_list = library_children['childern_list']
|
||||||
|
key_mappings.update({child['rating_key']:child['section_id'] for child in children_list})
|
||||||
|
else:
|
||||||
|
logger.warn(u"PlexPy Libraries :: Unable to get a list of library items for section_id %s." % section_id)
|
||||||
|
|
||||||
error_keys = set()
|
error_keys = set()
|
||||||
for item in result:
|
for item in history_results:
|
||||||
id = item['id']
|
rating_key = item['grandparent_rating_key'] if item['media_type'] != 'movie' else item['rating_key']
|
||||||
rating_key = item['rating_key']
|
section_id = key_mappings.get(str(rating_key), None)
|
||||||
metadata = pms_connect.get_metadata_details(rating_key=rating_key)
|
|
||||||
|
|
||||||
if metadata:
|
if section_id:
|
||||||
metadata = metadata['metadata']
|
try:
|
||||||
section_keys = {'id': id}
|
section_keys = {'id': item['id']}
|
||||||
section_values = {'section_id': metadata['section_id']}
|
section_values = {'section_id': section_id}
|
||||||
monitor_db.upsert('session_history_metadata', key_dict=section_keys, value_dict=section_values)
|
monitor_db.upsert('session_history_metadata', key_dict=section_keys, value_dict=section_values)
|
||||||
|
except:
|
||||||
|
error_keys.add(item['rating_key'])
|
||||||
else:
|
else:
|
||||||
error_keys.add(rating_key)
|
error_keys.add(item['rating_key'])
|
||||||
|
|
||||||
# Remove thread filter from the logger
|
# Remove thread filter from the logger
|
||||||
for handler in logger.logger.handlers:
|
#for handler in logger.logger.handlers:
|
||||||
handler.removeFilter(thread_filter)
|
# handler.removeFilter(thread_filter)
|
||||||
logger.debug(u"PlexPy Libraries :: Re-enabling logging in the current thread.")
|
#logger.debug(u"PlexPy Libraries :: Re-enabling logging in the current thread.")
|
||||||
|
|
||||||
if error_keys:
|
if error_keys:
|
||||||
logger.info(u"PlexPy Libraries :: Updated all section_id's in database except for rating_keys: %s." %
|
logger.info(u"PlexPy Libraries :: Updated all section_id's in database except for rating_keys: %s." %
|
||||||
|
@ -84,8 +103,8 @@ def update_section_ids():
|
||||||
plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 0)
|
plexpy.CONFIG.__setattr__('UPDATE_SECTION_IDS', 0)
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.")
|
#logger.debug(u"PlexPy Libraries :: Re-enabling monitoring.")
|
||||||
plexpy.initialize_scheduler()
|
#plexpy.initialize_scheduler()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -211,12 +211,6 @@ def notify(stream_data=None, notify_action=None):
|
||||||
|
|
||||||
def notify_timeline(timeline_data=None, notify_action=None):
|
def notify_timeline(timeline_data=None, notify_action=None):
|
||||||
if timeline_data and notify_action:
|
if timeline_data and notify_action:
|
||||||
if (timeline_data['media_type'] == 'movie' and plexpy.CONFIG.MOVIE_NOTIFY_ENABLE) \
|
|
||||||
or ((timeline_data['media_type'] == 'show' or timeline_data['media_type'] == 'episode') \
|
|
||||||
and plexpy.CONFIG.TV_NOTIFY_ENABLE) \
|
|
||||||
or ((timeline_data['media_type'] == 'artist' or timeline_data['media_type'] == 'track') \
|
|
||||||
and plexpy.CONFIG.MUSIC_NOTIFY_ENABLE):
|
|
||||||
|
|
||||||
for agent in notifiers.available_notification_agents():
|
for agent in notifiers.available_notification_agents():
|
||||||
if agent['on_created'] and notify_action == 'created':
|
if agent['on_created'] and notify_action == 'created':
|
||||||
# Build and send notification
|
# Build and send notification
|
||||||
|
@ -462,6 +456,7 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||||
transcode_video_height = ''
|
transcode_video_height = ''
|
||||||
transcode_audio_codec = ''
|
transcode_audio_codec = ''
|
||||||
transcode_audio_channels = ''
|
transcode_audio_channels = ''
|
||||||
|
user_id = ''
|
||||||
progress_time = ''
|
progress_time = ''
|
||||||
|
|
||||||
# Session values
|
# Session values
|
||||||
|
@ -485,7 +480,6 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||||
stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60)
|
stream_duration = int((time.time() - helpers.cast_to_float(session['started'])) / 60)
|
||||||
|
|
||||||
view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset'])
|
view_offset = helpers.convert_milliseconds_to_minutes(session['view_offset'])
|
||||||
progress_time = arrow.get(helpers.cast_to_int(session['view_offset'])/1000).format(plexpy.CONFIG.TIME_FORMAT.replace('zz','').replace('a','').replace('A','').replace('h',''))
|
|
||||||
user = session['friendly_name']
|
user = session['friendly_name']
|
||||||
platform = session['platform']
|
platform = session['platform']
|
||||||
player = session['player']
|
player = session['player']
|
||||||
|
@ -506,6 +500,8 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||||
transcode_video_height = session['transcode_height']
|
transcode_video_height = session['transcode_height']
|
||||||
transcode_audio_codec = session['transcode_audio_codec']
|
transcode_audio_codec = session['transcode_audio_codec']
|
||||||
transcode_audio_channels = session['transcode_audio_channels']
|
transcode_audio_channels = session['transcode_audio_channels']
|
||||||
|
user_id = session['user_id']
|
||||||
|
progress_time = arrow.get(helpers.cast_to_int(session['view_offset'])/1000).format(plexpy.CONFIG.TIME_FORMAT.replace('zz','').replace('a','').replace('A','').replace('h',''))
|
||||||
|
|
||||||
progress_percent = helpers.get_percent(view_offset, duration)
|
progress_percent = helpers.get_percent(view_offset, duration)
|
||||||
|
|
||||||
|
@ -558,6 +554,7 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||||
'transcode_video_height': transcode_video_height,
|
'transcode_video_height': transcode_video_height,
|
||||||
'transcode_audio_codec': transcode_audio_codec,
|
'transcode_audio_codec': transcode_audio_codec,
|
||||||
'transcode_audio_channels': transcode_audio_channels,
|
'transcode_audio_channels': transcode_audio_channels,
|
||||||
|
'user_id': user_id,
|
||||||
'title': full_title,
|
'title': full_title,
|
||||||
'library_name': metadata['library_name'],
|
'library_name': metadata['library_name'],
|
||||||
'show_name': show_name,
|
'show_name': show_name,
|
||||||
|
@ -579,7 +576,11 @@ def build_notify_text(session=None, timeline=None, state=None):
|
||||||
'summary': metadata['summary'],
|
'summary': metadata['summary'],
|
||||||
'tagline': metadata['tagline'],
|
'tagline': metadata['tagline'],
|
||||||
'rating': metadata['rating'],
|
'rating': metadata['rating'],
|
||||||
'duration': duration
|
'duration': duration,
|
||||||
|
'section_id': metadata['section_id'],
|
||||||
|
'rating_key': metadata['rating_key'],
|
||||||
|
'parent_rating_key': metadata['parent_rating_key'],
|
||||||
|
'grandparent_rating_key': metadata['grandparent_rating_key']
|
||||||
}
|
}
|
||||||
|
|
||||||
# Default subject text
|
# Default subject text
|
||||||
|
@ -798,8 +799,8 @@ def build_server_notify_text(state=None):
|
||||||
available_params = {'server_name': server_name,
|
available_params = {'server_name': server_name,
|
||||||
'server_uptime': server_uptime,
|
'server_uptime': server_uptime,
|
||||||
'action': state,
|
'action': state,
|
||||||
'datestamp': time.strftime(helpers.parse_js_date(plexpy.CONFIG.DATE_FORMAT)),
|
'datestamp': arrow.now().format(plexpy.CONFIG.DATE_FORMAT.replace('Do','').replace('zz','')),
|
||||||
'timestamp': time.strftime(helpers.parse_js_date(plexpy.CONFIG.TIME_FORMAT))}
|
'timestamp': arrow.now().format(plexpy.CONFIG.TIME_FORMAT.replace('Do','').replace('zz',''))}
|
||||||
|
|
||||||
# Default text
|
# Default text
|
||||||
subject_text = 'PlexPy (%s)' % server_name
|
subject_text = 'PlexPy (%s)' % server_name
|
||||||
|
|
|
@ -1165,8 +1165,10 @@ class TwitterNotifier(object):
|
||||||
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
SIGNIN_URL = 'https://api.twitter.com/oauth/authenticate'
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.consumer_key = "2LdJKXHDUwJtjYBsdwJisIOsh"
|
self.access_token = plexpy.CONFIG.TWITTER_ACCESS_TOKEN
|
||||||
self.consumer_secret = "QWbUcZzAIiL4zbDCIhy2EdUkV8yEEav3qMdo5y3FugxCFelWrA"
|
self.access_token_secret = plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET
|
||||||
|
self.consumer_key = plexpy.CONFIG.TWITTER_CONSUMER_KEY
|
||||||
|
self.consumer_secret = plexpy.CONFIG.TWITTER_CONSUMER_SECRET
|
||||||
|
|
||||||
def notify(self, subject, message):
|
def notify(self, subject, message):
|
||||||
if not subject or not message:
|
if not subject or not message:
|
||||||
|
@ -1191,16 +1193,16 @@ class TwitterNotifier(object):
|
||||||
else:
|
else:
|
||||||
request_token = dict(parse_qsl(content))
|
request_token = dict(parse_qsl(content))
|
||||||
|
|
||||||
plexpy.CONFIG.TWITTER_USERNAME = request_token['oauth_token']
|
plexpy.CONFIG.TWITTER_ACCESS_TOKEN = request_token['oauth_token']
|
||||||
plexpy.CONFIG.TWITTER_PASSWORD = request_token['oauth_token_secret']
|
plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET = request_token['oauth_token_secret']
|
||||||
|
|
||||||
return self.AUTHORIZATION_URL + "?oauth_token=" + request_token['oauth_token']
|
return self.AUTHORIZATION_URL + "?oauth_token=" + request_token['oauth_token']
|
||||||
|
|
||||||
def _get_credentials(self, key):
|
def _get_credentials(self, key):
|
||||||
request_token = {}
|
request_token = {}
|
||||||
|
|
||||||
request_token['oauth_token'] = plexpy.CONFIG.TWITTER_USERNAME
|
request_token['oauth_token'] = plexpy.CONFIG.TWITTER_ACCESS_TOKEN
|
||||||
request_token['oauth_token_secret'] = plexpy.CONFIG.TWITTER_PASSWORD
|
request_token['oauth_token_secret'] = plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET
|
||||||
request_token['oauth_callback_confirmed'] = 'true'
|
request_token['oauth_callback_confirmed'] = 'true'
|
||||||
|
|
||||||
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
|
token = oauth.Token(request_token['oauth_token'], request_token['oauth_token_secret'])
|
||||||
|
@ -1225,20 +1227,20 @@ class TwitterNotifier(object):
|
||||||
else:
|
else:
|
||||||
# logger.info(u"PlexPy Notifiers :: Your Twitter Access Token key: %s" % access_token['oauth_token'])
|
# logger.info(u"PlexPy Notifiers :: Your Twitter Access Token key: %s" % access_token['oauth_token'])
|
||||||
# logger.info(u"PlexPy Notifiers :: Access Token secret: %s" % access_token['oauth_token_secret'])
|
# logger.info(u"PlexPy Notifiers :: Access Token secret: %s" % access_token['oauth_token_secret'])
|
||||||
plexpy.CONFIG.TWITTER_USERNAME = access_token['oauth_token']
|
plexpy.CONFIG.TWITTER_ACCESS_TOKEN = access_token['oauth_token']
|
||||||
plexpy.CONFIG.TWITTER_PASSWORD = access_token['oauth_token_secret']
|
plexpy.CONFIG.TWITTER_ACCESS_TOKEN_SECRET = access_token['oauth_token_secret']
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _send_tweet(self, message=None):
|
def _send_tweet(self, message=None):
|
||||||
username = self.consumer_key
|
consumer_key = self.consumer_key
|
||||||
password = self.consumer_secret
|
consumer_secret = self.consumer_secret
|
||||||
access_token_key = plexpy.CONFIG.TWITTER_USERNAME
|
access_token = self.access_token
|
||||||
access_token_secret = plexpy.CONFIG.TWITTER_PASSWORD
|
access_token_secret = self.access_token_secret
|
||||||
|
|
||||||
# logger.info(u"PlexPy Notifiers :: Sending tweet: " + message)
|
# logger.info(u"PlexPy Notifiers :: Sending tweet: " + message)
|
||||||
|
|
||||||
api = twitter.Api(username, password, access_token_key, access_token_secret)
|
api = twitter.Api(consumer_key, consumer_secret, access_token, access_token_secret)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api.PostUpdate(message)
|
api.PostUpdate(message)
|
||||||
|
@ -1251,30 +1253,37 @@ class TwitterNotifier(object):
|
||||||
|
|
||||||
def return_config_options(self):
|
def return_config_options(self):
|
||||||
config_option = [{'label': 'Instructions',
|
config_option = [{'label': 'Instructions',
|
||||||
'description': 'Step 1: Click the <strong>Request Authorization</strong> button below.<br>\
|
'description': 'Step 1: Visit <a href="https://apps.twitter.com/" target="_blank"> \
|
||||||
Step 2: Input the <strong>Authorization Key</strong> you received from Step 1 below.<br>\
|
Twitter Apps</a> to <strong>Create New App</strong>. A vaild "Website" is not required.<br>\
|
||||||
Step 3: Click the <strong>Verify Key</strong> button below.',
|
Step 2: Go to <strong>Keys and Access Tokens</strong> and click \
|
||||||
|
<strong>Create my access token</strong>.<br>\
|
||||||
|
Step 3: Fill in the <strong>Consumer Key</strong>, <strong>Consumer Secret</strong>, \
|
||||||
|
<strong>Access Token</strong>, and <strong>Access Token Secret</strong> below.',
|
||||||
'input_type': 'help'
|
'input_type': 'help'
|
||||||
},
|
},
|
||||||
{'label': 'Request Authorization',
|
{'label': 'Twitter Consumer Key',
|
||||||
'value': 'Request Authorization',
|
'value': self.consumer_key,
|
||||||
'name': 'twitterStep1',
|
'name': 'twitter_consumer_key',
|
||||||
'description': 'Request Twitter authorization. (Ensure you allow the browser pop-up).',
|
'description': 'Your Twitter consumer key.',
|
||||||
'input_type': 'button'
|
|
||||||
},
|
|
||||||
{'label': 'Authorization Key',
|
|
||||||
'value': '',
|
|
||||||
'name': 'twitter_key',
|
|
||||||
'description': 'Your Twitter authorization key.',
|
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
},
|
},
|
||||||
{'label': 'Verify Key',
|
{'label': 'Twitter Consumer Secret',
|
||||||
'value': 'Verify Key',
|
'value': self.consumer_secret,
|
||||||
'name': 'twitterStep2',
|
'name': 'twitter_consumer_secret',
|
||||||
'description': 'Verify your Twitter authorization key.',
|
'description': 'Your Twitter consumer secret.',
|
||||||
'input_type': 'button'
|
'input_type': 'text'
|
||||||
},
|
},
|
||||||
{'input_type': 'nosave'
|
{'label': 'Twitter Access Token',
|
||||||
|
'value': self.access_token,
|
||||||
|
'name': 'twitter_access_token',
|
||||||
|
'description': 'Your Twitter access token.',
|
||||||
|
'input_type': 'text'
|
||||||
|
},
|
||||||
|
{'label': 'Twitter Access Token Secret',
|
||||||
|
'value': self.access_token_secret,
|
||||||
|
'name': 'twitter_access_token_secret',
|
||||||
|
'description': 'Your Twitter access token secret.',
|
||||||
|
'input_type': 'text'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1668,10 +1677,10 @@ class TELEGRAM(object):
|
||||||
'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="http://telegram.me/BotFather" target="_blank">@BotFather</a> on Telegram to get one.',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
},
|
},
|
||||||
{'label': 'Telegram Chat ID',
|
{'label': 'Telegram Chat ID, Group ID, or Channel Username',
|
||||||
'value': self.chat_id,
|
'value': self.chat_id,
|
||||||
'name': 'telegram_chat_id',
|
'name': 'telegram_chat_id',
|
||||||
'description': 'Your Telegram Chat ID, Group ID, or channel username. 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="http://telegram.me/myidbot" target="_blank">@myidbot</a> on Telegram to get an ID.',
|
||||||
'input_type': 'text'
|
'input_type': 'text'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2088,12 +2097,16 @@ class FacebookNotifier(object):
|
||||||
config_option = [{'label': 'Instructions',
|
config_option = [{'label': 'Instructions',
|
||||||
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
|
'description': '<strong>Facebook notifications are currently experimental!</strong><br><br> \
|
||||||
Step 1: Visit <a href="https://developers.facebook.com/apps/" target="_blank"> \
|
Step 1: Visit <a href="https://developers.facebook.com/apps/" target="_blank"> \
|
||||||
Facebook Developers</a> to create a new app using <strong>advanced setup</strong>.<br>\
|
Facebook Developers</a> to add a new app using <strong>basic setup</strong>.<br>\
|
||||||
Step 2: Go to <strong>Settings > Advanced</strong> and fill in \
|
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 (i.e. http://localhost:8181).<br>\
|
||||||
Step 3: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 2.<br>\
|
Step 4: Go to <strong>App Review</strong> and toggle public to <strong>Yes</strong>.<br>\
|
||||||
Step 4: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
|
Step 5: Fill in the <strong>PlexPy URL</strong> below with the exact same URL from Step 3.<br>\
|
||||||
Step 5: Click the <strong>Request Authorization</strong> button below.',
|
Step 6: Fill in the <strong>App ID</strong> and <strong>App Secret</strong> below.<br>\
|
||||||
|
Step 7: Click the <strong>Request Authorization</strong> button below.<br> \
|
||||||
|
Step 8: Fill in the <strong>Group ID</strong> below.',
|
||||||
'input_type': 'help'
|
'input_type': 'help'
|
||||||
},
|
},
|
||||||
{'label': 'PlexPy URL',
|
{'label': 'PlexPy URL',
|
||||||
|
|
|
@ -40,15 +40,19 @@ def get_server_friendly_name():
|
||||||
|
|
||||||
def refresh_libraries():
|
def refresh_libraries():
|
||||||
logger.info(u"PlexPy Pmsconnect :: Requesting libraries list refresh...")
|
logger.info(u"PlexPy Pmsconnect :: Requesting libraries list refresh...")
|
||||||
library_sections = PmsConnect().get_library_details()
|
|
||||||
|
|
||||||
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
server_id = plexpy.CONFIG.PMS_IDENTIFIER
|
||||||
|
if not server_id:
|
||||||
|
logger.error(u"PlexPy Pmsconnect :: No PMS identifier, cannot refresh libraries. Verify server in settings.")
|
||||||
|
return
|
||||||
|
|
||||||
library_keys = []
|
library_sections = PmsConnect().get_library_details()
|
||||||
|
|
||||||
if library_sections:
|
if library_sections:
|
||||||
monitor_db = database.MonitorDatabase()
|
monitor_db = database.MonitorDatabase()
|
||||||
|
|
||||||
|
library_keys = []
|
||||||
|
|
||||||
for section in library_sections:
|
for section in library_sections:
|
||||||
section_keys = {'server_id': server_id,
|
section_keys = {'server_id': server_id,
|
||||||
'section_id': section['section_id']}
|
'section_id': section['section_id']}
|
||||||
|
@ -72,10 +76,11 @@ def refresh_libraries():
|
||||||
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
|
plexpy.CONFIG.__setattr__('HOME_LIBRARY_CARDS', library_keys)
|
||||||
plexpy.CONFIG.write()
|
plexpy.CONFIG.write()
|
||||||
|
|
||||||
if plexpy.CONFIG.UPDATE_SECTION_IDS == 1:
|
if plexpy.CONFIG.UPDATE_SECTION_IDS == 1 or plexpy.CONFIG.UPDATE_SECTION_IDS == -1:
|
||||||
from plexpy import libraries
|
from plexpy import libraries
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
|
# Start library section_id update on it's own thread
|
||||||
threading.Thread(target=libraries.update_section_ids).start()
|
threading.Thread(target=libraries.update_section_ids).start()
|
||||||
|
|
||||||
logger.info(u"PlexPy Pmsconnect :: Libraries list refreshed.")
|
logger.info(u"PlexPy Pmsconnect :: Libraries list refreshed.")
|
||||||
|
@ -1588,9 +1593,9 @@ class PmsConnect(object):
|
||||||
sort_type = ''
|
sort_type = ''
|
||||||
|
|
||||||
if str(section_id).isdigit():
|
if str(section_id).isdigit():
|
||||||
library_data = self.get_library_list(section_id, list_type, count, sort_type, output_format='xml')
|
library_data = self.get_library_list(str(section_id), list_type, count, sort_type, output_format='xml')
|
||||||
elif str(rating_key).isdigit():
|
elif str(rating_key).isdigit():
|
||||||
library_data = self.get_children_list(rating_key, output_format='xml')
|
library_data = self.get_children_list(str(rating_key), output_format='xml')
|
||||||
else:
|
else:
|
||||||
logger.warn(u"PlexPy Pmsconnect :: get_library_children called by invalid section_id or rating_key provided.")
|
logger.warn(u"PlexPy Pmsconnect :: get_library_children called by invalid section_id or rating_key provided.")
|
||||||
return []
|
return []
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
PLEXPY_VERSION = "master"
|
PLEXPY_VERSION = "master"
|
||||||
PLEXPY_RELEASE_VERSION = "1.3.1"
|
PLEXPY_RELEASE_VERSION = "1.3.3"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue