예제 #1
0
 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()
예제 #2
0
파일: plex.py 프로젝트: reconman/Medusa
 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,
     })
예제 #3
0
    def __init__(self):
        self.updater = None
        self.install_type = None
        self.amActive = False
        self.install_type = self.find_install_type()
        if self.install_type == 'git':
            self.updater = GitUpdateManager()
        elif self.install_type == 'source':
            self.updater = SourceUpdateManager()

        self.session = MedusaSession()
예제 #4
0
    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()
예제 #5
0
파일: imdb.py 프로젝트: swipswaps/Medusa
 def __init__(self):
     """Initialize class."""
     self.cache_subfolder = __name__.split('.')[-1] if '.' in __name__ else __name__
     self.session = MedusaSession()
     self.imdb_api = Imdb(session=self.session)
     self.recommender = 'IMDB Popular'
     self.default_img_src = 'poster.png'
예제 #6
0
파일: plex.py 프로젝트: pymedusa/SickRage
 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,
     })
예제 #7
0
파일: anidb.py 프로젝트: zapru/Medusa
    def __init__(self):
        """Class retrieves a specified recommended show list from Trakt.

        List of returned shows is mapped to a RecommendedShow object
        """
        self.cache_subfolder = __name__.split('.')[-1] if '.' in __name__ else __name__
        self.session = MedusaSession()
        self.recommender = 'Anidb Popular'
        self.base_url = 'https://anidb.net/perl-bin/animedb.pl?show=anime&aid={aid}'
        self.default_img_src = 'poster.png'
예제 #8
0
 def __init__(self, recommender=None, source=None, cache_subfoler=None):
     """Recommended show base class (AnidbPopular, TraktPopular, etc)."""
     self.session = MedusaSession()
     self.recommender = recommender
     self.source = source
     self.cache_subfolder = 'recommended'
     self.default_img_src = 'poster.png'
     self.mapped_indexer = None
     self.mapped_series_id = None
     self.mapped_indexer_name = None
예제 #9
0
    def __init__(self):
        self.github_org = self.get_github_org()
        self.github_repo = self.get_github_repo()

        self.branch = app.BRANCH
        if app.BRANCH == '':
            self.branch = self._find_installed_branch()

        self._cur_commit_hash = app.CUR_COMMIT_HASH
        self._newest_commit_hash = None
        self._num_commits_behind = 0
        self._num_commits_ahead = 0

        self.session = MedusaSession()
예제 #10
0
    def __init__(self, name, host=None, username=None, password=None):
        """Constructor.

        :param name:
        :type name: string
        :param host:
        :type host: string
        :param username:
        :type username: string
        :param password:
        :type password: string
        """
        self.name = name
        self.username = app.TORRENT_USERNAME if username is None else username
        self.password = app.TORRENT_PASSWORD if password is None else password
        self.host = app.TORRENT_HOST if host is None else host
        self.rpcurl = app.TORRENT_RPCURL
        self.url = None
        self.response = None
        self.auth = None
        self.last_time = time.time()
        self.session = MedusaSession()
        self.session.auth = (self.username, self.password)
예제 #11
0
    def __init__(self, rec_show_prov, show_id, title, indexer, indexer_id,
                 **show_attr):
        """Create a show recommendation

        :param rec_show_prov: Recommended shows provider. Used to keep track of the provider,
                              which facilitated the recommended shows list.
        :param show_id: as provided by the list provider
        :param title: of the show as displayed in the recommended show page
        :param indexer: used to map the show to
        :param indexer_id: a mapped indexer_id for indexer
        :param rating: of the show in percent
        :param votes: number of votes
        :param image_href: the href when clicked on the show image (poster)
        :param image_src: the local url to the "cached" image (poster)
        :param default_img_src: a default image when no poster available
        """
        self.recommender = rec_show_prov.recommender
        self.cache_subfolder = rec_show_prov.cache_subfolder or 'recommended'
        self.default_img_src = getattr(rec_show_prov, 'default_img_src', '')

        self.show_id = show_id
        self.title = title
        self.indexer = int(indexer)
        try:
            self.indexer_id = int(indexer_id)
        except ValueError:
            raise MissingTvdbMapping('Could not parse the indexer_id [%s]' %
                                     indexer_id)

        self.rating = show_attr.get('rating') or 0

        self.votes = show_attr.get('votes')
        if self.votes and not isinstance(self.votes, int):
            trans_mapping = {ord(c): None for c in ['.', ',']}
            self.votes = int(
                self.votes.decode('utf-8').translate(trans_mapping))

        self.image_href = show_attr.get('image_href')
        self.image_src = show_attr.get('image_src')
        self.ids = show_attr.get('ids', {})
        self.is_anime = False

        # Check if the show is currently already in the db
        self.show_in_list = self.indexer_id in {
            show.indexerid
            for show in app.showList if show.indexerid
        }
        self.session = MedusaSession()
