Пример #1
0
 def test_update_library(self, host=None, username=None, password=None):
     self._testing = True
     result = self.update_library(host=unquote_plus(host),
                                  username=username,
                                  password=password)
     if '<br>' == result:
         result += 'Fail: No valid host set to connect with'
     return (
         ('Test result for', 'Successful test of')['Fail' not in result] +
         ' Plex server(s) ... %s<br>\n' % result)
Пример #2
0
    def _notify(self, title, body, hosts=None, username=None, password=None, **kwargs):
        """ Internal wrapper for the notify_snatch and notify_download functions

        Call either the JSON-RPC over HTTP or the legacy HTTP API methods depending on the Kodi API version.

        Args:
            title: Title of the notice to send
            body: Message body of the notice to send

        Return:
            A list of results in the format of host:ip:result, where result will either be 'OK' or False.
        """
        self.username, self.password = username, password

        title = title or 'SickGear'

        hosts = self._choose(hosts, sickbeard.KODI_HOST)

        success = True
        message = []
        for host in [x.strip() for x in hosts.split(',')]:
            cur_host = unquote_plus(host)

            api_version = self._get_kodi_version(cur_host)
            if self.response and 401 == self.response.get('status_code'):
                success = False
                message += ['Fail: Cannot authenticate with %s' % cur_host]
                self._log_debug(u'Failed to authenticate with %s' % cur_host)
            elif not api_version:
                success = False
                message += ['Fail: No supported Kodi found at %s' % cur_host]
                self._maybe_log_failed_detection(cur_host, 'connect and detect version for')
            else:
                if 4 >= api_version:
                    self._log_debug(u'Detected %sversion <= 11, using HTTP API'
                                    % self.prefix and ' ' + self.prefix.capitalize())
                    __method_send = self._send
                    command = dict(command='ExecBuiltIn',
                                   parameter='Notification(%s,%s)' % (title, body))
                else:
                    self._log_debug(u'Detected version >= 12, using JSON API')
                    __method_send = self._send_json
                    command = dict(method='GUI.ShowNotification', params=dict(
                        [('title', title), ('message', body), ('image', self._sg_logo_url)]
                        + ([], [('displaytime', 8000)])[self._testing]))

                response_notify = __method_send(cur_host, command, 10)
                if response_notify:
                    message += ['%s: %s' % ((response_notify, 'OK')['OK' in response_notify], cur_host)]

        return self._choose(('Success, all hosts tested', '<br />\n'.join(message))[not success], success)
Пример #3
0
    def _notify(self,
                title,
                body,
                host=None,
                username=None,
                password=None,
                **kwargs):
        """Internal wrapper for the notify_snatch and notify_download functions

        Args:
            title: Title of the notice to send
            body: Message body of the notice to send
            host: Plex Media Client(s) host:port
            username: Plex username
            password: Plex password

        Returns:
            Returns a test result string for ui output while testing, otherwise True if all tests are a success
        """
        host = self._choose(host, sickbeard.PLEX_HOST)
        username = self._choose(username, sickbeard.PLEX_USERNAME)
        password = self._choose(password, sickbeard.PLEX_PASSWORD)

        command = {
            'command':
            'ExecBuiltIn',
            'parameter':
            'Notification(%s,%s)' %
            (title.encode('utf-8'), body.encode('utf-8'))
        }

        results = []
        for cur_host in [x.strip() for x in host.split(',')]:
            cur_host = unquote_plus(cur_host)
            self._log(u'Sending notification to \'%s\'' % cur_host)
            result = self._send_to_plex(command, cur_host, username, password)
            results += [
                self._choose(('%s Plex client ... %s' %
                              (('Successful test notice sent to',
                                'Failed test for')[not result], cur_host)),
                             result)
            ]

        return self._choose('<br>\n'.join(results), all(results))
