diff --git a/CHANGELOG.md b/CHANGELOG.md
index 09ba5125..7128836a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,22 @@
# Changelog
+## v1.2.8 (2015-12-06)
+
+* Fix sanitize player names
+* Fix recently added notification delay
+* Fix recently added metadata queries
+* Fix multiple lines in notification body text
+* Fix UTF-8 encoding in Prowl notifications subject line
+* Change to only log IPv4 addresses
+* Add global toggle for recently added notifcations
+* Add feature to delete users
+* Add channel support for Telegram notification agent
+* Add icon for Apple tvOS
+* Add icon for Microsoft Edge
+
+
## v1.2.7 (2015-11-27)
+
* Fix IP address option in notifications
diff --git a/data/interfaces/default/css/plexpy.css b/data/interfaces/default/css/plexpy.css
index e2e11185..af57d0c1 100644
--- a/data/interfaces/default/css/plexpy.css
+++ b/data/interfaces/default/css/plexpy.css
@@ -2374,7 +2374,8 @@ a .home-platforms-instance-list-oval:hover,
top: 5px;
left: 12px;
}
-#users-to-delete > li {
+#users-to-delete > li,
+#users-to-purge > li {
color: #e9a049;
}
#updatebar {
diff --git a/data/interfaces/default/home_stats.html b/data/interfaces/default/home_stats.html
index 10832c41..e3773f3f 100644
--- a/data/interfaces/default/home_stats.html
+++ b/data/interfaces/default/home_stats.html
@@ -39,7 +39,7 @@ user_id Returns the user id for the associated stat.
friendly_name Returns the friendly name of the user for the associated stat.
== Only if 'stat_id' is 'top_platform' or 'last_watched' ==
-platform_type Returns the platform name for the associated stat.
+player Returns the player name for the associated stat.
== Only if 'stat_id' is 'last_watched' ==
last_watch Returns the time the media item was last watched.
@@ -709,7 +709,7 @@ DOCUMENTATION :: END
- - ${top_stat['rows'][0]['platform_type']}
+ - ${top_stat['rows'][0]['player']}
@@ -755,7 +755,7 @@ DOCUMENTATION :: END
- - ${top_stat['rows'][loop.index]['platform_type']}
+ - ${top_stat['rows'][loop.index]['player']}
diff --git a/data/interfaces/default/images/platforms/msedge.png b/data/interfaces/default/images/platforms/msedge.png
new file mode 100644
index 00000000..3aa8dd02
Binary files /dev/null and b/data/interfaces/default/images/platforms/msedge.png differ
diff --git a/data/interfaces/default/js/script.js b/data/interfaces/default/js/script.js
index 50348c46..5d82f90c 100644
--- a/data/interfaces/default/js/script.js
+++ b/data/interfaces/default/js/script.js
@@ -176,7 +176,9 @@ function getPlatformImagePath(platformName) {
if (platformName.indexOf("Roku") > -1) {
return 'interfaces/default/images/platforms/roku.png';
} else if (platformName.indexOf("Apple TV") > -1) {
- return 'interfaces/default/images/platforms/appletv.png';
+ return 'interfaces/default/images/platforms/atv.png';
+ } else if (platformName.indexOf("tvOS") > -1) {
+ return 'interfaces/default/images/platforms/atv.png';
} else if (platformName.indexOf("Firefox") > -1) {
return 'interfaces/default/images/platforms/firefox.png';
} else if (platformName.indexOf("Chromecast") > -1) {
@@ -201,6 +203,8 @@ function getPlatformImagePath(platformName) {
return 'interfaces/default/images/platforms/safari.png';
} else if (platformName.indexOf("Internet Explorer") > -1) {
return 'interfaces/default/images/platforms/ie.png';
+ } else if (platformName.indexOf("Microsoft Edge") > -1) {
+ return 'interfaces/default/images/platforms/msedge.png';
} else if (platformName.indexOf("Unknown Browser") > -1) {
return 'interfaces/default/images/platforms/dafault.png';
} else if (platformName.indexOf("Windows-XBMC") > -1) {
diff --git a/data/interfaces/default/js/tables/users.js b/data/interfaces/default/js/tables/users.js
index 78240a3e..ab03435e 100644
--- a/data/interfaces/default/js/tables/users.js
+++ b/data/interfaces/default/js/tables/users.js
@@ -1,3 +1,4 @@
+var users_to_delete = [];
var users_to_purge = [];
users_list_table_options = {
@@ -22,7 +23,8 @@ users_list_table_options = {
"targets": [0],
"data": null,
"createdCell": function (td, cellData, rowData, row, col) {
- $(td).html('Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.Click here for help. This is required if you enable IP logging.
+ Set the complete folder path where your Plex Server logs are, shortcuts are not recognized.
+ Click here for help. This is required if you enable IP logging (for PMS 0.9.12 and below).
Keep records of all movie, TV show, or music items played from your Plex Media Server.
Keep records of all movie, TV show, or music items played from your Plex Media Server.
Ignore Interval
diff --git a/plexpy/__init__.py b/plexpy/__init__.py
index 9e3e25c6..bbd234da 100644
--- a/plexpy/__init__.py
+++ b/plexpy/__init__.py
@@ -285,10 +285,20 @@ def initialize_scheduler():
hours=12, minutes=0, seconds=0)
schedule_job(pmsconnect.get_server_friendly_name, 'Refresh Plex Server Name',
hours=12, minutes=0, seconds=0)
- schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
- hours=0, minutes=0, seconds=seconds)
- schedule_job(activity_pinger.check_server_response, 'Check for server response',
- hours=0, minutes=0, seconds=seconds)
+
+ if CONFIG.NOTIFY_RECENTLY_ADDED:
+ schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
+ hours=0, minutes=0, seconds=seconds)
+ else:
+ schedule_job(activity_pinger.check_recently_added, 'Check for recently added items',
+ hours=0, minutes=0, seconds=0)
+
+ if CONFIG.MONITOR_REMOTE_ACCESS:
+ schedule_job(activity_pinger.check_server_response, 'Check for server response',
+ hours=0, minutes=0, seconds=seconds)
+ else:
+ schedule_job(activity_pinger.check_server_response, 'Check for server response',
+ hours=0, minutes=0, seconds=0)
# If we're not using websockets then fall back to polling
if not CONFIG.MONITORING_USE_WEBSOCKET or POLLING_FAILOVER:
@@ -406,9 +416,9 @@ def dbcheck():
c_db.execute(
'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, '
'user_id INTEGER DEFAULT NULL UNIQUE, username TEXT NOT NULL UNIQUE, '
- 'friendly_name TEXT, thumb TEXT, email TEXT, is_home_user INTEGER DEFAULT NULL, '
+ 'friendly_name TEXT, thumb TEXT, email TEXT, custom_avatar_url TEXT, is_home_user INTEGER DEFAULT NULL, '
'is_allow_sync INTEGER DEFAULT NULL, is_restricted INTEGER DEFAULT NULL, do_notify INTEGER DEFAULT 1, '
- 'keep_history INTEGER DEFAULT 1, custom_avatar_url TEXT)'
+ 'keep_history INTEGER DEFAULT 1, deleted_user INTEGER DEFAULT 0)'
)
# Upgrade sessions table from earlier versions
@@ -664,6 +674,15 @@ def dbcheck():
'WHERE t1.id = session_history.id) '
)
+ # Upgrade users table from earlier versions
+ try:
+ c_db.execute('SELECT deleted_user from users')
+ except sqlite3.OperationalError:
+ logger.debug(u"Altering database. Updating database table users.")
+ c_db.execute(
+ 'ALTER TABLE users ADD COLUMN deleted_user INTEGER DEFAULT 0'
+ )
+
conn_db.commit()
c_db.close()
diff --git a/plexpy/activity_pinger.py b/plexpy/activity_pinger.py
index aa41d1fe..2eb63bbe 100644
--- a/plexpy/activity_pinger.py
+++ b/plexpy/activity_pinger.py
@@ -33,7 +33,11 @@ def check_active_sessions(ws_request=False):
monitor_process = activity_processor.ActivityProcessor()
# logger.debug(u"PlexPy Monitor :: Checking for active streams.")
+ global int_ping_count
+
if session_list:
+ int_ping_count = 0
+
media_container = session_list['sessions']
# Check our temp table for what we must do with the new streams
@@ -165,6 +169,16 @@ def check_active_sessions(ws_request=False):
else:
logger.debug(u"PlexPy Monitor :: Unable to read session list.")
+ int_ping_count += 1
+ logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \
+ % str(int_ping_count))
+
+ if int_ping_count == 3:
+ # Fire off notifications
+ threading.Thread(target=notification_handler.notify_timeline,
+ kwargs=dict(notify_action='intdown')).start()
+
+
def check_recently_added():
with monitor_lock:
@@ -180,26 +194,29 @@ def check_recently_added():
recently_added = recently_added_list['recently_added']
for item in recently_added:
- if item['media_type'] == 'movie':
- metadata_list = pms_connect.get_metadata_details(item['rating_key'])
- if metadata_list:
- metadata = [metadata_list['metadata']]
- else:
- logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" \
- % str(item['rating_key']))
+ metadata = []
+
+ if 0 < time_threshold - int(item['added_at']) <= time_interval:
+ if item['media_type'] == 'movie':
+ metadata_list = pms_connect.get_metadata_details(item['rating_key'])
+ if metadata_list:
+ metadata = [metadata_list['metadata']]
+ else:
+ logger.error(u"PlexPy Monitor :: Unable to retrieve metadata for rating_key %s" \
+ % str(item['rating_key']))
- else:
- metadata_list = pms_connect.get_metadata_children_details(item['rating_key'])
- if metadata_list:
- metadata = metadata_list['metadata']
else:
- logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key %s" \
- % str(item['rating_key']))
+ metadata_list = pms_connect.get_metadata_children_details(item['rating_key'])
+ if metadata_list:
+ metadata = metadata_list['metadata']
+ else:
+ logger.error(u"PlexPy Monitor :: Unable to retrieve children metadata for rating_key %s" \
+ % str(item['rating_key']))
if metadata:
if not plexpy.CONFIG.NOTIFY_RECENTLY_ADDED_GRANDPARENT:
for item in metadata:
- if 0 < int(item['added_at']) - time_threshold <= time_interval:
+ 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']))
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
@@ -208,7 +225,7 @@ def check_recently_added():
else:
item = max(metadata, key=lambda x:x['added_at'])
- if 0 < int(item['added_at']) - time_threshold <= time_interval:
+ if 0 < time_threshold - int(item['added_at']) <= time_interval:
if item['media_type'] == 'episode' or item['media_type'] == 'track':
metadata_list = pms_connect.get_metadata_details(item['grandparent_rating_key'])
@@ -229,20 +246,10 @@ def check_server_response():
pms_connect = pmsconnect.PmsConnect()
server_response = pms_connect.get_server_response()
- global int_ping_count
global ext_ping_count
- # Check for internal server response
- if not server_response:
- int_ping_count += 1
- logger.warn(u"PlexPy Monitor :: Unable to get an internal response from the server, ping attempt %s." \
- % str(int_ping_count))
- # Reset internal ping counter
- else:
- int_ping_count = 0
-
# Check for remote access
- if server_response and plexpy.CONFIG.MONITOR_REMOTE_ACCESS:
+ if server_response:
mapping_state = server_response['mapping_state']
mapping_error = server_response['mapping_error']
@@ -261,11 +268,6 @@ def check_server_response():
else:
ext_ping_count = 0
- if int_ping_count == 3:
- # Fire off notifications
- threading.Thread(target=notification_handler.notify_timeline,
- kwargs=dict(notify_action='intdown')).start()
-
if ext_ping_count == 3:
# Fire off notifications
threading.Thread(target=notification_handler.notify_timeline,
diff --git a/plexpy/activity_processor.py b/plexpy/activity_processor.py
index bdc2b83d..fb1a2376 100644
--- a/plexpy/activity_processor.py
+++ b/plexpy/activity_processor.py
@@ -286,16 +286,16 @@ class ActivityProcessor(object):
# The logged IP will always be the first match and we don't want localhost entries
if ipv4[0] != '127.0.0.1':
# check if IPv4 mapped IPv6 address (::ffff:xxx.xxx.xxx.xxx)
- if '::ffff:' + ipv4[0] in line:
- logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
- u"and machineIdentifier %s."
- % ('::ffff:' + ipv4[0], rating_key, machine_id))
- return '::ffff:' + ipv4[0]
- else:
- logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
- u"and machineIdentifier %s."
- % (ipv4[0], rating_key, machine_id))
- return ipv4[0]
+ #if '::ffff:' + ipv4[0] in line:
+ # logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
+ # u"and machineIdentifier %s."
+ # % ('::ffff:' + ipv4[0], rating_key, machine_id))
+ # return '::ffff:' + ipv4[0]
+ #else:
+ logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s "
+ u"and machineIdentifier %s."
+ % (ipv4[0], rating_key, machine_id))
+ return ipv4[0]
logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on first pass. "
u"Attempting fallback check in 5 seconds...")
@@ -315,14 +315,14 @@ class ActivityProcessor(object):
if ipv4:
# The logged IP will always be the first match and we don't want localhost entries
if ipv4[0] != '127.0.0.1':
- if '::ffff:' + ipv4[0] in line:
- logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
- ('::ffff:' + ipv4[0], rating_key))
- return '::ffff:' + ipv4[0]
- else:
- logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
- (ipv4[0], rating_key))
- return ipv4[0]
+ #if '::ffff:' + ipv4[0] in line:
+ # logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
+ # ('::ffff:' + ipv4[0], rating_key))
+ # return '::ffff:' + ipv4[0]
+ #else:
+ logger.debug(u"PlexPy ActivityProcessor :: Matched IP address (%s) for stream ratingKey %s." %
+ (ipv4[0], rating_key))
+ return ipv4[0]
logger.debug(u"PlexPy ActivityProcessor :: Unable to find IP address on fallback search. Not logging IP address.")
diff --git a/plexpy/config.py b/plexpy/config.py
index 08054742..491f2081 100644
--- a/plexpy/config.py
+++ b/plexpy/config.py
@@ -151,27 +151,28 @@ _CONFIG_DEFINITIONS = {
'NMA_ON_EXTDOWN': (int, 'NMA', 0),
'NMA_ON_INTDOWN': (int, 'NMA', 0),
'NOTIFY_CONSECUTIVE': (int, 'Monitoring', 1),
+ 'NOTIFY_RECENTLY_ADDED': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_GRANDPARENT': (int, 'Monitoring', 0),
'NOTIFY_RECENTLY_ADDED_DELAY': (int, 'Monitoring', 60),
'NOTIFY_WATCHED_PERCENT': (int, 'Monitoring', 85),
- 'NOTIFY_ON_START_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_START_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) started playing {title}.'),
- 'NOTIFY_ON_STOP_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_STOP_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has stopped {title}.'),
- 'NOTIFY_ON_PAUSE_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_PAUSE_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has paused {title}.'),
- 'NOTIFY_ON_RESUME_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_RESUME_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has resumed {title}.'),
- 'NOTIFY_ON_BUFFER_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_BUFFER_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) is buffering {title}.'),
- 'NOTIFY_ON_WATCHED_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_WATCHED_BODY_TEXT': (str, 'Monitoring', '{user} ({player}) has watched {title}.'),
- 'NOTIFY_ON_CREATED_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_CREATED_BODY_TEXT': (str, 'Monitoring', '{title} was recently added to Plex.'),
- 'NOTIFY_ON_EXTDOWN_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_EXTDOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server remote access is down.'),
- 'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (str, 'Monitoring', 'PlexPy ({server_name})'),
- 'NOTIFY_ON_INTDOWN_BODY_TEXT': (str, 'Monitoring', 'The Plex Media Server is down.'),
+ 'NOTIFY_ON_START_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_START_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) started playing {title}.'),
+ 'NOTIFY_ON_STOP_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_STOP_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has stopped {title}.'),
+ 'NOTIFY_ON_PAUSE_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_PAUSE_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has paused {title}.'),
+ 'NOTIFY_ON_RESUME_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_RESUME_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has resumed {title}.'),
+ 'NOTIFY_ON_BUFFER_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_BUFFER_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) is buffering {title}.'),
+ 'NOTIFY_ON_WATCHED_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_WATCHED_BODY_TEXT': (unicode, 'Monitoring', '{user} ({player}) has watched {title}.'),
+ 'NOTIFY_ON_CREATED_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_CREATED_BODY_TEXT': (unicode, 'Monitoring', '{title} was recently added to Plex.'),
+ 'NOTIFY_ON_EXTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_EXTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server remote access is down.'),
+ 'NOTIFY_ON_INTDOWN_SUBJECT_TEXT': (unicode, 'Monitoring', 'PlexPy ({server_name})'),
+ 'NOTIFY_ON_INTDOWN_BODY_TEXT': (unicode, 'Monitoring', 'The Plex Media Server is down.'),
'OSX_NOTIFY_APP': (str, 'OSX_Notify', '/Applications/PlexPy'),
'OSX_NOTIFY_ENABLED': (int, 'OSX_Notify', 0),
'OSX_NOTIFY_ON_PLAY': (int, 'OSX_Notify', 0),
@@ -250,7 +251,7 @@ _CONFIG_DEFINITIONS = {
'REFRESH_USERS_ON_STARTUP': (int, 'Monitoring', 1),
'TELEGRAM_BOT_TOKEN': (str, 'Telegram', ''),
'TELEGRAM_ENABLED': (int, 'Telegram', 0),
- 'TELEGRAM_CHAT_ID': (int, 'Telegram', 0),
+ 'TELEGRAM_CHAT_ID': (str, 'Telegram', ''),
'TELEGRAM_ON_PLAY': (int, 'Telegram', 0),
'TELEGRAM_ON_STOP': (int, 'Telegram', 0),
'TELEGRAM_ON_PAUSE': (int, 'Telegram', 0),
diff --git a/plexpy/datafactory.py b/plexpy/datafactory.py
index 948b8fa4..ca218bf3 100644
--- a/plexpy/datafactory.py
+++ b/plexpy/datafactory.py
@@ -108,6 +108,9 @@ class DataFactory(object):
# Rename Mystery platform names
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
+ # Sanitize player name
+ player = helpers.sanitize(item["player"])
+
row = {"reference_id": item["reference_id"],
"id": item["id"],
"date": item["date"],
@@ -119,7 +122,7 @@ class DataFactory(object):
"user": item["user"],
"friendly_name": item["friendly_name"],
"platform": platform,
- "player": item["player"],
+ "player": player,
"ip_address": item["ip_address"],
"media_type": item["media_type"],
"rating_key": item["rating_key"],
@@ -545,7 +548,7 @@ class DataFactory(object):
'session_history_metadata.thumb, ' \
'session_history_metadata.grandparent_thumb, ' \
'MAX(session_history.started) as last_watch, ' \
- 'session_history.player as platform, ' \
+ 'session_history.player, ' \
'((CASE WHEN session_history.view_offset IS NULL THEN 0.1 ELSE \
session_history.view_offset * 1.0 END) / \
(CASE WHEN session_history_metadata.duration IS NULL THEN 1.0 ELSE \
@@ -571,6 +574,9 @@ class DataFactory(object):
thumb = item[7]
else:
thumb = item[8]
+
+ # Sanitize player name
+ player = helpers.sanitize(item["player"])
row = {'row_id': item[0],
'user': item[1],
@@ -582,7 +588,7 @@ class DataFactory(object):
'thumb': thumb,
'grandparent_thumb': item[8],
'last_watch': item[9],
- 'platform_type': item[10],
+ 'player': player,
}
last_watched.append(row)
@@ -800,6 +806,40 @@ class DataFactory(object):
else:
return 'Unable to delete items. Input user_id not valid.'
+ def delete_user(self, user_id=None):
+ monitor_db = database.MonitorDatabase()
+
+ if user_id.isdigit():
+ self.delete_all_user_history(user_id)
+ logger.info(u"PlexPy DataFactory :: Deleting user with id %s from database." % user_id)
+ monitor_db.action('UPDATE users SET deleted_user = 1 WHERE user_id = ?', [user_id])
+ monitor_db.action('UPDATE users SET keep_history = 0 WHERE user_id = ?', [user_id])
+ monitor_db.action('UPDATE users SET do_notify = 0 WHERE user_id = ?', [user_id])
+
+ return 'Deleted user with id %s.' % user_id
+ else:
+ return 'Unable to delete user. Input user_id not valid.'
+
+ def undelete_user(self, user_id=None, username=None):
+ monitor_db = database.MonitorDatabase()
+
+ if user_id and user_id.isdigit():
+ logger.info(u"PlexPy DataFactory :: Re-adding user with id %s to database." % user_id)
+ monitor_db.action('UPDATE users SET deleted_user = 0 WHERE user_id = ?', [user_id])
+ monitor_db.action('UPDATE users SET keep_history = 1 WHERE user_id = ?', [user_id])
+ monitor_db.action('UPDATE users SET do_notify = 1 WHERE user_id = ?', [user_id])
+
+ return 'Re-added user with id %s.' % user_id
+ elif username:
+ logger.info(u"PlexPy DataFactory :: Re-adding user with username %s to database." % username)
+ monitor_db.action('UPDATE users SET deleted_user = 0 WHERE username = ?', [username])
+ monitor_db.action('UPDATE users SET keep_history = 1 WHERE username = ?', [username])
+ monitor_db.action('UPDATE users SET do_notify = 1 WHERE username = ?', [username])
+
+ return 'Re-added user with username %s.' % username
+ else:
+ return 'Unable to re-add user. Input user_id or username not valid.'
+
def get_search_query(self, rating_key=''):
monitor_db = database.MonitorDatabase()
diff --git a/plexpy/helpers.py b/plexpy/helpers.py
index e5f5cc42..64b796db 100644
--- a/plexpy/helpers.py
+++ b/plexpy/helpers.py
@@ -430,3 +430,9 @@ def process_json_kwargs(json_kwargs):
params = json.loads(json_kwargs)
return params
+
+def sanitize(string):
+ if string:
+ return str(string).replace('<','<').replace('>','>')
+ else:
+ return ''
\ No newline at end of file
diff --git a/plexpy/notification_handler.py b/plexpy/notification_handler.py
index 29d65097..2f91a5df 100644
--- a/plexpy/notification_handler.py
+++ b/plexpy/notification_handler.py
@@ -315,13 +315,13 @@ def build_notify_text(session=None, timeline=None, state=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('
[^>]+. |
[^>]+. ', re.IGNORECASE)
+ pattern = re.compile('
[^>]+. |
[^>]+. ', 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('
[^>]+. |
[^>]+. ', re.IGNORECASE)
+ pattern = re.compile('
[^>]+. |
[^>]+. ', 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('
[^>]+. |
[^>]+. ', re.IGNORECASE)
+ pattern = re.compile('
[^>]+. |
[^>]+. ', re.IGNORECASE|re.DOTALL)
else:
pattern = None
diff --git a/plexpy/notifiers.py b/plexpy/notifiers.py
index c072cefc..d8124917 100644
--- a/plexpy/notifiers.py
+++ b/plexpy/notifiers.py
@@ -505,7 +505,7 @@ class PROWL(object):
data = {'apikey': plexpy.CONFIG.PROWL_KEYS,
'application': 'PlexPy',
- 'event': event,
+ 'event': event.encode("utf-8"),
'description': message.encode("utf-8"),
'priority': plexpy.CONFIG.PROWL_PRIORITY}
@@ -896,9 +896,9 @@ class PUSHALOT(object):
response = http_handler.getresponse()
request_status = response.status
- logger.debug(u"Pushalot response status: %r" % request_status)
- logger.debug(u"Pushalot response headers: %r" % response.getheaders())
- logger.debug(u"Pushalot response body: %r" % response.read())
+ #logger.debug(u"Pushalot response status: %r" % request_status)
+ #logger.debug(u"Pushalot response headers: %r" % response.getheaders())
+ #logger.debug(u"Pushalot response body: %r" % response.read())
if request_status == 200:
logger.info(u"Pushalot notifications sent.")
@@ -1526,7 +1526,7 @@ class TELEGRAM(object):
{'label': 'Telegram Chat ID',
'value': self.chat_id,
'name': 'telegram_chat_id',
- 'description': 'Your Telegram Chat ID or Group ID. Contact
@myidbot on Telegram to get an ID.',
+ 'description': 'Your Telegram Chat ID, Group ID, or channel username. Contact
@myidbot on Telegram to get an ID.',
'input_type': 'text'
}
]
diff --git a/plexpy/pmsconnect.py b/plexpy/pmsconnect.py
index a313813a..d52ff65a 100644
--- a/plexpy/pmsconnect.py
+++ b/plexpy/pmsconnect.py
@@ -803,7 +803,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
- 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address'),
+ 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
@@ -924,7 +924,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
- 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address'),
+ 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
@@ -981,7 +981,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
- 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address'),
+ 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
@@ -1038,7 +1038,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
- 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address'),
+ 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
@@ -1128,7 +1128,7 @@ class PmsConnect(object):
'user_id': user_details['user_id'],
'friendly_name': user_details['friendly_name'],
'user_thumb': user_details['thumb'],
- 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address'),
+ 'ip_address': helpers.get_xml_attr(session.getElementsByTagName('Player')[0], 'address').split(':')[-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,
diff --git a/plexpy/users.py b/plexpy/users.py
index 1c0c23a7..e932e6c8 100644
--- a/plexpy/users.py
+++ b/plexpy/users.py
@@ -24,6 +24,8 @@ class Users(object):
def get_user_list(self, kwargs=None):
data_tables = datatables.DataTables()
+ custom_where = ['users.deleted_user', 0]
+
columns = ['session_history.id',
'users.user_id as user_id',
'users.custom_avatar_url as user_thumb',
@@ -48,7 +50,7 @@ class Users(object):
try:
query = data_tables.ssp_query(table_name='users',
columns=columns,
- custom_where=[],
+ custom_where=[custom_where],
group_by=['users.user_id'],
join_types=['LEFT OUTER JOIN',
'LEFT OUTER JOIN',
@@ -87,13 +89,16 @@ class Users(object):
# Rename Mystery platform names
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
+ # Sanitize player name
+ player = helpers.sanitize(item["player"])
+
row = {"id": item['id'],
"plays": item['plays'],
"last_seen": item['last_seen'],
"friendly_name": item['friendly_name'],
"ip_address": item['ip_address'],
"platform": platform,
- "player": item['player'],
+ "player": player,
"last_watched": item['last_watched'],
"thumb": thumb,
"media_type": item['media_type'],
@@ -178,12 +183,15 @@ class Users(object):
# Rename Mystery platform names
platform = common.PLATFORM_NAME_OVERRIDES.get(item["platform"], item["platform"])
+ # Sanitize player name
+ player = helpers.sanitize(item["player"])
+
row = {"id": item['id'],
"last_seen": item['last_seen'],
"ip_address": item['ip_address'],
"play_count": item['play_count'],
"platform": platform,
- "player": item['player'],
+ "player": player,
"last_watched": item['last_watched'],
"thumb": thumb,
"media_type": item['media_type'],
@@ -529,7 +537,10 @@ class Users(object):
# Rename Mystery platform names
platform_type = common.PLATFORM_NAME_OVERRIDES.get(item[2], item[2])
- row = {'player_name': item[0],
+ # Sanitize player name
+ player = helpers.sanitize(item[0])
+
+ row = {'player_name': player,
'platform_type': platform_type,
'total_plays': item[1],
'result_id': result_id
diff --git a/plexpy/version.py b/plexpy/version.py
index dcea93a3..e5f27395 100644
--- a/plexpy/version.py
+++ b/plexpy/version.py
@@ -1,2 +1,2 @@
PLEXPY_VERSION = "master"
-PLEXPY_RELEASE_VERSION = "1.2.7"
+PLEXPY_RELEASE_VERSION = "1.2.8"
diff --git a/plexpy/webserve.py b/plexpy/webserve.py
index 62b16cc5..a3b237e8 100644
--- a/plexpy/webserve.py
+++ b/plexpy/webserve.py
@@ -1,7 +1,4 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-
-# This file is part of PlexPy.
+# This file is part of PlexPy.
#
# PlexPy is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -16,7 +13,7 @@
# You should have received a copy of the GNU General Public License
# along with PlexPy. If not, see
.
-from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users
+from plexpy import logger, notifiers, plextv, pmsconnect, common, log_reader, datafactory, graphs, users, helpers
from plexpy.helpers import checked, radio
from mako.lookup import TemplateLookup
@@ -448,6 +445,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_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,
"notify_watched_percent": plexpy.CONFIG.NOTIFY_WATCHED_PERCENT,
@@ -494,7 +492,7 @@ class WebInterface(object):
"tv_notify_on_pause", "movie_notify_on_pause", "music_notify_on_pause", "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_grandparent", "monitor_remote_access"
+ "notify_recently_added", "notify_recently_added_grandparent", "monitor_remote_access"
]
for checked_config in checked_configs:
if checked_config not in kwargs:
@@ -519,6 +517,14 @@ class WebInterface(object):
if (kwargs['monitoring_interval'] != str(plexpy.CONFIG.MONITORING_INTERVAL)) or \
(kwargs['refresh_users_interval'] != str(plexpy.CONFIG.REFRESH_USERS_INTERVAL)):
reschedule = True
+
+ if 'notify_recently_added' in kwargs and \
+ (kwargs['notify_recently_added'] != plexpy.CONFIG.NOTIFY_RECENTLY_ADDED):
+ reschedule = True
+
+ if 'monitor_remote_access' in kwargs and \
+ (kwargs['monitor_remote_access'] != plexpy.CONFIG.MONITOR_REMOTE_ACCESS):
+ reschedule = True
if 'pms_ip' in kwargs:
if kwargs['pms_ip'] != plexpy.CONFIG.PMS_IP:
@@ -729,6 +735,9 @@ class WebInterface(object):
if not session['ip_address']:
ip_address = data_factory.get_session_ip(session['session_key'])
session['ip_address'] = ip_address
+ # Sanitize player name
+ session['player'] = helpers.sanitize(session['player'])
+
except:
return serve_template(templatename="current_activity.html", data=None)
@@ -1397,6 +1406,40 @@ class WebInterface(object):
cherrypy.response.headers['Content-type'] = 'application/json'
return json.dumps({'message': 'no data received'})
+ @cherrypy.expose
+ def delete_user(self, user_id, **kwargs):
+ data_factory = datafactory.DataFactory()
+
+ if user_id:
+ delete_row = data_factory.delete_user(user_id=user_id)
+
+ if delete_row:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': delete_row})
+ else:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': 'no data received'})
+
+ @cherrypy.expose
+ def undelete_user(self, user_id=None, username=None, **kwargs):
+ data_factory = datafactory.DataFactory()
+
+ if user_id:
+ delete_row = data_factory.undelete_user(user_id=user_id)
+
+ if delete_row:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': delete_row})
+ elif username:
+ delete_row = data_factory.undelete_user(username=username)
+
+ if delete_row:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': delete_row})
+ else:
+ cherrypy.response.headers['Content-type'] = 'application/json'
+ return json.dumps({'message': 'no data received'})
+
@cherrypy.expose
def search(self, query=''):