class Notifier(object): """Boxcar2 class.""" def __init__(self): """Initialize the class.""" self.session = MedusaSession() self.url = 'https://new.boxcar.io/api/notifications' def test_notify(self, accesstoken, title='Medusa: Test'): """Test the notify.""" return self._send_boxcar2('This is a test notification from Medusa', title, accesstoken) def _send_boxcar2(self, msg, title, accesstoken): """ Send a boxcar2 notification to the address provided. msg: The message to send title: The title of the message accesstoken: to send to this device return: True if the message succeeded, False otherwise """ # http://blog.boxcar.io/post/93211745502/boxcar-api-update-boxcar-api-update-icon-and post_data = { 'user_credentials': accesstoken, 'notification[title]': 'Medusa: {}: {}'.format(title, msg), 'notification[long_message]': msg, 'notification[sound]': 'notifier-2', 'notification[source_name]': 'Medusa', 'notification[icon_url]': app.LOGO_URL } # TODO: SESSION: Check if this needs exception handling. response = self.session.post(self.url, data=post_data, timeout=60).json() if not response: log.error('Boxcar2 notification failed.') return False log.debug('Boxcar2 notification successful.') return True def notify_snatch(self, title, message): """Send the snatch message.""" if app.BOXCAR2_NOTIFY_ONSNATCH: self._notify_boxcar2(title, message) def notify_download(self, ep_obj, title=common.notifyStrings[common.NOTIFY_DOWNLOAD]): """Send the download message.""" if app.BOXCAR2_NOTIFY_ONDOWNLOAD: self._notify_boxcar2(title, ep_obj.pretty_name_with_quality()) def notify_subtitle_download( self, ep_obj, lang, title=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD]): """Send the subtitle download message.""" if app.BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD: self._notify_boxcar2(title, ep_obj.pretty_name() + ': ' + lang) def notify_git_update(self, new_version='??'): """Send update available message.""" update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] self._notify_boxcar2(title, update_text + new_version) def notify_login(self, ipaddress=''): """Send the new login message.""" update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] self._notify_boxcar2(title, update_text.format(ipaddress)) def _notify_boxcar2(self, title, message, accesstoken=None): """ Send a boxcar2 notification based on the provided info or SB config. title: The title of the notification to send message: The message string to send accesstoken: to send to this device """ if not app.USE_BOXCAR2: log.debug( 'Notification for Boxcar2 not enabled, skipping this notification' ) return False accesstoken = accesstoken or app.BOXCAR2_ACCESSTOKEN log.debug('Sending notification for {0}', message) return self._send_boxcar2(message, title, accesstoken)
class Notifier(object): """Emby notifier class.""" def __init__(self): self.session = MedusaSession() def _notify_emby(self, message, host=None, emby_apikey=None): """ Notify Emby host via HTTP API. :return: True for no issue or False if there was an error """ # fill in omitted parameters if not host: host = app.EMBY_HOST if not emby_apikey: emby_apikey = app.EMBY_APIKEY url = 'http://{host}/emby/Notifications/Admin'.format(host=host) try: resp = self.session.post( url=url, data={ 'Name': 'Medusa', 'Description': message, 'ImageUrl': app.LOGO_URL }, headers={ 'X-MediaBrowser-Token': emby_apikey, 'Content-Type': 'application/json' } ) resp.raise_for_status() if resp.content: log.debug('EMBY: HTTP response: {0}', resp.content.replace('\n', '')) log.info('EMBY: Successfully sent a test notification.') return True except (HTTPError, RequestException) as error: log.warning('EMBY: Warning: Unable to contact Emby at {url}: {error}', {'url': url, 'error': ex(error)}) return False ############################################################################## # Public functions ############################################################################## def test_notify(self, host, emby_apikey): """ Sends a test notification. :return: True for no issue or False if there was an error """ return self._notify_emby('This is a test notification from Medusa', host, emby_apikey) def update_library(self, show=None): """ Update the Emby Media Server host via HTTP API. :return: True for no issue or False if there was an error """ if app.USE_EMBY: if not app.EMBY_HOST: log.debug('EMBY: No host specified, check your settings') return False if show: # EMBY only supports TVDB ids provider = 'tvdbid' if show.indexer == INDEXER_TVDBV2: tvdb_id = show.indexerid else: # Try using external ids to get a TVDB id tvdb_id = show.externals.get(mappings[INDEXER_TVDBV2], None) if tvdb_id is None: if show.indexer == INDEXER_TVRAGE: log.warning('EMBY: TVRage indexer no longer valid') else: log.warning( 'EMBY: Unable to find a TVDB ID for {series},' ' and {indexer} indexer is unsupported', {'series': show.name, 'indexer': indexer_id_to_name(show.indexer)} ) return False params = { provider: str(tvdb_id) } else: params = {} url = 'http://{host}/emby/Library/Series/Updated'.format(host=app.EMBY_HOST) try: resp = self.session.post( url=url, params=params, headers={ 'X-MediaBrowser-Token': app.EMBY_APIKEY } ) resp.raise_for_status() if resp.content: log.debug('EMBY: HTTP response: {0}', resp.content.replace('\n', '')) log.info('EMBY: Successfully sent a "Series Library Updated" command.') return True except (HTTPError, RequestException) as error: log.warning('EMBY: Warning: Unable to contact Emby at {url}: {error}', {'url': url, 'error': ex(error)}) return False
class Notifier(object): def __init__(self): self.session = MedusaSession() def test_notify(self, pushalot_authorizationtoken): return self._sendPushalot( pushalot_authorizationtoken=pushalot_authorizationtoken, event='Test', message='Testing Pushalot settings from Medusa', force=True) def notify_snatch(self, ep_name, is_proper): if app.PUSHALOT_NOTIFY_ONSNATCH: self._sendPushalot(pushalot_authorizationtoken=None, event=common.notifyStrings[( common.NOTIFY_SNATCH, common.NOTIFY_SNATCH_PROPER)[is_proper]], message=ep_name) def notify_download(self, ep_name): if app.PUSHALOT_NOTIFY_ONDOWNLOAD: self._sendPushalot( pushalot_authorizationtoken=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=ep_name) def notify_subtitle_download(self, ep_name, lang): if app.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD: self._sendPushalot( pushalot_authorizationtoken=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], message='{}:{}'.format(ep_name, lang)) def notify_git_update(self, new_version='??'): update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] self._sendPushalot(pushalot_authorizationtoken=None, event=title, message=update_text + new_version) def notify_login(self, ipaddress=''): update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] self._sendPushalot(pushalot_authorizationtoken=None, event=title, message=update_text.format(ipaddress)) def _sendPushalot(self, pushalot_authorizationtoken=None, event=None, message=None, force=False): if not (app.USE_PUSHALOT or force): return False pushalot_authorizationtoken = pushalot_authorizationtoken or app.PUSHALOT_AUTHORIZATIONTOKEN log.debug('Pushalot event: {0}', event) log.debug('Pushalot message: {0}', message) log.debug('Pushalot api: {0}', pushalot_authorizationtoken) post_data = { 'AuthorizationToken': pushalot_authorizationtoken, 'Title': event or '', 'Body': message or '' } # TODO: SESSION: Check if this needs exception handling. jdata = self.session.post('https://pushalot.com/api/sendmessage', data=post_data).json() or {} # {'Status': 200, 'Description': 'The request has been completed successfully.', 'Success': True} success = jdata.pop('Success', False) if success: log.debug('Pushalot notifications sent.') else: log.error('Pushalot notification failed: {0} {1}', jdata.get('Status', ''), jdata.get('Description', 'Unknown')) return success
class Notifier(object): def __init__(self): self.session = MedusaSession() self.url = 'https://api.pushbullet.com/v2/' def test_notify(self, pushbullet_api): log.debug('Sending a test Pushbullet notification.') return self._sendPushbullet( pushbullet_api, event='Test', message='Testing Pushbullet settings from Medusa', force=True) def get_devices(self, pushbullet_api): log.debug( 'Testing Pushbullet authentication and retrieving the device list.' ) headers = { 'Access-Token': pushbullet_api, 'Content-Type': 'application/json' } try: r = self.session.get(urljoin(self.url, 'devices'), headers=headers) return r.text except ValueError: return {} def notify_snatch(self, title, message, **kwargs): if app.PUSHBULLET_NOTIFY_ONSNATCH: self._sendPushbullet(pushbullet_api=None, event=title, message=message) def notify_download(self, ep_obj): if app.PUSHBULLET_NOTIFY_ONDOWNLOAD: self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD] + ': ' + ep_obj.pretty_name_with_quality(), message=ep_obj.pretty_name_with_quality()) def notify_subtitle_download(self, ep_obj, lang): if app.PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD: self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD] + ': ' + ep_obj.pretty_name() + ': ' + lang, message=ep_obj.pretty_name() + ': ' + lang) def notify_git_update(self, new_version='??'): link = re.match(r'.*href="(.*?)" .*', app.NEWEST_VERSION_STRING) if link: link = link.group(1) self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_GIT_UPDATE], message=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] + new_version, link=link) def notify_login(self, ipaddress=''): self._sendPushbullet(pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_LOGIN], message=common.notifyStrings[ common.NOTIFY_LOGIN_TEXT].format(ipaddress)) def _sendPushbullet( # pylint: disable=too-many-arguments self, pushbullet_api=None, pushbullet_device=None, event=None, message=None, link=None, force=False): push_result = {'success': False, 'error': ''} if not (app.USE_PUSHBULLET or force): return False pushbullet_api = pushbullet_api or app.PUSHBULLET_API pushbullet_device = pushbullet_device or app.PUSHBULLET_DEVICE log.debug('Pushbullet event: {0!r}', event) log.debug('Pushbullet message: {0!r}', message) log.debug('Pushbullet api: {0!r}', pushbullet_api) log.debug('Pushbullet devices: {0!r}', pushbullet_device) post_data = { 'title': event, 'body': message, 'device_iden': pushbullet_device, 'type': 'link' if link else 'note' } if link: post_data['url'] = link headers = { 'Access-Token': pushbullet_api, 'Content-Type': 'application/json' } r = self.session.post(urljoin(self.url, 'pushes'), json=post_data, headers=headers) try: response = r.json() except ValueError: log.warning( 'Pushbullet notification failed. Could not parse pushbullet response.' ) push_result[ 'error'] = 'Pushbullet notification failed. Could not parse pushbullet response.' return push_result failed = response.pop('error', {}) if failed: log.warning('Pushbullet notification failed: {0}', failed.get('message')) push_result[ 'error'] = 'Pushbullet notification failed: {0}'.format( failed.get('message')) else: log.debug('Pushbullet notification sent.') push_result['success'] = True return push_result
class Notifier(object): def __init__(self): self.session = MedusaSession() self.session.headers.update({ 'X-Plex-Device-Name': 'Medusa', 'X-Plex-Product': 'Medusa Notifier', 'X-Plex-Client-Identifier': common.USER_AGENT, 'X-Plex-Version': app.APP_VERSION, }) @staticmethod def _notify_pht(title, message, host=None, username=None, password=None, force=False): # pylint: disable=too-many-arguments """Internal wrapper for the notify_snatch and notify_download functions Args: message: Message body of the notice to send title: Title of the notice to send host: Plex Home Theater(s) host:port username: Plex username password: Plex password force: Used for the Test method to override config safety checks Returns: Returns a list results in the format of host:ip:result The result will either be 'OK' or False, this is used to be parsed by the calling function. """ from medusa.notifiers import kodi_notifier # suppress notifications if the notifier is disabled but the notify options are checked if not app.USE_PLEX_CLIENT and not force: return False host = host or app.PLEX_CLIENT_HOST username = username or app.PLEX_CLIENT_USERNAME password = password or app.PLEX_CLIENT_PASSWORD return kodi_notifier._notify_kodi(title, message, host=host, username=username, password=password, force=force, dest_app='PLEX') # pylint: disable=protected-access ############################################################################## # Public functions ############################################################################## def notify_snatch(self, title, message, **kwargs): if app.PLEX_NOTIFY_ONSNATCH: self._notify_pht(title, message) def notify_download(self, ep_obj): if app.PLEX_NOTIFY_ONDOWNLOAD: self._notify_pht(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_obj.pretty_name_with_quality()) def notify_subtitle_download(self, ep_obj, lang): if app.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD: self._notify_pht( common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], ep_obj.pretty_name() + ': ' + lang) def notify_git_update(self, new_version='??'): if app.NOTIFY_ON_UPDATE: update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] if update_text and title and new_version: self._notify_pht(title, update_text + new_version) def notify_login(self, ipaddress=''): if app.NOTIFY_ON_LOGIN: update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] if update_text and title and ipaddress: self._notify_pht(title, update_text.format(ipaddress)) def test_notify_pht(self, host, username, password): return self._notify_pht('Test Notification', 'This is a test notification from Medusa', host, username, password, force=True) def test_notify_pms(self, host, username, password, plex_server_token): return self.update_library(hosts=host, username=username, password=password, plex_server_token=plex_server_token, force=True) def update_library( self, ep_obj=None, hosts=None, # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches username=None, password=None, plex_server_token=None, force=False): """Handles updating the Plex Media Server host via HTTP API Plex Media Server currently only supports updating the whole video library and not a specific path. Returns: Returns None for no issue, else a string of host with connection issues """ if not (app.USE_PLEX_SERVER and app.PLEX_UPDATE_LIBRARY) and not force: return None hosts = hosts or app.PLEX_SERVER_HOST if not hosts: log.debug( u'PLEX: No Plex Media Server host specified, check your settings' ) return False if not self.get_token(username, password, plex_server_token): log.warning( u'PLEX: Error getting auth token for Plex Media Server, check your settings' ) return False file_location = '' if not ep_obj else ep_obj.location gen_hosts = generate(hosts) hosts = (x.strip() for x in gen_hosts if x.strip()) all_hosts = {} matching_hosts = {} failed_hosts = set() schema = 'https' if app.PLEX_SERVER_HTTPS else 'http' for cur_host in hosts: url = '{schema}://{host}/library/sections'.format(schema=schema, host=cur_host) try: response = self.session.get(url) except requests.RequestException as error: log.warning( u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error)) failed_hosts.add(cur_host) continue try: response.raise_for_status() except requests.RequestException as error: if response.status_code == 401: log.warning( u'PLEX: Unauthorized. Please set TOKEN or USERNAME and PASSWORD in Plex settings' ) else: log.warning( u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error)) failed_hosts.add(cur_host) continue else: xml_response = response.text if not xml_response: log.warning( u'PLEX: Error while trying to contact Plex Media Server: {0}', cur_host) failed_hosts.add(cur_host) continue else: media_container = etree.fromstring(xml_response) sections = media_container.findall('.//Directory') if not sections: log.debug(u'PLEX: Plex Media Server not running on: {0}', cur_host) failed_hosts.add(cur_host) continue for section in sections: if 'show' == section.attrib['type']: key = str(section.attrib['key']) keyed_host = { key: cur_host, } all_hosts.update(keyed_host) if not file_location: continue for section_location in section.findall('.//Location'): section_path = re.sub( r'[/\\]+', '/', section_location.attrib['path'].lower()) section_path = re.sub(r'^(.{,2})[/\\]', '', section_path) location_path = re.sub(r'[/\\]+', '/', file_location.lower()) location_path = re.sub(r'^(.{,2})[/\\]', '', location_path) if section_path in location_path: matching_hosts.update(keyed_host) if force: return ', '.join(failed_hosts) if failed_hosts else None if matching_hosts: hosts_try = matching_hosts result = u'PLEX: Updating hosts where TV section paths match the downloaded show: {0}' else: hosts_try = all_hosts result = u'PLEX: Updating all hosts with TV sections: {0}' log.debug(result.format(', '.join(hosts_try))) for section_key, cur_host in iteritems(hosts_try): url = '{schema}://{host}/library/sections/{key}/refresh'.format( schema=schema, host=cur_host, key=section_key, ) try: response = self.session.get(url) except requests.RequestException as error: log.warning( u'PLEX: Error updating library section for Plex Media Server: {0}', ex(error)) failed_hosts.add(cur_host) else: del response # request succeeded so response is not needed return ', '.join(failed_hosts) if failed_hosts else None def get_token(self, username=None, password=None, plex_server_token=None): """ Get auth token. Try to get the auth token from the argument, the config, the session, or the Plex website in that order. :param username: plex.tv username :param password: plex.tv password :param plex_server_token: auth token :returns: Plex auth token being used or True if authentication is not required, else None """ username = username or app.PLEX_SERVER_USERNAME password = password or app.PLEX_SERVER_PASSWORD plex_server_token = plex_server_token or app.PLEX_SERVER_TOKEN if plex_server_token: self.session.headers['X-Plex-Token'] = plex_server_token if 'X-Plex-Token' in self.session.headers: return self.session.headers['X-Plex-Token'] if not (username and password): return True log.debug(u'PLEX: fetching plex.tv credentials for user: {0}', username) error_msg = u'PLEX: Error fetching credentials from plex.tv for user {0}: {1}' try: # sign in response = self.session.post('https://plex.tv/users/sign_in.json', data={ 'user[login]': username, 'user[password]': password, }) response.raise_for_status() except requests.RequestException as error: log.debug(error_msg, username, error) return try: # get json data data = response.json() except ValueError as error: log.debug(error_msg, username, error) return try: # get token from key plex_server_token = data['user']['authentication_token'] except KeyError as error: log.debug(error_msg, username, error) return else: self.session.headers['X-Plex-Token'] = plex_server_token return self.session.headers.get('X-Plex-Token')
class Notifier(object): def __init__(self): self.session = MedusaSession() def test_notify(self, pushalot_authorizationtoken): return self._sendPushalot( pushalot_authorizationtoken=pushalot_authorizationtoken, event='Test', message='Testing Pushalot settings from Medusa', force=True ) def notify_snatch(self, title, message): if app.PUSHALOT_NOTIFY_ONSNATCH: self._sendPushalot( pushalot_authorizationtoken=None, event=title, message=message ) def notify_download(self, ep_obj): if app.PUSHALOT_NOTIFY_ONDOWNLOAD: self._sendPushalot( pushalot_authorizationtoken=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD], message=ep_obj.pretty_name_with_quality() ) def notify_subtitle_download(self, ep_obj, lang): if app.PUSHALOT_NOTIFY_ONSUBTITLEDOWNLOAD: self._sendPushalot( pushalot_authorizationtoken=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], message='{}:{}'.format(ep_obj.pretty_name(), lang) ) def notify_git_update(self, new_version='??'): update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] self._sendPushalot( pushalot_authorizationtoken=None, event=title, message=update_text + new_version ) def notify_login(self, ipaddress=''): update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] self._sendPushalot( pushalot_authorizationtoken=None, event=title, message=update_text.format(ipaddress) ) def _sendPushalot(self, pushalot_authorizationtoken=None, event=None, message=None, force=False): if not (app.USE_PUSHALOT or force): return False pushalot_authorizationtoken = pushalot_authorizationtoken or app.PUSHALOT_AUTHORIZATIONTOKEN log.debug('Pushalot event: {0}', event) log.debug('Pushalot message: {0}', message) log.debug('Pushalot api: {0}', pushalot_authorizationtoken) post_data = { 'AuthorizationToken': pushalot_authorizationtoken, 'Title': event or '', 'Body': message or '' } # TODO: SESSION: Check if this needs exception handling. jdata = self.session.post( 'https://pushalot.com/api/sendmessage', data=post_data).json() or {} # {'Status': 200, 'Description': 'The request has been completed successfully.', 'Success': True} success = jdata.pop('Success', False) if success: log.debug('Pushalot notifications sent.') else: log.error( 'Pushalot notification failed: {0} {1}', jdata.get('Status', ''), jdata.get('Description', 'Unknown') ) return success
class Notifier(object): def __init__(self): self.session = MedusaSession() self.url = 'https://api.pushbullet.com/v2/' def test_notify(self, pushbullet_api): log.debug('Sending a test Pushbullet notification.') return self._sendPushbullet( pushbullet_api, event='Test', message='Testing Pushbullet settings from Medusa', force=True ) def get_devices(self, pushbullet_api): log.debug('Testing Pushbullet authentication and retrieving the device list.') headers = {'Access-Token': pushbullet_api, 'Content-Type': 'application/json'} try: r = self.session.get(urljoin(self.url, 'devices'), headers=headers) return r.text except ValueError: return {} def notify_snatch(self, title, message): if app.PUSHBULLET_NOTIFY_ONSNATCH: self._sendPushbullet( pushbullet_api=None, event=title, message=message ) def notify_download(self, ep_obj): if app.PUSHBULLET_NOTIFY_ONDOWNLOAD: self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_DOWNLOAD] + ': ' + ep_obj.pretty_name_with_quality(), message=ep_obj.pretty_name_with_quality() ) def notify_subtitle_download(self, ep_obj, lang): if app.PUSHBULLET_NOTIFY_ONSUBTITLEDOWNLOAD: self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD] + ': ' + ep_obj.pretty_name() + ': ' + lang, message=ep_obj.pretty_name() + ': ' + lang ) def notify_git_update(self, new_version='??'): link = re.match(r'.*href="(.*?)" .*', app.NEWEST_VERSION_STRING) if link: link = link.group(1) self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_GIT_UPDATE], message=common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] + new_version, link=link ) def notify_login(self, ipaddress=''): self._sendPushbullet( pushbullet_api=None, event=common.notifyStrings[common.NOTIFY_LOGIN], message=common.notifyStrings[common.NOTIFY_LOGIN_TEXT].format(ipaddress) ) def _sendPushbullet( # pylint: disable=too-many-arguments self, pushbullet_api=None, pushbullet_device=None, event=None, message=None, link=None, force=False): push_result = {'success': False, 'error': ''} if not (app.USE_PUSHBULLET or force): return False pushbullet_api = pushbullet_api or app.PUSHBULLET_API pushbullet_device = pushbullet_device or app.PUSHBULLET_DEVICE log.debug('Pushbullet event: {0!r}', event) log.debug('Pushbullet message: {0!r}', message) log.debug('Pushbullet api: {0!r}', pushbullet_api) log.debug('Pushbullet devices: {0!r}', pushbullet_device) post_data = { 'title': event, 'body': message, 'device_iden': pushbullet_device, 'type': 'link' if link else 'note' } if link: post_data['url'] = link headers = {'Access-Token': pushbullet_api, 'Content-Type': 'application/json'} r = self.session.post(urljoin(self.url, 'pushes'), json=post_data, headers=headers) try: response = r.json() except ValueError: log.warning('Pushbullet notification failed. Could not parse pushbullet response.') push_result['error'] = 'Pushbullet notification failed. Could not parse pushbullet response.' return push_result failed = response.pop('error', {}) if failed: log.warning('Pushbullet notification failed: {0}', failed.get('message')) push_result['error'] = 'Pushbullet notification failed: {0}'.format(failed.get('message')) else: log.debug('Pushbullet notification sent.') push_result['success'] = True return push_result
class Notifier(object): def __init__(self): self.headers = { 'X-Plex-Device-Name': 'Medusa', 'X-Plex-Product': 'Medusa Notifier', 'X-Plex-Client-Identifier': common.USER_AGENT, 'X-Plex-Version': '2016.02.10' } self.session = MedusaSession() @staticmethod def _notify_pht(message, title='Medusa', host=None, username=None, password=None, force=False): # pylint: disable=too-many-arguments """Internal wrapper for the notify_snatch and notify_download functions Args: message: Message body of the notice to send title: Title of the notice to send host: Plex Home Theater(s) host:port username: Plex username password: Plex password force: Used for the Test method to override config safety checks Returns: Returns a list results in the format of host:ip:result The result will either be 'OK' or False, this is used to be parsed by the calling function. """ from . import kodi_notifier # suppress notifications if the notifier is disabled but the notify options are checked if not app.USE_PLEX_CLIENT and not force: return False host = host or app.PLEX_CLIENT_HOST username = username or app.PLEX_CLIENT_USERNAME password = password or app.PLEX_CLIENT_PASSWORD return kodi_notifier._notify_kodi(message, title=title, host=host, username=username, password=password, force=force, dest_app='PLEX') # pylint: disable=protected-access ############################################################################## # Public functions ############################################################################## def notify_snatch(self, ep_name, is_proper): if app.PLEX_NOTIFY_ONSNATCH: self._notify_pht( ep_name, common.notifyStrings[(common.NOTIFY_SNATCH, common.NOTIFY_SNATCH_PROPER)[is_proper]]) def notify_download(self, ep_name): if app.PLEX_NOTIFY_ONDOWNLOAD: self._notify_pht(ep_name, common.notifyStrings[common.NOTIFY_DOWNLOAD]) def notify_subtitle_download(self, ep_name, lang): if app.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD: self._notify_pht( ep_name + ': ' + lang, common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD]) def notify_git_update(self, new_version='??'): if app.NOTIFY_ON_UPDATE: update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] if update_text and title and new_version: self._notify_pht(update_text + new_version, title) def notify_login(self, ipaddress=''): if app.NOTIFY_ON_LOGIN: update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] if update_text and title and ipaddress: self._notify_pht(update_text.format(ipaddress), title) def test_notify_pht(self, host, username, password): return self._notify_pht('This is a test notification from Medusa', 'Test Notification', host, username, password, force=True) def test_notify_pms(self, host, username, password, plex_server_token): return self.update_library(hosts=host, username=username, password=password, plex_server_token=plex_server_token, force=True) def update_library( self, ep_obj=None, hosts=None, # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches username=None, password=None, plex_server_token=None, force=False): """Handles updating the Plex Media Server host via HTTP API Plex Media Server currently only supports updating the whole video library and not a specific path. Returns: Returns None for no issue, else a string of host with connection issues """ if not (app.USE_PLEX_SERVER and app.PLEX_UPDATE_LIBRARY) and not force: return None hosts = hosts or app.PLEX_SERVER_HOST if not hosts: log.debug( u'PLEX: No Plex Media Server host specified, check your settings' ) return False if not self.get_token(username, password, plex_server_token): log.warning( u'PLEX: Error getting auth token for Plex Media Server, check your settings' ) return False file_location = '' if not ep_obj else ep_obj.location gen_hosts = generate(hosts) hosts = {x.strip() for x in gen_hosts if x.strip()} hosts_all = hosts_match = {} hosts_failed = set() for cur_host in hosts: url = 'http{0}://{1}/library/sections'.format( ('', 's')[bool(app.PLEX_SERVER_HTTPS)], cur_host) try: # TODO: SESSION: Check if this needs exception handling. xml_response = self.session.get(url, headers=self.headers).text if not xml_response: log.warning( u'PLEX: Error while trying to contact Plex Media Server: {0}', cur_host) hosts_failed.add(cur_host) continue media_container = etree.fromstring(xml_response) except IOError as error: log.warning( u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error)) hosts_failed.add(cur_host) continue except Exception as error: if 'invalid token' in str(error): log.warning(u'PLEX: Please set TOKEN in Plex settings: ') else: log.warning( u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error)) hosts_failed.add(cur_host) continue sections = media_container.findall('.//Directory') if not sections: log.debug(u'PLEX: Plex Media Server not running on: {0}', cur_host) hosts_failed.add(cur_host) continue for section in sections: if 'show' == section.attrib['type']: keyed_host = [(str(section.attrib['key']), cur_host)] hosts_all.update(keyed_host) if not file_location: continue for section_location in section.findall('.//Location'): section_path = re.sub( r'[/\\]+', '/', section_location.attrib['path'].lower()) section_path = re.sub(r'^(.{,2})[/\\]', '', section_path) location_path = re.sub(r'[/\\]+', '/', file_location.lower()) location_path = re.sub(r'^(.{,2})[/\\]', '', location_path) if section_path in location_path: hosts_match.update(keyed_host) if force: return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)] if hosts_match: log.debug( u'PLEX: Updating hosts where TV section paths match the downloaded show: {0}', ', '.join(set(hosts_match))) else: log.debug(u'PLEX: Updating all hosts with TV sections: {0}', ', '.join(set(hosts_all))) hosts_try = (hosts_match.copy(), hosts_all.copy())[not len(hosts_match)] for section_key, cur_host in iteritems(hosts_try): url = 'http{0}://{1}/library/sections/{2}/refresh'.format( ('', 's')[bool(app.PLEX_SERVER_HTTPS)], cur_host, section_key) try: # TODO: Check if this needs exception handling self.session.get(url, headers=self.headers).text except Exception as error: log.warning( u'PLEX: Error updating library section for Plex Media Server: {0}', ex(error)) hosts_failed.add(cur_host) return (', '.join(set(hosts_failed)), None)[not len(hosts_failed)] def get_token(self, username=None, password=None, plex_server_token=None): username = username or app.PLEX_SERVER_USERNAME password = password or app.PLEX_SERVER_PASSWORD plex_server_token = plex_server_token or app.PLEX_SERVER_TOKEN if plex_server_token: self.headers['X-Plex-Token'] = plex_server_token if 'X-Plex-Token' in self.headers: return True if not (username and password): return True log.debug(u'PLEX: fetching plex.tv credentials for user: {0}', username) params = {'user[login]': username, 'user[password]': password} try: response = self.session.post('https://plex.tv/users/sign_in.json', data=params, headers=self.headers).json() self.headers['X-Plex-Token'] = response['user'][ 'authentication_token'] except Exception as error: self.headers.pop('X-Plex-Token', '') log.debug( u'PLEX: Error fetching credentials from from plex.tv for user {0}: {1}', username, error) return 'X-Plex-Token' in self.headers
class AniListPopular(BasePopular): # pylint: disable=too-few-public-methods """Anilist popular class.""" BASE_URL = 'https://graphql.anilist.co' TITLE = 'AniList' CACHE_SUBFOLDER = __name__.split('.')[-1] if '.' in __name__ else __name__ def __init__(self): """Class retrieves a specified recommended show list from Anilist. List of returned shows is mapped to a RecommendedShow object """ super(AniListPopular, self).__init__() self.cache_subfolder = AniListPopular.CACHE_SUBFOLDER self.recommender = AniListPopular.TITLE self.source = EXTERNAL_ANILIST self.base_url = AniListPopular.BASE_URL self.session = MedusaSession() @recommended_series_cache.cache_on_arguments(namespace='anilist', function_key_generator=create_key_from_series) def _create_recommended_show(self, show): """Create the RecommendedShow object from the returned showobj.""" rec_show = RecommendedShow( self, show['id'], show['title']['userPreferred'], **{ 'rating': show['averageScore'] / 10 if show['averageScore'] else 0, 'votes': show['popularity'], 'image_href': f"https://anilist.co/anime/{show['id']}", 'ids': { 'anilist_id': show['id'] }, 'is_anime': True, 'subcat': f"{show['startDate']['year']}_{show['season'].lower()}", 'genres': [genre.lower() for genre in show['genres']], 'plot': show['description'] } ) # Check cache or get and save image use_default = self.default_img_src if not show['coverImage']['large'] else None rec_show.cache_image(show['coverImage']['large'], default=use_default) return rec_show def fetch_popular_shows(self, year, season): """Get popular show information from IMDB.""" result = [] query = 'query($page:Int = 1 $id:Int $type:MediaType $isAdult:Boolean = false $search:String $format:[MediaFormat]$status:MediaStatus $countryOfOrigin:CountryCode $source:MediaSource $season:MediaSeason $seasonYear:Int $year:String $onList:Boolean $yearLesser:FuzzyDateInt $yearGreater:FuzzyDateInt $episodeLesser:Int $episodeGreater:Int $durationLesser:Int $durationGreater:Int $chapterLesser:Int $chapterGreater:Int $volumeLesser:Int $volumeGreater:Int $licensedBy:[String]$genres:[String]$excludedGenres:[String]$tags:[String]$excludedTags:[String]$minimumTagRank:Int $sort:[MediaSort]=[POPULARITY_DESC,SCORE_DESC]){Page(page:$page,perPage:20){pageInfo{total perPage currentPage lastPage hasNextPage}media(id:$id type:$type season:$season format_in:$format status:$status countryOfOrigin:$countryOfOrigin source:$source search:$search onList:$onList seasonYear:$seasonYear startDate_like:$year startDate_lesser:$yearLesser startDate_greater:$yearGreater episodes_lesser:$episodeLesser episodes_greater:$episodeGreater duration_lesser:$durationLesser duration_greater:$durationGreater chapters_lesser:$chapterLesser chapters_greater:$chapterGreater volumes_lesser:$volumeLesser volumes_greater:$volumeGreater licensedBy_in:$licensedBy genre_in:$genres genre_not_in:$excludedGenres tag_in:$tags tag_not_in:$excludedTags minimumTagRank:$minimumTagRank sort:$sort isAdult:$isAdult){id title{userPreferred}coverImage{extraLarge large color}startDate{year month day}endDate{year month day}bannerImage season description type format status(version:2)episodes duration chapters volumes genres isAdult averageScore popularity nextAiringEpisode{airingAt timeUntilAiring episode}mediaListEntry{id status}studios(isMain:true){edges{isMain node{id name}}}}}}' variables = { 'page': 1, 'type': 'ANIME', 'seasonYear': year, 'season': season.upper(), 'sort': 'SCORE_DESC', 'format': ['TV'] } try: response = self.session.post(self.base_url, json={'query': query, 'variables': variables}) results = response.json()['data'] except Exception as error: log.warning('Unable to get Anilist shows: {0!r}', error) return [] if not results.get('Page') or not results['Page'].get('media'): return [] for show in results['Page']['media']: try: recommended_show = self._create_recommended_show(show) if recommended_show: recommended_show.save_to_db() result.append(recommended_show) except Exception: log.warning('Could not parse AniDB show, with exception: {0}', traceback.format_exc()) return result
class Notifier(object): """Emby notifier class.""" def __init__(self): self.session = MedusaSession() def _notify_emby(self, message, host=None, emby_apikey=None): """ Notify Emby host via HTTP API. :return: True for no issue or False if there was an error """ # fill in omitted parameters if not host: host = app.EMBY_HOST if not emby_apikey: emby_apikey = app.EMBY_APIKEY url = 'http://{host}/emby/Notifications/Admin'.format(host=host) data = json.dumps({ 'Name': 'Medusa', 'Description': message, 'ImageUrl': app.LOGO_URL }) try: resp = self.session.post( url=url, data=data, headers={ 'X-MediaBrowser-Token': emby_apikey, 'Content-Type': 'application/json' } ) resp.raise_for_status() if resp.content: log.debug('EMBY: HTTP response: {0}', resp.content.replace('\n', '')) log.info('EMBY: Successfully sent a test notification.') return True except (HTTPError, RequestException) as error: log.warning('EMBY: Warning: Unable to contact Emby at {url}: {error}', {'url': url, 'error': ex(error)}) return False ############################################################################## # Public functions ############################################################################## def test_notify(self, host, emby_apikey): """ Sends a test notification. :return: True for no issue or False if there was an error """ return self._notify_emby('This is a test notification from Medusa', host, emby_apikey) def update_library(self, show=None): """ Update the Emby Media Server host via HTTP API. :return: True for no issue or False if there was an error """ if app.USE_EMBY: if not app.EMBY_HOST: log.debug('EMBY: No host specified, check your settings') return False if show: # EMBY only supports TVDB ids provider = 'tvdbid' if show.indexer == INDEXER_TVDBV2: tvdb_id = show.indexerid else: # Try using external ids to get a TVDB id tvdb_id = show.externals.get(mappings[INDEXER_TVDBV2], None) if tvdb_id is None: if show.indexer == INDEXER_TVRAGE: log.warning('EMBY: TVRage indexer no longer valid') else: log.warning( 'EMBY: Unable to find a TVDB ID for {series},' ' and {indexer} indexer is unsupported', {'series': show.name, 'indexer': indexer_id_to_name(show.indexer)} ) return False params = { provider: text_type(tvdb_id) } else: params = {} url = 'http://{host}/emby/Library/Series/Updated'.format(host=app.EMBY_HOST) try: resp = self.session.post( url=url, params=params, headers={ 'X-MediaBrowser-Token': app.EMBY_APIKEY } ) resp.raise_for_status() if resp.content: log.debug('EMBY: HTTP response: {0}', resp.content.replace('\n', '')) log.info('EMBY: Successfully sent a "Series Library Updated" command.') return True except (HTTPError, RequestException) as error: log.warning('EMBY: Warning: Unable to contact Emby at {url}: {error}', {'url': url, 'error': ex(error)}) return False
class Notifier(object): """Boxcar2 class.""" def __init__(self): """Initialize the class.""" self.session = MedusaSession() self.url = 'https://new.boxcar.io/api/notifications' def test_notify(self, accesstoken, title='Medusa: Test'): """Test the notify.""" return self._send_boxcar2('This is a test notification from Medusa', title, accesstoken) def _send_boxcar2(self, msg, title, accesstoken): """ Send a boxcar2 notification to the address provided. msg: The message to send title: The title of the message accesstoken: to send to this device return: True if the message succeeded, False otherwise """ # http://blog.boxcar.io/post/93211745502/boxcar-api-update-boxcar-api-update-icon-and post_data = { 'user_credentials': accesstoken, 'notification[title]': 'Medusa: {}: {}'.format(title, msg), 'notification[long_message]': msg, 'notification[sound]': 'notifier-2', 'notification[source_name]': 'Medusa', 'notification[icon_url]': app.LOGO_URL } # TODO: SESSION: Check if this needs exception handling. response = self.session.post(self.url, data=post_data, timeout=60).json() if not response: log.error('Boxcar2 notification failed.') return False log.debug('Boxcar2 notification successful.') return True def notify_snatch(self, title, message): """Send the snatch message.""" if app.BOXCAR2_NOTIFY_ONSNATCH: self._notify_boxcar2(title, message) def notify_download(self, ep_obj, title=common.notifyStrings[common.NOTIFY_DOWNLOAD]): """Send the download message.""" if app.BOXCAR2_NOTIFY_ONDOWNLOAD: self._notify_boxcar2(title, ep_obj.pretty_name_with_quality()) def notify_subtitle_download(self, ep_obj, lang, title=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD]): """Send the subtitle download message.""" if app.BOXCAR2_NOTIFY_ONSUBTITLEDOWNLOAD: self._notify_boxcar2(title, ep_obj.pretty_name() + ': ' + lang) def notify_git_update(self, new_version='??'): """Send update available message.""" update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] self._notify_boxcar2(title, update_text + new_version) def notify_login(self, ipaddress=''): """Send the new login message.""" update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] self._notify_boxcar2(title, update_text.format(ipaddress)) def _notify_boxcar2(self, title, message, accesstoken=None): """ Send a boxcar2 notification based on the provided info or SB config. title: The title of the notification to send message: The message string to send accesstoken: to send to this device """ if not app.USE_BOXCAR2: log.debug('Notification for Boxcar2 not enabled, skipping this notification') return False accesstoken = accesstoken or app.BOXCAR2_ACCESSTOKEN log.debug('Sending notification for {0}', message) return self._send_boxcar2(message, title, accesstoken)
class Notifier(object): def __init__(self): self.session = MedusaSession() self.session.headers.update({ 'X-Plex-Device-Name': 'Medusa', 'X-Plex-Product': 'Medusa Notifier', 'X-Plex-Client-Identifier': common.USER_AGENT, 'X-Plex-Version': app.APP_VERSION, }) @staticmethod def _notify_pht(title, message, host=None, username=None, password=None, force=False): # pylint: disable=too-many-arguments """Internal wrapper for the notify_snatch and notify_download functions Args: message: Message body of the notice to send title: Title of the notice to send host: Plex Home Theater(s) host:port username: Plex username password: Plex password force: Used for the Test method to override config safety checks Returns: Returns a list results in the format of host:ip:result The result will either be 'OK' or False, this is used to be parsed by the calling function. """ from medusa.notifiers import kodi_notifier # suppress notifications if the notifier is disabled but the notify options are checked if not app.USE_PLEX_CLIENT and not force: return False host = host or app.PLEX_CLIENT_HOST username = username or app.PLEX_CLIENT_USERNAME password = password or app.PLEX_CLIENT_PASSWORD return kodi_notifier._notify_kodi(message, title=title, host=host, username=username, password=password, force=force, dest_app='PLEX') # pylint: disable=protected-access ############################################################################## # Public functions ############################################################################## def notify_snatch(self, title, message): if app.PLEX_NOTIFY_ONSNATCH: self._notify_pht(title, message) def notify_download(self, ep_obj): if app.PLEX_NOTIFY_ONDOWNLOAD: self._notify_pht(common.notifyStrings[common.NOTIFY_DOWNLOAD], ep_obj.pretty_name_with_quality()) def notify_subtitle_download(self, ep_obj, lang): if app.PLEX_NOTIFY_ONSUBTITLEDOWNLOAD: self._notify_pht(common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD], ep_obj.pretty_name() + ': ' + lang) def notify_git_update(self, new_version='??'): if app.NOTIFY_ON_UPDATE: update_text = common.notifyStrings[common.NOTIFY_GIT_UPDATE_TEXT] title = common.notifyStrings[common.NOTIFY_GIT_UPDATE] if update_text and title and new_version: self._notify_pht(title, update_text + new_version) def notify_login(self, ipaddress=''): if app.NOTIFY_ON_LOGIN: update_text = common.notifyStrings[common.NOTIFY_LOGIN_TEXT] title = common.notifyStrings[common.NOTIFY_LOGIN] if update_text and title and ipaddress: self._notify_pht(title, update_text.format(ipaddress)) def test_notify_pht(self, host, username, password): return self._notify_pht('Test Notification', 'This is a test notification from Medusa', host, username, password, force=True) def test_notify_pms(self, host, username, password, plex_server_token): return self.update_library(hosts=host, username=username, password=password, plex_server_token=plex_server_token, force=True) def update_library(self, ep_obj=None, hosts=None, # pylint: disable=too-many-arguments, too-many-locals, too-many-statements, too-many-branches username=None, password=None, plex_server_token=None, force=False): """Handles updating the Plex Media Server host via HTTP API Plex Media Server currently only supports updating the whole video library and not a specific path. Returns: Returns None for no issue, else a string of host with connection issues """ if not (app.USE_PLEX_SERVER and app.PLEX_UPDATE_LIBRARY) and not force: return None hosts = hosts or app.PLEX_SERVER_HOST if not hosts: log.debug(u'PLEX: No Plex Media Server host specified, check your settings') return False if not self.get_token(username, password, plex_server_token): log.warning(u'PLEX: Error getting auth token for Plex Media Server, check your settings') return False file_location = '' if not ep_obj else ep_obj.location gen_hosts = generate(hosts) hosts = (x.strip() for x in gen_hosts if x.strip()) all_hosts = {} matching_hosts = {} failed_hosts = set() schema = 'https' if app.PLEX_SERVER_HTTPS else 'http' for cur_host in hosts: url = '{schema}://{host}/library/sections'.format( schema=schema, host=cur_host ) try: response = self.session.get(url) except requests.RequestException as error: log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error)) failed_hosts.add(cur_host) continue try: response.raise_for_status() except requests.RequestException as error: if response.status_code == 401: log.warning(u'PLEX: Unauthorized. Please set TOKEN or USERNAME and PASSWORD in Plex settings') else: log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', ex(error)) failed_hosts.add(cur_host) continue else: xml_response = response.text if not xml_response: log.warning(u'PLEX: Error while trying to contact Plex Media Server: {0}', cur_host) failed_hosts.add(cur_host) continue else: media_container = etree.fromstring(xml_response) sections = media_container.findall('.//Directory') if not sections: log.debug(u'PLEX: Plex Media Server not running on: {0}', cur_host) failed_hosts.add(cur_host) continue for section in sections: if 'show' == section.attrib['type']: key = str(section.attrib['key']) keyed_host = { key: cur_host, } all_hosts.update(keyed_host) if not file_location: continue for section_location in section.findall('.//Location'): section_path = re.sub(r'[/\\]+', '/', section_location.attrib['path'].lower()) section_path = re.sub(r'^(.{,2})[/\\]', '', section_path) location_path = re.sub(r'[/\\]+', '/', file_location.lower()) location_path = re.sub(r'^(.{,2})[/\\]', '', location_path) if section_path in location_path: matching_hosts.update(keyed_host) if force: return ', '.join(failed_hosts) if failed_hosts else None if matching_hosts: hosts_try = matching_hosts result = u'PLEX: Updating hosts where TV section paths match the downloaded show: {0}' else: hosts_try = all_hosts result = u'PLEX: Updating all hosts with TV sections: {0}' log.debug(result.format(', '.join(hosts_try))) for section_key, cur_host in iteritems(hosts_try): url = '{schema}://{host}/library/sections/{key}/refresh'.format( schema=schema, host=cur_host, key=section_key, ) try: response = self.session.get(url) except requests.RequestException as error: log.warning(u'PLEX: Error updating library section for Plex Media Server: {0}', ex(error)) failed_hosts.add(cur_host) else: del response # request succeeded so response is not needed return ', '.join(failed_hosts) if failed_hosts else None def get_token(self, username=None, password=None, plex_server_token=None): """ Get auth token. Try to get the auth token from the argument, the config, the session, or the Plex website in that order. :param username: plex.tv username :param password: plex.tv password :param plex_server_token: auth token :returns: Plex auth token being used or True if authentication is not required, else None """ username = username or app.PLEX_SERVER_USERNAME password = password or app.PLEX_SERVER_PASSWORD plex_server_token = plex_server_token or app.PLEX_SERVER_TOKEN if plex_server_token: self.session.headers['X-Plex-Token'] = plex_server_token if 'X-Plex-Token' in self.session.headers: return self.session.headers['X-Plex-Token'] if not (username and password): return True log.debug(u'PLEX: fetching plex.tv credentials for user: {0}', username) error_msg = u'PLEX: Error fetching credentials from plex.tv for user {0}: {1}' try: # sign in response = self.session.post( 'https://plex.tv/users/sign_in.json', data={ 'user[login]': username, 'user[password]': password, } ) response.raise_for_status() except requests.RequestException as error: log.debug(error_msg, username, error) return try: # get json data data = response.json() except ValueError as error: log.debug(error_msg, username, error) return try: # get token from key plex_server_token = data['user']['authentication_token'] except KeyError as error: log.debug(error_msg, username, error) return else: self.session.headers['X-Plex-Token'] = plex_server_token return self.session.headers.get('X-Plex-Token')