Пример #4
0
    def _tinf(self, ids=None, use_props=True, err=False):
        # type: (Optional[list], bool, bool) -> list
        """
        Fetch client task information
        :param ids: Optional id(s) to get task info for. None to get all task info
        :param use_props: Optional override forces retrieval of torrents info instead of torrent generic properties
        :param err: Optional return error dict instead of empty array
        :return: Zero or more task object(s) from response
        """
        result = []
        rids = (ids if isinstance(ids, (list, type(None))) else
                [x.strip() for x in ids.split(',')]) or [None]
        getinfo = use_props and None is not ids
        params = {}
        cmd = ('torrents/info', 'query/torrents')[not self.api_ns]
        if not getinfo:
            label = sickbeard.TORRENT_LABEL.replace(' ', '_')
            if label and not ids:
                params['category'] = label
        for rid in rids:
            if getinfo:
                if self.api_ns:
                    cmd = 'torrents/properties'
                    params['hash'] = rid
                else:
                    cmd = 'query/propertiesGeneral/%s' % rid
            elif rid:
                params['hashes'] = rid
            try:
                tasks = self._client_request(cmd,
                                             params=params,
                                             timeout=60,
                                             json=True)
                result += tasks and (isinstance(tasks, list) and tasks or (isinstance(tasks, dict) and [tasks])) \
                    or ([], [{'state': 'error', 'hash': rid}])[err]
            except (BaseException, Exception):
                if getinfo:
                    result += [dict(error=True, id=rid)]
        for t in filter_iter(
                lambda d: isinstance(d.get('name'), string_types) and d.get(
                    'name'), (result, [])[getinfo]):
            t['name'] = unquote_plus(t.get('name'))

        return result
Пример #5
0
    def _tinf(self, ids=None, err=False):
        # type: (Optional[list], bool) -> list
        """
        Fetch client task information
        :param ids: Optional id(s) to get task info for. None to get all task info
        :param err: Optional return error dict instead of empty array
        :return: Zero or more task object(s) from response
        """
        result = []
        rids = (ids if isinstance(ids, (list, type(None))) else
                [x.strip() for x in ids.split(',')]) or [None]
        getinfo = None is not ids
        for rid in rids:
            try:
                if not self._testmode:
                    # noinspection PyTypeChecker
                    tasks = self._client_request(
                        ('list', 'getinfo')[getinfo],
                        t_id=rid,
                        t_params=dict(additional='detail,file,transfer'
                                      ))['data']['tasks']
                else:
                    # noinspection PyUnresolvedReferences
                    tasks = (filter_list(lambda d: d.get('id') == rid,
                                         self._testdata),
                             self._testdata)[not rid]
                result += tasks and (isinstance(tasks, list) and tasks or (isinstance(tasks, dict) and [tasks])) \
                    or ([], [{'error': True, 'id': rid}])[err]
            except (BaseException, Exception):
                if getinfo:
                    result += [dict(error=True, id=rid)]
        for t in filter_iter(
                lambda d: isinstance(d.get('title'), string_types) and d.get(
                    'title'), result):
            t['title'] = unquote_plus(t.get('title'))

        return result
Пример #6
0
    def _search_provider(self, search_params, **kwargs):

        results = []
        if not self._authorised():
            return results

        items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}

        rc = dict([(k, re.compile('(?i)' + v))
                   for (k, v) in iteritems({
                       'show_id': r'"show\?id=(\d+)[^>]+>([^<]+)<\/a>',
                       'get': 'load_torrent'
                   })])
        search_types = sorted([x for x in iteritems(search_params)],
                              key=lambda tup: tup[0],
                              reverse=True)
        maybe_only = search_types[0][0]
        show_detail = '_only' in maybe_only and search_params.pop(
            maybe_only)[0] or ''
        for mode in search_params:
            for search_string in search_params[mode]:
                if 'Cache' == mode:
                    search_url = self.urls['browse']
                    html = self.get_url(search_url)
                    if self.should_skip():
                        return results
                else:
                    search_string = unidecode(search_string)
                    search_string = search_string.replace(show_detail,
                                                          '').strip()
                    search_url = self.urls['search'] % search_string
                    html = self.get_url(search_url)
                    if self.should_skip():
                        return results

                    shows = rc['show_id'].findall(html)
                    if any(shows):
                        html = ''
                        for show in set(shows):
                            sid, title = show
                            if title in unquote_plus(search_string):
                                html and time.sleep(1.1)
                                html += self.get_url(self.urls['show'] % sid)
                                if self.should_skip():
                                    return results

                cnt = len(items[mode])
                try:
                    if not html or self._has_no_results(html):
                        raise generic.HaltParseException

                    with BS4Parser(html) as tbl:
                        tbl_rows = tbl.tbody and tbl.tbody.find_all(
                            'tr') or tbl.table and tbl.table.find_all('tr')

                        if 2 > len(tbl_rows or []):
                            raise generic.HaltParseException

                        head = None
                        for tr in tbl_rows[0:]:
                            cells = tr.find_all('td')
                            if 4 > len(cells):
                                continue
                            try:
                                head = head if None is not head else self._header_row(
                                    tr)
                                stats = cells[head['leech']].get_text().strip()
                                seeders, leechers = [
                                    (try_int(x[0], 0), try_int(x[1], 0))
                                    for x in re.findall(
                                        r'(?::(\d+))(?:\W*[/]\W*:(\d+))?',
                                        stats) if x[0]
                                ][0]
                                if self._reject_item(seeders, leechers):
                                    continue
                                sizes = [
                                    (try_int(x[0], x[0]), try_int(x[1], False))
                                    for x in re.findall(
                                        r'([\d.]+\w+)?(?:\s*[(\[](\d+)[)\]])?',
                                        stats) if x[0]
                                ][0]
                                size = sizes[(0, 1)[1 < len(sizes)]]

                                for element in [
                                        x for x in cells[2].contents[::-1]
                                        if text_type(x).strip()
                                ]:
                                    if 'NavigableString' in str(
                                            element.__class__):
                                        title = text_type(element).strip()
                                        break

                                download_url = self._link(
                                    tr.find('a', href=rc['get'])['href'])
                            except (AttributeError, TypeError, ValueError):
                                continue

                            if title and download_url:
                                items[mode].append(
                                    (title, download_url, seeders,
                                     self._bytesizer(size)))

                except generic.HaltParseException:
                    pass
                except (BaseException, Exception):
                    logger.log(
                        u'Failed to parse. Traceback: %s' %
                        traceback.format_exc(), logger.ERROR)
                self._log_search(mode, len(items[mode]) - cnt, search_url)

            results = self._sort_seeding(mode, results + items[mode])

        return results
Пример #7
0
    def _update_json(self, host=None, show_name=None):
        """ Handle updating Kodi host via HTTP JSON-RPC

        Update the video library for a specific tv show if passed, otherwise update the whole library if option enabled.

        Args:
            show_name: Name of a TV show to target for a library update

        Return:
            True or False
        """

        if not host:
            self._log_warning(u'No host specified, aborting update')
            return False

        # if we're doing per-show
        if show_name:
            self._log_debug(u'JSON library update. Host: %s Show: %s' %
                            (host, show_name))

            # try fetching tvshowid using show_name with a fallback to getting show list
            show_name = unquote_plus(show_name)
            commands = [
                dict(method='VideoLibrary.GetTVShows',
                     params={
                         'filter': {
                             'field': 'title',
                             'operator': 'is',
                             'value': '%s' % show_name
                         },
                         'properties': ['title']
                     }),
                dict(method='VideoLibrary.GetTVShows')
            ]

            shows = None
            for command in commands:
                response = self._send_json(host, command)
                shows = response.get('tvshows')
                if shows:
                    break

            if not shows:
                self._log_debug(u'No items in GetTVShows response')
                return False

            tvshowid = -1
            path = ''
            # noinspection PyTypeChecker
            for show in shows:
                if show_name == show.get('title') or show_name == show.get(
                        'label'):
                    tvshowid = show.get('tvshowid', -1)
                    path = show.get('file', '')
                    break
            del shows

            # we didn't find the show (exact match), thus revert to just doing a full update if enabled
            if -1 == tvshowid:
                self._log_debug(
                    u'Doesn\'t have "%s" in it\'s known shows, full library update required'
                    % show_name)
                return False

            # lookup tv-show path if we don't already know it
            if not len(path):
                command = dict(method='VideoLibrary.GetTVShowDetails',
                               params={
                                   'tvshowid': tvshowid,
                                   'properties': ['file']
                               })
                response = self._send_json(host, command)
                path = 'tvshowdetails' in response and response[
                    'tvshowdetails'].get('file', '') or ''

            if not len(path):
                self._log_warning(
                    u'No valid path found for %s with ID: %s on %s' %
                    (show_name, tvshowid, host))
                return False

            self._log_debug(u'Updating %s on %s at %s' %
                            (show_name, host, path))
            command = dict(method='VideoLibrary.Scan',
                           params={
                               'directory':
                               '%s' %
                               json.dumps(path)[1:-1].replace('\\\\', '\\')
                           })
            response_scan = self._send_json(host, command)
            if not response_scan.get('OK'):
                self._log_error(
                    u'Update of show directory failed for %s on %s at %s response: %s'
                    % (show_name, host, path, response_scan))
                return False

        # do a full update if requested
        else:
            self._log_debug(u'Full library update on host: %s' % host)
            response_scan = self._send_json(host,
                                            dict(method='VideoLibrary.Scan'))
            if not response_scan.get('OK'):
                self._log_error(
                    u'Failed full library update on: %s response: %s' %
                    (host, response_scan))
                return False

        return True
Пример #8
0
    def _search_provider(self, search_params, **kwargs):

        results = []
        if not self._authorised():
            return results

        items = {'Cache': [], 'Season': [], 'Episode': [], 'Propers': []}

        rc = dict([(k, re.compile('(?i)' + v))
                   for (k, v) in iteritems({
                       'info': r'/torrents?/(?P<tid>(?P<tid_num>\d{2,})[^"]*)',
                       'get': 'download'
                   })])
        for mode in search_params:
            for search_string in search_params[mode]:
                search_string = unidecode(unquote_plus(search_string))

                vals = [i for i in range(5, 16)]
                random.SystemRandom().shuffle(vals)
                attempts = html = soup = tbl = None
                fetch = 'failed fetch'
                for attempts, s in enumerate((0, vals[0], vals[5], vals[10])):
                    time.sleep(s)
                    html = self.get_url(self.urls['search'] %
                                        (search_string, self._token))
                    if self.should_skip():
                        return results
                    if html:
                        try:
                            soup = BS4Parser(html).soup
                            tbl = soup.find('table', class_='table')
                            if tbl:
                                fetch = 'data fetched'
                                break
                        except (BaseException, Exception):
                            pass
                if attempts:
                    logger.log('%s %s after %s attempts' %
                               (mode, fetch, attempts + 1))

                cnt = len(items[mode])
                try:
                    if not html or self._has_no_results(html) or not tbl:
                        raise generic.HaltParseException

                    tbl_rows = tbl.find_all('tr')

                    if 2 > len(tbl_rows):
                        raise generic.HaltParseException

                    head = None
                    for tr in tbl_rows[1:]:
                        cells = tr.find_all('td')
                        if 6 > len(cells):
                            continue
                        try:
                            head = head if None is not head else self._header_row(
                                tr)
                            seeders, leechers, size = [
                                try_int(n, n) for n in [
                                    cells[head[x]].get_text().strip()
                                    for x in ('seed', 'leech', 'size')
                                ]
                            ]
                            if self._reject_item(
                                    seeders, leechers, self.freeleech and
                                (None is tr.find('i', class_='fa-star'))):
                                continue

                            title = tr.find(
                                'a', href=rc['info']).get_text().strip()
                            download_url = self._link(
                                tr.find('a', href=rc['get'])['href'])
                        except (BaseException, Exception):
                            continue

                        try:
                            titles = self.regulate_title(
                                title, mode, search_string)
                            if download_url and titles:
                                for title in titles:
                                    items[mode].append(
                                        (title, download_url, seeders,
                                         self._bytesizer(size)))
                        except (BaseException, Exception):
                            pass

                except generic.HaltParseException:
                    pass
                except (BaseException, Exception):
                    logger.log(
                        u'Failed to parse. Traceback: %s' %
                        traceback.format_exc(), logger.ERROR)

                if soup:
                    soup.clear(True)
                    del soup

                self._log_search(mode,
                                 len(items[mode]) - cnt,
                                 ('search string: ' +
                                  search_string.replace('%', '%%'),
                                  self.name)['Cache' == mode])

                if mode in 'Season' and len(items[mode]):
                    break

            results = self._sort_seeding(mode, results + items[mode])

        return results
