Пример #1
0
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)
Пример #2
0
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
Пример #3
0
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
Пример #4
0
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
Пример #5
0
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')
Пример #6
0
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
Пример #7
0
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
Пример #8
0
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
Пример #9
0
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
Пример #10
0
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
Пример #11
0
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)
Пример #12
0
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')