예제 #12
0
    def __init__(self):
        """Initialize class."""
        self.cache_subfolder = __name__.split('.')[-1] if '.' in __name__ else __name__
        self.session = MedusaSession()
        self.recommender = 'IMDB Popular'
        self.default_img_src = 'poster.png'
        self.anidb = Anidb(cache_dir=app.CACHE_DIR)

        # Use akas.imdb.com, just like the imdb lib.
        self.url = 'http://akas.imdb.com/search/title'

        self.params = {
            'at': 0,
            'sort': 'moviemeter',
            'title_type': 'tv_series',
            'year': '%s,%s' % (date.today().year - 1, date.today().year + 1),
        }
예제 #13
0
    def __init__(self, name, host=None, username=None, password=None):
        """Constructor.

        :param name:
        :type name: string
        :param host:
        :type host: string
        :param username:
        :type username: string
        :param password:
        :type password: string
        """
        self.name = name
        self.username = app.TORRENT_USERNAME if username is None else username
        self.password = app.TORRENT_PASSWORD if password is None else password
        self.host = app.TORRENT_HOST if host is None else host
        self.rpcurl = app.TORRENT_RPCURL
        self.url = None
        self.response = None
        self.auth = None
        self.last_time = time.time()
        self.session = MedusaSession()
        self.session.auth = (self.username, self.password)
예제 #14
0
class GenericClient(object):
    """Base class for all torrent clients."""

    def __init__(self, name, host=None, username=None, password=None):
        """Constructor.

        :param name:
        :type name: string
        :param host:
        :type host: string
        :param username:
        :type username: string
        :param password:
        :type password: string
        """
        self.name = name
        self.username = app.TORRENT_USERNAME if username is None else username
        self.password = app.TORRENT_PASSWORD if password is None else password
        self.host = app.TORRENT_HOST if host is None else host
        self.rpcurl = app.TORRENT_RPCURL
        self.url = None
        self.response = None
        self.auth = None
        self.last_time = time.time()
        self.session = MedusaSession()
        self.session.auth = (self.username, self.password)

    def _request(self, method='get', params=None, data=None, files=None, cookies=None):

        if time.time() > self.last_time + 1800 or not self.auth:
            self.last_time = time.time()
            self._get_auth()

        text_params = str(params)
        text_data = str(data)
        text_files = str(files)
        log.debug(
            '{name}: Requested a {method} connection to {url} with'
            ' params: {params} Data: {data} Files: {files}', {
                'name': self.name,
                'method': method.upper(),
                'url': self.url,
                'params': text_params[0:99] + '...' if len(text_params) > 102 else text_params,
                'data': text_data[0:99] + '...' if len(text_data) > 102 else text_data,
                'files': text_files[0:99] + '...' if len(text_files) > 102 else text_files,
            }
        )

        if not self.auth:
            log.warning('{name}: Authentication Failed', {'name': self.name})

            return False
        try:
            self.response = self.session.request(method, self.url, params=params, data=data, files=files,
                                                 cookies=cookies, timeout=120, verify=False)
        except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL) as error:
            log.warning('{name}: Invalid Host: {error}', {'name': self.name, 'error': error})
            return False
        except requests.exceptions.RequestException as error:
            log.warning('{name}: Error occurred during request: {error}',
                        {'name': self.name, 'error': error})
            return False
        except Exception as error:
            log.error('{name}: Unknown exception raised when sending torrent to'
                      ' {name}: {error}', {'name': self.name, 'error': error})
            return False

        if self.response.status_code == 401:
            log.error('{name}: Invalid Username or Password,'
                      ' check your config', {'name': self.name})
            return False

        code_description = http_code_description(self.response.status_code)

        if code_description is not None:
            log.info('{name}: {code}',
                     {'name': self.name, 'code': code_description})
            return False

        log.debug('{name}: Response to {method} request is {response}', {
            'name': self.name,
            'method': method.upper(),
            'response': self.response.text[0:1024] + '...' if len(self.response.text) > 1027 else self.response.text
        })

        return True

    def _get_auth(self):
        """Return the auth_id needed for the client."""
        raise NotImplementedError

    def _add_torrent_uri(self, result):
        """Return the True/False from the client when a torrent is added via url (magnet or .torrent link).

        :param result:
        :type result: medusa.classes.SearchResult
        """
        raise NotImplementedError

    def _add_torrent_file(self, result):
        """Return the True/False from the client when a torrent is added via result.content (only .torrent file).

        :param result:
        :type result: medusa.classes.SearchResult
        """
        raise NotImplementedError

    def _set_torrent_label(self, result):
        """Return the True/False from the client when a torrent is set with label.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_ratio(self, result):
        """Return the True/False from the client when a torrent is set with ratio.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_seed_time(self, result):
        """Return the True/False from the client when a torrent is set with a seed time.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_priority(self, result):
        """Return the True/False from the client when a torrent is set with result.priority (-1 = low, 0 = normal, 1 = high).

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_path(self, torrent_path):
        """Return the True/False from the client when a torrent is set with path.

        :param torrent_path:
        :type torrent_path: string
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_pause(self, result):
        """Return the True/False from the client when a torrent is set with pause.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    @staticmethod
    def _get_info_hash(result):

        if result.url.startswith('magnet:'):
            result.hash = re.findall(r'urn:btih:([\w]{32,40})', result.url)[0]
            if len(result.hash) == 32:
                result.hash = b16encode(b32decode(result.hash)).lower()
        else:

            try:
                # `bencode.bdecode` is monkeypatched in `medusa.init`
                torrent_bdecode = bdecode(result.content, allow_extra_data=True)
                info = torrent_bdecode['info']
                result.hash = sha1(bencode(info)).hexdigest()
            except (BencodeDecodeError, KeyError):
                log.warning(
                    'Unable to bdecode torrent. Invalid torrent: {name}. '
                    'Deleting cached result if exists', {'name': result.name}
                )
                cache_db_con = db.DBConnection('cache.db')
                cache_db_con.action(
                    'DELETE FROM [{provider}] '
                    'WHERE name = ? '.format(provider=result.provider.get_id()),
                    [result.name]
                )
            except Exception:
                log.error(traceback.format_exc())

        return result

    def send_torrent(self, result):
        """Add torrent to the client.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: str or bool
        """
        r_code = False

        log.debug('Calling {name} Client', {'name': self.name})

        if not self.auth:
            if not self._get_auth():
                log.warning('{name}: Authentication Failed', {'name': self.name})
                return r_code

        try:
            # Sets per provider seed ratio
            result.ratio = result.provider.seed_ratio()

            # lazy fix for now, I'm sure we already do this somewhere else too
            result = self._get_info_hash(result)

            if not result.hash:
                return False

            if result.url.startswith('magnet:'):
                r_code = self._add_torrent_uri(result)
            else:
                r_code = self._add_torrent_file(result)

            if not r_code:
                log.warning('{name}: Unable to send Torrent',
                            {'name': self.name})
                return False

            if not self._set_torrent_pause(result):
                log.error('{name}: Unable to set the pause for Torrent',
                          {'name': self.name})

            if not self._set_torrent_label(result):
                log.error('{name}: Unable to set the label for Torrent',
                          {'name': self.name})

            if not self._set_torrent_ratio(result):
                log.error('{name}: Unable to set the ratio for Torrent',
                          {'name': self.name})

            if not self._set_torrent_seed_time(result):
                log.error('{name}: Unable to set the seed time for Torrent',
                          {'name': self.name})

            if not self._set_torrent_path(result):
                log.error('{name}: Unable to set the path for Torrent',
                          {'name': self.name})

            if result.priority != 0 and not self._set_torrent_priority(result):
                log.error('{name}: Unable to set priority for Torrent',
                          {'name': self.name})

        except Exception as msg:
            log.error('{name}: Failed Sending Torrent',
                      {'name': self.name})
            log.debug('{name}: Exception raised when sending torrent {result}.'
                      ' Error: {error}',
                      {'name': self.name, 'result': result, 'error': msg})
            return r_code

        return r_code

    def test_authentication(self):
        """Test authentication.

        :return:
        :rtype: tuple(bool, str)
        """
        try:
            self.response = self.session.get(self.url, timeout=120, verify=False)
        except requests.exceptions.ConnectionError:
            return False, 'Error: {name} Connection Error'.format(name=self.name)
        except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL):
            return False, 'Error: Invalid {name} host'.format(name=self.name)

        if self.response.status_code == 401:
            return False, 'Error: Invalid {name} Username or Password, check your config!'.format(name=self.name)

        try:
            self._get_auth()
            if self.response.status_code == 200 and self.auth:
                return True, 'Success: Connected and Authenticated'
            else:
                return False, 'Error: Unable to get {name} Authentication, check your config!'.format(name=self.name)
        except Exception as error:
            return False, 'Unable to connect to {name}. Error: {msg}'.format(name=self.name, msg=error)

    def remove_torrent(self, info_hash):
        """Remove torrent from client using given info_hash.

        :param info_hash:
        :type info_hash: string
        :return
        :rtype: bool
        """
        raise NotImplementedError

    def remove_ratio_reached(self):
        """Remove all Medusa torrents that ratio was reached.

        It loops in all hashes returned from client and check if it is in the snatch history
        if its then it checks if we already processed media from the torrent (episode status `Downloaded`)
        If is a RARed torrent then we don't have a media file so we check if that hash is from an
        episode that has a `Downloaded` status
        """
        raise NotImplementedError
예제 #15
0
 def __init__(self):
     self.session = MedusaSession()
     self.url = 'https://api.pushbullet.com/v2/'
예제 #16
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
예제 #17
0
 def __init__(self):
     self.lock = threading.Lock()
     self.amActive = False
     self.session = MedusaSession()
     self.update_cache = UpdateCache()
예제 #18
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
예제 #19
0
파일: join.py 프로젝트: reconman/Medusa
 def __init__(self):
     """Init method."""
     self.session = MedusaSession()
     self.url = 'https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush?'
예제 #20
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)
예제 #21
0
파일: plex.py 프로젝트: pymedusa/SickRage
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')
예제 #22
0
 def __init__(self):
     """Initialize the class."""
     self.session = MedusaSession()
     self.url = 'https://new.boxcar.io/api/notifications'
예제 #23
0
파일: pushalot.py 프로젝트: zapru/Medusa
 def __init__(self):
     self.session = MedusaSession()
예제 #24
0
 def __init__(self):
     """Initialize the class."""
     self.session = MedusaSession()
     self.url = 'https://new.boxcar.io/api/notifications'
예제 #25
0
class CheckVersion(object):
    """Version check class meant to run as a thread object with the sr scheduler."""
    def __init__(self):
        self.updater = None
        self.install_type = None
        self.amActive = False
        self.install_type = self.find_install_type()
        if self.install_type == 'git':
            self.updater = GitUpdateManager()
        elif self.install_type == 'source':
            self.updater = SourceUpdateManager()

        self.session = MedusaSession()

    def run(self, force=False):

        self.amActive = True

        # Update remote branches and store in app.GIT_REMOTE_BRANCHES
        self.list_remote_branches()

        if self.updater:
            # set current branch version
            app.BRANCH = self.get_branch()

            if self.check_for_new_version(force):
                if app.AUTO_UPDATE:
                    log.info(u'New update found, starting auto-updater ...')
                    ui.notifications.message(
                        'New update found, starting auto-updater')
                    if self.run_backup_if_safe():
                        if app.version_check_scheduler.action.update():
                            log.info(u'Update was successful!')
                            ui.notifications.message('Update was successful')
                            app.events.put(app.events.SystemEvent.RESTART)
                        else:
                            log.info(u'Update failed!')
                            ui.notifications.message('Update failed!')

            self.check_for_new_news(force)

        self.amActive = False

    def run_backup_if_safe(self):
        return self.safe_to_update() is True and self._runbackup() is True

    def _runbackup(self):
        # Do a system backup before update
        log.info(u'Config backup in progress...')
        ui.notifications.message('Backup', 'Config backup in progress...')
        try:
            backupDir = os.path.join(app.DATA_DIR, app.BACKUP_DIR)
            if not os.path.isdir(backupDir):
                os.mkdir(backupDir)

            if self._keeplatestbackup(backupDir) and self._backup(backupDir):
                log.info(u'Config backup successful, updating...')
                ui.notifications.message(
                    'Backup', 'Config backup successful, updating...')
                return True
            else:
                log.warning(u'Config backup failed, aborting update')
                ui.notifications.message(
                    'Backup', 'Config backup failed, aborting update')
                return False
        except Exception as e:
            log.error(u'Update: Config backup failed. Error: {0!r}', e)
            ui.notifications.message('Backup',
                                     'Config backup failed, aborting update')
            return False

    @staticmethod
    def _keeplatestbackup(backupDir=None):
        if not backupDir:
            return False

        import glob
        files = glob.glob(os.path.join(backupDir, '*.zip'))
        if not files:
            return True

        now = time.time()
        newest = files[0], now - os.path.getctime(files[0])
        for f in files[1:]:
            age = now - os.path.getctime(f)
            if age < newest[1]:
                newest = f, age
        files.remove(newest[0])

        for f in files:
            os.remove(f)

        return True

    # TODO: Merge with backup in helpers
    @staticmethod
    def _backup(backupDir=None):
        if not backupDir:
            return False
        source = [
            os.path.join(app.DATA_DIR, app.APPLICATION_DB), app.CONFIG_FILE,
            os.path.join(app.DATA_DIR, app.FAILED_DB),
            os.path.join(app.DATA_DIR, app.CACHE_DB)
        ]
        target = os.path.join(
            backupDir,
            app.BACKUP_FILENAME.format(
                timestamp=time.strftime('%Y%m%d%H%M%S')))

        for (path, dirs, files) in os.walk(app.CACHE_DIR, topdown=True):
            for dirname in dirs:
                if path == app.CACHE_DIR and dirname not in ['images']:
                    dirs.remove(dirname)
            for filename in files:
                source.append(os.path.join(path, filename))

        return helpers.backup_config_zip(source, target, app.DATA_DIR)

    def safe_to_update(self):
        def db_safe(self):
            message = {
                'equal': {
                    'type':
                    DEBUG,
                    'text':
                    u'We can proceed with the update. New update has same DB version'
                },
                'upgrade': {
                    'type':
                    WARNING,
                    'text':
                    u"We can't proceed with the update. New update has a new DB version. Please manually update"
                },
                'downgrade': {
                    'type':
                    WARNING,
                    'text':
                    u"We can't proceed with the update. New update has a old DB version. It's not possible to downgrade"
                },
            }
            try:
                result = self.getDBcompare()
                if result in message:
                    log.log(
                        message[result]['type'], message[result]
                        ['text'])  # unpack the result message into a log entry
                else:
                    log.warning(
                        u"We can't proceed with the update. Unable to check remote DB version. Error: {0}",
                        result)
                return result in ['equal'
                                  ]  # add future True results to the list
            except Exception as error:
                log.error(
                    u"We can't proceed with the update. Unable to compare DB version. Error: {0!r}",
                    error)
                return False

        def postprocessor_safe():
            if not app.auto_post_processor_scheduler.action.amActive:
                log.debug(
                    u'We can proceed with the update. Post-Processor is not running'
                )
                return True
            else:
                log.debug(
                    u"We can't proceed with the update. Post-Processor is running"
                )
                return False

        def showupdate_safe():
            if not app.show_update_scheduler.action.amActive:
                log.debug(
                    u'We can proceed with the update. Shows are not being updated'
                )
                return True
            else:
                log.debug(
                    u"We can't proceed with the update. Shows are being updated"
                )
                return False

        db_safe = db_safe(self)
        postprocessor_safe = postprocessor_safe()
        showupdate_safe = showupdate_safe()

        if db_safe and postprocessor_safe and showupdate_safe:
            log.debug(u'Proceeding with auto update')
            return True
        else:
            log.debug(u'Auto update aborted')
            return False

    def getDBcompare(self):
        """
        Compare the current DB version with the new branch version.

        :return: 'upgrade', 'equal', or 'downgrade'
        """
        try:
            self.updater.need_update()
            cur_hash = str(self.updater.get_newest_commit_hash())
            assert len(
                cur_hash
            ) == 40, 'Commit hash wrong length: {length} hash: {hash}'.format(
                length=len(cur_hash), hash=cur_hash)

            check_url = 'http://cdn.rawgit.com/{org}/{repo}/{commit}/medusa/databases/main_db.py'.format(
                org=app.GIT_ORG, repo=app.GIT_REPO, commit=cur_hash)
            response = self.session.get(check_url)

            # Get remote DB version
            match_max_db = re.search(
                r'MAX_DB_VERSION\s*=\s*(?P<version>\d{2,3})', response.text)
            new_branch_major_db_version = int(
                match_max_db.group('version')) if match_max_db else None
            match_minor_db = re.search(
                r'CURRENT_MINOR_DB_VERSION\s*=\s*(?P<version>\d{1,2})',
                response.text)
            new_branch_min_db_version = int(
                match_minor_db.group('version')) if match_minor_db else None

            # Check local DB version
            main_db_con = db.DBConnection()
            cur_branch_major_db_version, cur_branch_minor_db_version = main_db_con.checkDBVersion(
            )

            if any([
                    cur_branch_major_db_version is None,
                    cur_branch_minor_db_version is None,
                    new_branch_major_db_version is None,
                    new_branch_min_db_version is None
            ]):
                return 'Could not compare database versions, aborting'

            if new_branch_major_db_version > cur_branch_major_db_version:
                return 'upgrade'
            elif new_branch_major_db_version == cur_branch_major_db_version:
                if new_branch_min_db_version < cur_branch_minor_db_version:
                    return 'downgrade'
                elif new_branch_min_db_version > cur_branch_minor_db_version:
                    return 'upgrade'
                return 'equal'
            else:
                return 'downgrade'
        except Exception as e:
            return repr(e)

    @staticmethod
    def find_install_type():
        """
        Determines how this copy of sr was installed.

        :return: type of installation. Possible values are:
            'win': any compiled windows build
            'git': running from source using git
            'source': running from source without git
        """
        # check if we're a windows build
        if app.BRANCH.startswith('build '):
            install_type = 'win'
        elif os.path.isdir(os.path.join(app.PROG_DIR, u'.git')):
            install_type = 'git'
        else:
            install_type = 'source'

        return install_type

    def check_for_new_version(self, force=False):
        """
        Check the internet for a newer version.

        :force: if true the VERSION_NOTIFY setting will be ignored and a check will be forced
        :return: bool, True for new version or False for no new version.
        """
        if not self.updater or (not app.VERSION_NOTIFY and not app.AUTO_UPDATE
                                and not force):
            log.info(
                u'Version checking is disabled, not checking for the newest version'
            )
            app.NEWEST_VERSION_STRING = None
            return False

        # checking for updates
        if not app.AUTO_UPDATE:
            log.info(u'Checking for updates using {0}',
                     self.install_type.upper())

        if not self.updater.need_update():
            app.NEWEST_VERSION_STRING = None

            if force:
                ui.notifications.message('No update needed')
                log.info(u'No update needed')

            # no updates needed
            return False

        # found updates
        self.updater.set_newest_text()
        return self.updater.can_update()

    def check_for_new_news(self, force=False):
        """
        Check GitHub for the latest news.

        :return: unicode, a copy of the news
        :force: ignored
        """
        # Grab a copy of the news
        log.debug(u'check_for_new_news: Checking GitHub for latest news.')
        try:
            news = self.session.get(app.NEWS_URL).text
        except Exception:
            log.warning(u'check_for_new_news: Could not load news from repo.')
            news = ''

        if not news:
            return ''

        try:
            last_read = datetime.datetime.strptime(app.NEWS_LAST_READ,
                                                   '%Y-%m-%d')
        except Exception:
            last_read = 0

        app.NEWS_UNREAD = 0
        gotLatest = False
        for match in re.finditer(r'^####\s*(\d{4}-\d{2}-\d{2})\s*####', news,
                                 re.M):
            if not gotLatest:
                gotLatest = True
                app.NEWS_LATEST = match.group(1)

            try:
                if datetime.datetime.strptime(match.group(1),
                                              '%Y-%m-%d') > last_read:
                    app.NEWS_UNREAD += 1
            except Exception:
                pass

        return news

    def need_update(self):
        if self.updater:
            return self.updater.need_update()

    def update(self):
        if self.updater:
            # update branch with current config branch value
            self.updater.branch = app.BRANCH

            # check for updates
            if self.updater.need_update():
                return self.updater.update()

    def list_remote_branches(self):
        if self.updater:
            app.GIT_REMOTE_BRANCHES = self.updater.list_remote_branches()
        return app.GIT_REMOTE_BRANCHES

    def get_branch(self):
        if self.updater:
            return self.updater.branch
예제 #26
0
 def __init__(self):
     self.lock = threading.Lock()
     self.amActive = False
     self.session = MedusaSession()
예제 #27
0
 def __init__(self):
     self.session = MedusaSession()
예제 #28
0
}

indexerConfig = {
    INDEXER_TVDBV2: {
        'enabled':
        True,
        'id':
        INDEXER_TVDBV2,
        'name':
        'TVDBv2',
        'module':
        TVDBv2,
        'api_params': {
            'language': 'en',
            'use_zip': True,
            'session': MedusaSession(cache_control={'cache_etags': False}),
        },
        'xem_origin':
        'tvdb',
        'icon':
        'thetvdb16.png',
        'scene_loc':
        '{base_url}/scene_exceptions/scene_exceptions_tvdb.json'.format(
            base_url=BASE_PYMEDUSA_URL),
        'base_url':
        'https://api.thetvdb.com/',
        'show_url':
        'http://thetvdb.com/?tab=series&id=',
        'mapped_to':
        'tvdb_id',  # The attribute to which other indexers can map there thetvdb id to
        'identifier':
예제 #29
0
파일: plex.py 프로젝트: reconman/Medusa
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')
예제 #30
0
파일: emby.py 프로젝트: pymedusa/SickRage
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
예제 #31
0
파일: emby.py 프로젝트: neo62248/Medusa
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
예제 #32
0
 def __init__(self):
     self.session = MedusaSession()
     self.url = 'https://api.pushbullet.com/v2/'
예제 #33
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
예제 #34
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
예제 #35
0
파일: pushalot.py 프로젝트: zapru/Medusa
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
예제 #36
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
예제 #37
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)
예제 #38
0
class GenericClient(object):
    """Base class for all torrent clients."""
    def __init__(self, name, host=None, username=None, password=None):
        """Constructor.

        :param name:
        :type name: string
        :param host:
        :type host: string
        :param username:
        :type username: string
        :param password:
        :type password: string
        """
        self.name = name
        self.username = app.TORRENT_USERNAME if username is None else username
        self.password = app.TORRENT_PASSWORD if password is None else password
        self.host = app.TORRENT_HOST if host is None else host
        self.rpcurl = app.TORRENT_RPCURL
        self.url = None
        self.response = None
        self.auth = None
        self.last_time = time.time()
        self.session = MedusaSession()
        self.session.auth = (self.username, self.password)

    def _request(self,
                 method='get',
                 params=None,
                 data=None,
                 files=None,
                 cookies=None):

        if time.time() > self.last_time + 1800 or not self.auth:
            self.last_time = time.time()
            self._get_auth()

        text_params = str(params)
        text_data = str(data)
        log.debug(
            '{name}: Requested a {method} connection to {url} with'
            ' params: {params} Data: {data}', {
                'name':
                self.name,
                'method':
                method.upper(),
                'url':
                self.url,
                'params':
                text_params[0:99] +
                '...' if len(text_params) > 102 else text_params,
                'data':
                text_data[0:99] + '...' if len(text_data) > 102 else text_data
            })

        if not self.auth:
            log.warning('{name}: Authentication Failed', {'name': self.name})

            return False
        try:
            self.response = self.session.request(method,
                                                 self.url,
                                                 params=params,
                                                 data=data,
                                                 files=files,
                                                 cookies=cookies,
                                                 timeout=120,
                                                 verify=False)
        except requests.exceptions.ConnectionError as msg:
            log.error('{name}: Unable to connect {error}', {
                'name': self.name,
                'error': msg
            })
            return False
        except (requests.exceptions.MissingSchema,
                requests.exceptions.InvalidURL):
            log.error('{name}: Invalid Host', {'name': self.name})
            return False
        except requests.exceptions.HTTPError as msg:
            log.error('{name}: Invalid HTTP Request {error}', {
                'name': self.name,
                'error': msg
            })
            return False
        except requests.exceptions.Timeout as msg:
            log.warning('{name}: Connection Timeout {error}', {
                'name': self.name,
                'error': msg
            })
            return False
        except Exception as msg:
            log.error(
                '{name}: Unknown exception raised when send torrent to'
                ' {name} : {error}', {
                    'name': self.name,
                    'error': msg
                })
            return False

        if self.response.status_code == 401:
            log.error(
                '{name}: Invalid Username or Password,'
                ' check your config', {'name': self.name})
            return False

        code_description = http_code_description(self.response.status_code)

        if code_description is not None:
            log.info('{name}: {code}', {
                'name': self.name,
                'code': code_description
            })
            return False

        log.debug(
            '{name}: Response to {method} request is {response}', {
                'name':
                self.name,
                'method':
                method.upper(),
                'response':
                self.response.text[0:1024] +
                '...' if len(self.response.text) > 1027 else self.response.text
            })

        return True

    def _get_auth(self):
        """Return the auth_id needed for the client."""
        raise NotImplementedError

    def _add_torrent_uri(self, result):
        """Return the True/False from the client when a torrent is added via url (magnet or .torrent link).

        :param result:
        :type result: medusa.classes.SearchResult
        """
        raise NotImplementedError

    def _add_torrent_file(self, result):
        """Return the True/False from the client when a torrent is added via result.content (only .torrent file).

        :param result:
        :type result: medusa.classes.SearchResult
        """
        raise NotImplementedError

    def _set_torrent_label(self, result):
        """Return the True/False from the client when a torrent is set with label.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_ratio(self, result):
        """Return the True/False from the client when a torrent is set with ratio.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_seed_time(self, result):
        """Return the True/False from the client when a torrent is set with a seed time.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_priority(self, result):
        """Return the True/False from the client when a torrent is set with result.priority (-1 = low, 0 = normal, 1 = high).

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_path(self, torrent_path):
        """Return the True/False from the client when a torrent is set with path.

        :param torrent_path:
        :type torrent_path: string
        :return:
        :rtype: bool
        """
        return True

    def _set_torrent_pause(self, result):
        """Return the True/False from the client when a torrent is set with pause.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: bool
        """
        return True

    @staticmethod
    def _get_info_hash(result):

        if result.url.startswith('magnet:'):
            result.hash = re.findall(r'urn:btih:([\w]{32,40})', result.url)[0]
            if len(result.hash) == 32:
                result.hash = b16encode(b32decode(result.hash)).lower()
        else:

            try:
                torrent_bdecode = bdecode(result.content)
                info = torrent_bdecode['info']
                result.hash = sha1(bencode(info)).hexdigest()
            except (BTFailure, KeyError):
                log.warning(
                    'Unable to bdecode torrent. Invalid torrent: {name}. '
                    'Deleting cached result if exists', {'name': result.name})
                cache_db_con = db.DBConnection('cache.db')
                cache_db_con.action(
                    b'DELETE FROM [{provider}] '
                    b'WHERE name = ? '.format(
                        provider=result.provider.get_id()), [result.name])
            except Exception:
                log.error(traceback.format_exc())

        return result

    def send_torrent(self, result):
        """Add torrent to the client.

        :param result:
        :type result: medusa.classes.SearchResult
        :return:
        :rtype: str or bool
        """
        r_code = False

        log.debug('Calling {name} Client', {'name': self.name})

        if not self.auth:
            if not self._get_auth():
                log.warning('{name}: Authentication Failed',
                            {'name': self.name})
                return r_code

        try:
            # Sets per provider seed ratio
            result.ratio = result.provider.seed_ratio()

            # lazy fix for now, I'm sure we already do this somewhere else too
            result = self._get_info_hash(result)

            if not result.hash:
                return False

            if result.url.startswith('magnet:'):
                r_code = self._add_torrent_uri(result)
            else:
                r_code = self._add_torrent_file(result)

            if not r_code:
                log.warning('{name}: Unable to send Torrent',
                            {'name': self.name})
                return False

            if not self._set_torrent_pause(result):
                log.error('{name}: Unable to set the pause for Torrent',
                          {'name': self.name})

            if not self._set_torrent_label(result):
                log.error('{name}: Unable to set the label for Torrent',
                          {'name': self.name})

            if not self._set_torrent_ratio(result):
                log.error('{name}: Unable to set the ratio for Torrent',
                          {'name': self.name})

            if not self._set_torrent_seed_time(result):
                log.error('{name}: Unable to set the seed time for Torrent',
                          {'name': self.name})

            if not self._set_torrent_path(result):
                log.error('{name}: Unable to set the path for Torrent',
                          {'name': self.name})

            if result.priority != 0 and not self._set_torrent_priority(result):
                log.error('{name}: Unable to set priority for Torrent',
                          {'name': self.name})

        except Exception as msg:
            log.error('{name}: Failed Sending Torrent', {'name': self.name})
            log.debug(
                '{name}: Exception raised when sending torrent {result}.'
                ' Error: {error}', {
                    'name': self.name,
                    'result': result,
                    'error': msg
                })
            return r_code

        return r_code

    def test_authentication(self):
        """Test authentication.

        :return:
        :rtype: tuple(bool, str)
        """
        try:
            self.response = self.session.get(self.url,
                                             timeout=120,
                                             verify=False)
        except requests.exceptions.ConnectionError:
            return False, 'Error: {name} Connection Error'.format(
                name=self.name)
        except (requests.exceptions.MissingSchema,
                requests.exceptions.InvalidURL):
            return False, 'Error: Invalid {name} host'.format(name=self.name)

        if self.response.status_code == 401:
            return False, 'Error: Invalid {name} Username or Password, check your config!'.format(
                name=self.name)

        try:
            self._get_auth()
            if self.response.status_code == 200 and self.auth:
                return True, 'Success: Connected and Authenticated'
            else:
                return False, 'Error: Unable to get {name} Authentication, check your config!'.format(
                    name=self.name)
        except Exception as error:
            return False, 'Unable to connect to {name}. Error: {msg}'.format(
                name=self.name, msg=error)

    def remove_torrent(self, info_hash):
        """Remove torrent from client using given info_hash.

        :param info_hash:
        :type info_hash: string
        :return
        :rtype: bool
        """
        raise NotImplementedError

    def remove_ratio_reached(self):
        """Remove all Medusa torrents that ratio was reached.

        It loops in all hashes returned from client and check if it is in the snatch history
        if its then it checks if we already processed media from the torrent (episode status `Downloaded`)
        If is a RARed torrent then we don't have a media file so we check if that hash is from an
        episode that has a `Downloaded` status
        """
        raise NotImplementedError
예제 #39
0
)
from medusa.cache import recommended_series_cache
from medusa.helpers import ensure_list
from medusa.imdb import Imdb
from medusa.indexers.utils import indexer_id_to_name, indexer_name_mapping
from medusa.logger.adapters.style import BraceAdapter
from medusa.session.core import MedusaSession

from simpleanidb import Anidb

from six import PY2, ensure_text, text_type

log = BraceAdapter(logging.getLogger(__name__))
log.logger.addHandler(logging.NullHandler())

session = MedusaSession()


class LazyApi(object):
    """Decorators to lazily construct API classes."""

    imdb_api = None
    anidb_api = None

    @classmethod
    def load_anidb_api(cls, func):
        """
        Decorate a function to lazy load the anidb_api.

        We need to do this, because we're passing the Medusa cache location to the lib. As the module is imported before
        the app.CACHE_DIR location has been read, we can't initialize it at module level.
예제 #40
0
 def __init__(self):
     """Show updatere constructor."""
     self.lock = threading.Lock()
     self.amActive = False
     self.session = MedusaSession()
     self.update_cache = UpdateCache()