Пример #9
0
    def _notify(self,
                title,
                body,
                hosts=None,
                username=None,
                password=None,
                **kwargs):
        """Internal wrapper for the notify_snatch and notify_download functions

        Detects JSON-RPC version then branches the logic for either the JSON-RPC or legacy HTTP API methods.

        Args:
            title: Title of the notice to send
            body: Message body of the notice to send
            hosts: XBMC webserver host:port
            username: XBMC webserver username
            password: XBMC webserver password

        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.

        """
        hosts = self._choose(hosts, sickbeard.XBMC_HOST)
        username = self._choose(username, sickbeard.XBMC_USERNAME)
        password = self._choose(password, sickbeard.XBMC_PASSWORD)

        success = False
        result = []
        for cur_host in [x.strip() for x in hosts.split(',')]:
            cur_host = unquote_plus(cur_host)

            self._log(u'Sending notification to "%s"' % cur_host)

            xbmcapi = self._get_xbmc_version(cur_host, username, password)
            if xbmcapi:
                if 4 >= xbmcapi:
                    self._log_debug(u'Detected version <= 11, using HTTP API')
                    command = dict(command='ExecBuiltIn',
                                   parameter='Notification(' +
                                   title.encode('utf-8') + ',' +
                                   body.encode('utf-8') + ')')
                    notify_result = self._send_to_xbmc(command, cur_host,
                                                       username, password)
                    if notify_result:
                        result += [cur_host + ':' + str(notify_result)]
                        success |= 'OK' in notify_result or success
                else:
                    self._log_debug(u'Detected version >= 12, using JSON API')
                    command = '{"jsonrpc":"2.0","method":"GUI.ShowNotification",' \
                              '"params":{"title":"%s","message":"%s", "image": "%s"},"id":1}' % \
                              (title.encode('utf-8'), body.encode('utf-8'), self._sg_logo_url)
                    notify_result = self._send_to_xbmc_json(
                        command, cur_host, username, password)
                    if notify_result.get('result'):
                        result += [
                            cur_host + ':' +
                            decode_str(notify_result['result'],
                                       sickbeard.SYS_ENCODING)
                        ]
                        success |= 'OK' in notify_result or success
            else:
                if sickbeard.XBMC_ALWAYS_ON or self._testing:
                    self._log_error(
                        u'Failed to detect version for "%s", check configuration and try again'
                        % cur_host)
                result += [cur_host + ':No response']
                success = False

        return self._choose(('Success, all hosts tested',
                             '<br />\n'.join(result))[not bool(success)],
                            bool(success))