mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-08-19 21:03:21 -07:00
Rename Tautulli android app to remote app
This commit is contained in:
parent
bde2372d90
commit
741f3d836a
7 changed files with 334 additions and 208 deletions
|
@ -1,4 +1,4 @@
|
|||
# This file is part of Tautulli.
|
||||
# This file is part of Tautulli.
|
||||
#
|
||||
# Tautulli is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
|
@ -2342,7 +2342,9 @@ def dbcheck():
|
|||
|
||||
# Rename notifiers in the database
|
||||
result = c_db.execute('SELECT agent_label FROM notifiers '
|
||||
'WHERE agent_label = "XBMC" OR agent_label = "OSX Notify"').fetchone()
|
||||
'WHERE agent_label = "XBMC" '
|
||||
'OR agent_label = "OSX Notify" '
|
||||
'OR agent_name = "androidapp"').fetchone()
|
||||
if result:
|
||||
logger.debug("Altering database. Renaming notifiers.")
|
||||
c_db.execute(
|
||||
|
@ -2351,6 +2353,10 @@ def dbcheck():
|
|||
c_db.execute(
|
||||
'UPDATE notifiers SET agent_label = "macOS Notification Center" WHERE agent_label = "OSX Notify"'
|
||||
)
|
||||
c_db.execute(
|
||||
'UPDATE notifiers SET agent_name = "remoteapp", agent_label = "Tautulli Remote App" '
|
||||
'WHERE agent_name = "androidapp"'
|
||||
)
|
||||
|
||||
# Upgrade exports table from earlier versions
|
||||
try:
|
||||
|
|
|
@ -98,7 +98,7 @@ AGENT_IDS = {'growl': 0,
|
|||
'browser': 17,
|
||||
'join': 18,
|
||||
'discord': 20,
|
||||
'androidapp': 21,
|
||||
'remoteapp': 21,
|
||||
'groupme': 22,
|
||||
'mqtt': 23,
|
||||
'zapier': 24,
|
||||
|
@ -110,10 +110,10 @@ DEFAULT_CUSTOM_CONDITIONS = [{'parameter': '', 'operator': '', 'value': ''}]
|
|||
|
||||
|
||||
def available_notification_agents():
|
||||
agents = [{'label': 'Tautulli Remote Android App',
|
||||
'name': 'androidapp',
|
||||
'id': AGENT_IDS['androidapp'],
|
||||
'class': ANDROIDAPP,
|
||||
agents = [{'label': 'Tautulli Remote App',
|
||||
'name': 'remoteapp',
|
||||
'id': AGENT_IDS['remoteapp'],
|
||||
'class': TAUTULLIREMOTEAPP,
|
||||
'action_types': ('all',)
|
||||
},
|
||||
{'label': 'Boxcar',
|
||||
|
@ -882,188 +882,6 @@ class Notifier(object):
|
|||
return config_options
|
||||
|
||||
|
||||
class ANDROIDAPP(Notifier):
|
||||
"""
|
||||
Tautulli Remote Android app notifications
|
||||
"""
|
||||
NAME = 'Tautulli Remote Android App'
|
||||
_DEFAULT_CONFIG = {'device_id': '',
|
||||
'priority': 3,
|
||||
'notification_type': 0
|
||||
}
|
||||
|
||||
def agent_notify(self, subject='', body='', action='', notification_id=None, **kwargs):
|
||||
# Check mobile device is still registered
|
||||
device = mobile_app.get_mobile_devices(device_id=self.config['device_id'])
|
||||
if not device:
|
||||
logger.warn("Tautulli Notifiers :: Unable to send Android app notification: device not registered.")
|
||||
return
|
||||
else:
|
||||
device = device[0]
|
||||
|
||||
pretty_metadata = PrettyMetadata(kwargs.get('parameters'))
|
||||
|
||||
plaintext_data = {'notification_id': notification_id,
|
||||
'subject': subject,
|
||||
'body': body,
|
||||
'action': action,
|
||||
'priority': self.config['priority'],
|
||||
'notification_type': self.config['notification_type'],
|
||||
'session_key': pretty_metadata.parameters.get('session_key', ''),
|
||||
'session_id': pretty_metadata.parameters.get('session_id', ''),
|
||||
'user_id': pretty_metadata.parameters.get('user_id', ''),
|
||||
'rating_key': pretty_metadata.parameters.get('rating_key', ''),
|
||||
'poster_thumb': pretty_metadata.parameters.get('poster_thumb', '')}
|
||||
|
||||
#logger.debug("Plaintext data: {}".format(plaintext_data))
|
||||
|
||||
if CRYPTODOME:
|
||||
# Key generation
|
||||
salt = get_random_bytes(16)
|
||||
passphrase = device['device_token']
|
||||
key_length = 32 # AES256
|
||||
iterations = 1000
|
||||
key = PBKDF2(passphrase, salt, dkLen=key_length, count=iterations,
|
||||
prf=lambda p, s: HMAC.new(p, s, SHA1).digest())
|
||||
|
||||
#logger.debug("Encryption key (base64): {}".format(base64.b64encode(key)))
|
||||
|
||||
# Encrypt using AES GCM
|
||||
nonce = get_random_bytes(16)
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce)
|
||||
encrypted_data, gcm_tag = cipher.encrypt_and_digest(json.dumps(plaintext_data).encode('utf-8'))
|
||||
encrypted_data += gcm_tag
|
||||
|
||||
#logger.debug("Encrypted data (base64): {}".format(base64.b64encode(encrypted_data)))
|
||||
#logger.debug("GCM tag (base64): {}".format(base64.b64encode(gcm_tag)))
|
||||
#logger.debug("Nonce (base64): {}".format(base64.b64encode(nonce)))
|
||||
#logger.debug("Salt (base64): {}".format(base64.b64encode(salt)))
|
||||
|
||||
payload = {'app_id': mobile_app._ONESIGNAL_APP_ID,
|
||||
'include_player_ids': [device['onesignal_id']],
|
||||
'contents': {'en': 'Tautulli Notification'},
|
||||
'data': {'encrypted': True,
|
||||
'cipher_text': base64.b64encode(encrypted_data),
|
||||
'nonce': base64.b64encode(nonce),
|
||||
'salt': base64.b64encode(salt),
|
||||
'server_id': plexpy.CONFIG.PMS_UUID}
|
||||
}
|
||||
else:
|
||||
logger.warn("Tautulli Notifiers :: PyCryptodome library is missing. "
|
||||
"Android app notifications will be sent unecrypted. "
|
||||
"Install the library to encrypt the notifications.")
|
||||
|
||||
payload = {'app_id': mobile_app._ONESIGNAL_APP_ID,
|
||||
'include_player_ids': [device['onesignal_id']],
|
||||
'contents': {'en': 'Tautulli Notification'},
|
||||
'data': {'encrypted': False,
|
||||
'plain_text': plaintext_data,
|
||||
'server_id': plexpy.CONFIG.PMS_UUID}
|
||||
}
|
||||
|
||||
#logger.debug("OneSignal payload: {}".format(payload))
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
return self.make_request("https://onesignal.com/api/v1/notifications", headers=headers, json=payload)
|
||||
|
||||
def get_devices(self):
|
||||
db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
query = 'SELECT * FROM mobile_devices WHERE official = 1 ' \
|
||||
'AND onesignal_id IS NOT NULL AND onesignal_id != ""'
|
||||
result = db.select(query=query)
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Notifiers :: Unable to retrieve Android app devices list: %s." % e)
|
||||
return {'': ''}
|
||||
|
||||
devices = {}
|
||||
for device in result:
|
||||
if device['friendly_name']:
|
||||
devices[device['device_id']] = device['friendly_name']
|
||||
else:
|
||||
devices[device['device_id']] = device['device_name']
|
||||
|
||||
return devices
|
||||
|
||||
def _return_config_options(self):
|
||||
config_option = []
|
||||
|
||||
if not CRYPTODOME:
|
||||
config_option.append({
|
||||
'label': 'Warning',
|
||||
'description': '<strong>The PyCryptodome library is missing. '
|
||||
'The content of your notifications will be sent unencrypted!</strong><br>'
|
||||
'Please install the library to encrypt the notification contents. '
|
||||
'Instructions can be found in the '
|
||||
'<a href="' + helpers.anon_url(
|
||||
'https://github.com/%s/%s/wiki/Frequently-Asked-Questions#notifications-pycryptodome'
|
||||
% (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO)) + '" target="_blank">FAQ</a>.' ,
|
||||
'input_type': 'help'
|
||||
})
|
||||
else:
|
||||
config_option.append({
|
||||
'label': 'Note',
|
||||
'description': 'The PyCryptodome library was found. '
|
||||
'The content of your notifications will be sent encrypted!',
|
||||
'input_type': 'help'
|
||||
})
|
||||
|
||||
config_option[-1]['description'] += '<br><br>Notifications are sent using the ' \
|
||||
'<a href="' + helpers.anon_url('https://onesignal.com') + '" target="_blank">' \
|
||||
'OneSignal</a>. Some user data is collected and cannot be encrypted. ' \
|
||||
'Please read the <a href="' + helpers.anon_url(
|
||||
'https://onesignal.com/privacy_policy') + '" target="_blank">' \
|
||||
'OneSignal Privacy Policy</a> for more details.'
|
||||
|
||||
devices = self.get_devices()
|
||||
|
||||
if not devices:
|
||||
config_option.append({
|
||||
'label': 'Device',
|
||||
'description': 'No mobile devices registered with OneSignal. '
|
||||
'<a data-tab-destination="android_app" data-toggle="tab" data-dismiss="modal">'
|
||||
'Get the Android App</a> and register a device.<br>'
|
||||
'Note: Only devices registered with a valid OneSignal ID will appear in the list.',
|
||||
'input_type': 'help'
|
||||
})
|
||||
else:
|
||||
config_option.append({
|
||||
'label': 'Device',
|
||||
'value': self.config['device_id'],
|
||||
'name': 'androidapp_device_id',
|
||||
'description': 'Select your mobile device or '
|
||||
'<a data-tab-destination="android_app" data-toggle="tab" data-dismiss="modal">'
|
||||
'register a new device</a> with Tautulli.<br>'
|
||||
'Note: Only devices registered with a valid OneSignal ID will appear in the list.',
|
||||
'input_type': 'select',
|
||||
'select_options': devices
|
||||
})
|
||||
|
||||
config_option.append({
|
||||
'label': 'Priority',
|
||||
'value': self.config['priority'],
|
||||
'name': 'androidapp_priority',
|
||||
'description': 'Set the notification priority.',
|
||||
'input_type': 'select',
|
||||
'select_options': {1: 'Minimum', 2: 'Low', 3: 'Normal', 4: 'High'}
|
||||
})
|
||||
config_option.append({
|
||||
'label': 'Notification Image Type',
|
||||
'value': self.config['notification_type'],
|
||||
'name': 'androidapp_notification_type',
|
||||
'description': 'Set the notification image type.',
|
||||
'input_type': 'select',
|
||||
'select_options': {0: 'No notification image',
|
||||
1: 'Small image (Expandable text)',
|
||||
2: 'Large image (Non-expandable text)'
|
||||
}
|
||||
})
|
||||
|
||||
return config_option
|
||||
|
||||
|
||||
class BOXCAR(Notifier):
|
||||
"""
|
||||
Boxcar notifications
|
||||
|
@ -3399,6 +3217,188 @@ class SLACK(Notifier):
|
|||
return config_option
|
||||
|
||||
|
||||
class TAUTULLIREMOTEAPP(Notifier):
|
||||
"""
|
||||
Tautulli Remote app notifications
|
||||
"""
|
||||
NAME = 'Tautulli Remote App'
|
||||
_DEFAULT_CONFIG = {'device_id': '',
|
||||
'priority': 3,
|
||||
'notification_type': 0
|
||||
}
|
||||
|
||||
def agent_notify(self, subject='', body='', action='', notification_id=None, **kwargs):
|
||||
# Check mobile device is still registered
|
||||
device = mobile_app.get_mobile_devices(device_id=self.config['device_id'])
|
||||
if not device:
|
||||
logger.warn("Tautulli Notifiers :: Unable to send Tautulli Remote app notification: device not registered.")
|
||||
return
|
||||
else:
|
||||
device = device[0]
|
||||
|
||||
pretty_metadata = PrettyMetadata(kwargs.get('parameters'))
|
||||
|
||||
plaintext_data = {'notification_id': notification_id,
|
||||
'subject': subject,
|
||||
'body': body,
|
||||
'action': action,
|
||||
'priority': self.config['priority'],
|
||||
'notification_type': self.config['notification_type'],
|
||||
'session_key': pretty_metadata.parameters.get('session_key', ''),
|
||||
'session_id': pretty_metadata.parameters.get('session_id', ''),
|
||||
'user_id': pretty_metadata.parameters.get('user_id', ''),
|
||||
'rating_key': pretty_metadata.parameters.get('rating_key', ''),
|
||||
'poster_thumb': pretty_metadata.parameters.get('poster_thumb', '')}
|
||||
|
||||
#logger.debug("Plaintext data: {}".format(plaintext_data))
|
||||
|
||||
if CRYPTODOME:
|
||||
# Key generation
|
||||
salt = get_random_bytes(16)
|
||||
passphrase = device['device_token']
|
||||
key_length = 32 # AES256
|
||||
iterations = 1000
|
||||
key = PBKDF2(passphrase, salt, dkLen=key_length, count=iterations,
|
||||
prf=lambda p, s: HMAC.new(p, s, SHA1).digest())
|
||||
|
||||
#logger.debug("Encryption key (base64): {}".format(base64.b64encode(key)))
|
||||
|
||||
# Encrypt using AES GCM
|
||||
nonce = get_random_bytes(16)
|
||||
cipher = AES.new(key, AES.MODE_GCM, nonce)
|
||||
encrypted_data, gcm_tag = cipher.encrypt_and_digest(json.dumps(plaintext_data).encode('utf-8'))
|
||||
encrypted_data += gcm_tag
|
||||
|
||||
#logger.debug("Encrypted data (base64): {}".format(base64.b64encode(encrypted_data)))
|
||||
#logger.debug("GCM tag (base64): {}".format(base64.b64encode(gcm_tag)))
|
||||
#logger.debug("Nonce (base64): {}".format(base64.b64encode(nonce)))
|
||||
#logger.debug("Salt (base64): {}".format(base64.b64encode(salt)))
|
||||
|
||||
payload = {'app_id': mobile_app._ONESIGNAL_APP_ID,
|
||||
'include_player_ids': [device['onesignal_id']],
|
||||
'contents': {'en': 'Tautulli Notification'},
|
||||
'data': {'encrypted': True,
|
||||
'cipher_text': base64.b64encode(encrypted_data),
|
||||
'nonce': base64.b64encode(nonce),
|
||||
'salt': base64.b64encode(salt),
|
||||
'server_id': plexpy.CONFIG.PMS_UUID}
|
||||
}
|
||||
else:
|
||||
logger.warn("Tautulli Notifiers :: PyCryptodome library is missing. "
|
||||
"Tautulli Remote app notifications will be sent unecrypted. "
|
||||
"Install the library to encrypt the notifications.")
|
||||
|
||||
payload = {'app_id': mobile_app._ONESIGNAL_APP_ID,
|
||||
'include_player_ids': [device['onesignal_id']],
|
||||
'contents': {'en': 'Tautulli Notification'},
|
||||
'data': {'encrypted': False,
|
||||
'plain_text': plaintext_data,
|
||||
'server_id': plexpy.CONFIG.PMS_UUID}
|
||||
}
|
||||
|
||||
#logger.debug("OneSignal payload: {}".format(payload))
|
||||
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
|
||||
return self.make_request("https://onesignal.com/api/v1/notifications", headers=headers, json=payload)
|
||||
|
||||
def get_devices(self):
|
||||
db = database.MonitorDatabase()
|
||||
|
||||
try:
|
||||
query = 'SELECT * FROM mobile_devices WHERE official = 1 ' \
|
||||
'AND onesignal_id IS NOT NULL AND onesignal_id != ""'
|
||||
result = db.select(query=query)
|
||||
except Exception as e:
|
||||
logger.warn("Tautulli Notifiers :: Unable to retrieve Tautulli Remote app devices list: %s." % e)
|
||||
return {'': ''}
|
||||
|
||||
devices = {}
|
||||
for device in result:
|
||||
if device['friendly_name']:
|
||||
devices[device['device_id']] = device['friendly_name']
|
||||
else:
|
||||
devices[device['device_id']] = device['device_name']
|
||||
|
||||
return devices
|
||||
|
||||
def _return_config_options(self):
|
||||
config_option = []
|
||||
|
||||
if not CRYPTODOME:
|
||||
config_option.append({
|
||||
'label': 'Warning',
|
||||
'description': '<strong>The PyCryptodome library is missing. '
|
||||
'The content of your notifications will be sent unencrypted!</strong><br>'
|
||||
'Please install the library to encrypt the notification contents. '
|
||||
'Instructions can be found in the '
|
||||
'<a href="' + helpers.anon_url(
|
||||
'https://github.com/%s/%s/wiki/Frequently-Asked-Questions#notifications-pycryptodome'
|
||||
% (plexpy.CONFIG.GIT_USER, plexpy.CONFIG.GIT_REPO)) + '" target="_blank">FAQ</a>.' ,
|
||||
'input_type': 'help'
|
||||
})
|
||||
else:
|
||||
config_option.append({
|
||||
'label': 'Note',
|
||||
'description': 'The PyCryptodome library was found. '
|
||||
'The content of your notifications will be sent encrypted!',
|
||||
'input_type': 'help'
|
||||
})
|
||||
|
||||
config_option[-1]['description'] += '<br><br>Notifications are sent using the ' \
|
||||
'<a href="' + helpers.anon_url('https://onesignal.com') + '" target="_blank">' \
|
||||
'OneSignal</a>. Some user data is collected and cannot be encrypted. ' \
|
||||
'Please read the <a href="' + helpers.anon_url(
|
||||
'https://onesignal.com/privacy_policy') + '" target="_blank">' \
|
||||
'OneSignal Privacy Policy</a> for more details.'
|
||||
|
||||
devices = self.get_devices()
|
||||
|
||||
if not devices:
|
||||
config_option.append({
|
||||
'label': 'Device',
|
||||
'description': 'No mobile devices registered with OneSignal. '
|
||||
'<a data-tab-destination="remote_app" data-toggle="tab" data-dismiss="modal">'
|
||||
'Get the Tautulli Remote App</a> and register a device.<br>'
|
||||
'Note: Only devices registered with a valid OneSignal ID will appear in the list.',
|
||||
'input_type': 'help'
|
||||
})
|
||||
else:
|
||||
config_option.append({
|
||||
'label': 'Device',
|
||||
'value': self.config['device_id'],
|
||||
'name': 'remoteapp_device_id',
|
||||
'description': 'Select your mobile device or '
|
||||
'<a data-tab-destination="remote_app" data-toggle="tab" data-dismiss="modal">'
|
||||
'register a new device</a> with Tautulli.<br>'
|
||||
'Note: Only devices registered with a valid OneSignal ID will appear in the list.',
|
||||
'input_type': 'select',
|
||||
'select_options': devices
|
||||
})
|
||||
|
||||
config_option.append({
|
||||
'label': 'Priority',
|
||||
'value': self.config['priority'],
|
||||
'name': 'remoteapp_priority',
|
||||
'description': 'Set the notification priority.',
|
||||
'input_type': 'select',
|
||||
'select_options': {1: 'Minimum', 2: 'Low', 3: 'Normal', 4: 'High'}
|
||||
})
|
||||
config_option.append({
|
||||
'label': 'Notification Image Type',
|
||||
'value': self.config['notification_type'],
|
||||
'name': 'remoteapp_notification_type',
|
||||
'description': 'Set the notification image type.',
|
||||
'input_type': 'select',
|
||||
'select_options': {0: 'No notification image',
|
||||
1: 'Small image (Expandable text)',
|
||||
2: 'Large image (Non-expandable text)'
|
||||
}
|
||||
})
|
||||
|
||||
return config_option
|
||||
|
||||
|
||||
class TELEGRAM(Notifier):
|
||||
"""
|
||||
Telegram notifications
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue