Example #1
0
def ex(e):
    """
    :param e: The exception to convert into a six.text_type string
    :return: A six.text_type string from the exception text if it exists
    """

    message = ''

    if not e or not e.args:
        return message

    for arg in e.args:
        if arg is not None:
            if isinstance(arg, six.string_types):
                fixed_arg = ss(arg)
            else:
                try:
                    fixed_arg = 'error {0}'.format(ss(str(arg)))
                except Exception:
                    fixed_arg = None

            if fixed_arg:
                if not message:
                    message = fixed_arg
                else:
                    try:
                        message = '{0} : {1}'.format(message, fixed_arg)
                    except UnicodeError:
                        message = '{0} : {1}'.format(
                            six.text_type(message, errors='replace'),
                            six.text_type(fixed_arg, errors='replace'))

    return message
Example #2
0
    def _parseEp(ep_name):
        ep_name = ss(ep_name)

        sep = ' - '
        titles = ep_name.split(sep)
        logger.log('TITLES: {0}'.format(titles), logger.DEBUG)
        return titles
Example #3
0
    def test_email(self):
        """
        Test email notifications
        """
        email_notifier = EmailNotifier()

        # Per-show-email notifications were added early on and utilized a different format than the other notifiers.
        # Therefore, to test properly (and ensure backwards compatibility), this routine will test shows that use
        # both the old and the new storage methodology
        legacy_test_emails = "[email protected],[email protected],[email protected]"
        test_emails = "[email protected],[email protected],[email protected]"

        for show in self.legacy_shows:
            showid = self._get_showid_by_showname(show.name)
            self.mydb.action("UPDATE tv_shows SET notify_list = ? WHERE show_id = ?", [legacy_test_emails, showid])

        for show in self.shows:
            showid = self._get_showid_by_showname(show.name)
            Home.saveShowNotifyList(show=showid, emails=test_emails)

        # Now, iterate through all shows using the email list generation routines that are used in the notifier proper
        shows = self.legacy_shows + self.shows
        for show in shows:
            for episode in show.episodes:
                ep_name = ss(episode._format_pattern('%SN - %Sx%0E - %EN - ') + episode.quality)  # pylint: disable=protected-access
                show_name = email_notifier._parseEp(ep_name)  # pylint: disable=protected-access
                recipients = email_notifier._generate_recipients(show_name)  # pylint: disable=protected-access
                self._debug_spew("- Email Notifications for " + show.name + " (episode: " + episode.name + ") will be sent to:")
                for email in recipients:
                    self._debug_spew("-- " + email.strip())
                self._debug_spew("\n\r")

        return True
Example #4
0
    def test_prowl(self):
        """
        Test prowl notifications
        """
        prowl_notifier = ProwlNotifier()

        # Prowl per-show-notifications only utilize the new methodology for storage; therefore, the list of legacy_shows
        # will not be altered (to preserve backwards compatibility testing)
        test_prowl_apis = "11111111111111111111,22222222222222222222"

        for show in self.shows:
            showid = self._get_showid_by_showname(show.name)
            Home.saveShowNotifyList(show=showid, prowlAPIs=test_prowl_apis)

        # Now, iterate through all shows using the Prowl API generation routines that are used in the notifier proper
        for show in self.shows:
            for episode in show.episodes:
                ep_name = ss(episode._format_pattern('%SN - %Sx%0E - %EN - ') + episode.quality)  # pylint: disable=protected-access
                show_name = prowl_notifier._parse_episode(ep_name)  # pylint: disable=protected-access
                recipients = prowl_notifier._generate_recipients(show_name)  # pylint: disable=protected-access
                self._debug_spew("- Prowl Notifications for " + show.name + " (episode: " + episode.name + ") will be sent to:")
                for api in recipients:
                    self._debug_spew("-- " + api.strip())
                self._debug_spew("\n\r")

        return True
Example #5
0
    def _send_to_kodi(command, host=None, username=None, password=None, dest_app="KODI"):  # pylint: disable=too-many-arguments
        """Handles communication to KODI servers via HTTP API

        Args:
            command: Dictionary of field/data pairs, encoded via urllib and passed to the KODI API via HTTP
            host: KODI webserver host:port
            username: KODI webserver username
            password: KODI webserver password

        Returns:
            Returns response.result for successful commands or False if there was an error

        """

        # fill in omitted parameters
        if not username:
            username = sickbeard.KODI_USERNAME
        if not password:
            password = sickbeard.KODI_PASSWORD

        if not host:
            logger.log('No {0} host passed, aborting update'.format(dest_app), logger.WARNING)
            return False

        for key in command:
            if isinstance(command[key], six.text_type):
                command[key] = command[key].encode('utf-8')

        enc_command = urllib.parse.urlencode(command)
        logger.log("{0} encoded API command: {1!r}".format(dest_app, enc_command), logger.DEBUG)

        # url = 'http://%s/xbmcCmds/xbmcHttp/?%s' % (host, enc_command)  # maybe need for old plex?
        url = 'http://{0}/kodiCmds/kodiHttp/?{1}'.format(host, enc_command)
        try:
            req = urllib.request.Request(url)
            # if we have a password, use authentication
            if password:
                base64string = base64.encodestring('{0}:{1}'.format(username, password))[:-1]
                authheader = "Basic {0}".format(base64string)
                req.add_header("Authorization", authheader)
                logger.log("Contacting {0} (with auth header) via url: {1}".format(dest_app, ss(url)), logger.DEBUG)
            else:
                logger.log("Contacting {0} via url: {1}".format(dest_app, ss(url)), logger.DEBUG)

            try:
                response = urllib.request.urlopen(req)
            except (http_client.BadStatusLine, urllib.error.URLError) as e:
                logger.log("Couldn't contact {0} HTTP at {1!r} : {2!r}".format(dest_app, url, ex(e)), logger.DEBUG)
                return False

            result = response.read().decode(sickbeard.SYS_ENCODING)
            response.close()

            logger.log("{0} HTTP response: {1}".format(dest_app, result.replace('\n', '')), logger.DEBUG)
            return result

        except Exception as e:
            logger.log("Couldn't contact {0} HTTP at {1!r} : {2!r}".format(dest_app, url, ex(e)), logger.DEBUG)
            return False
Example #6
0
    def notify_download(self, ep_name, title='Completed:'):  # pylint: disable=unused-argument
        '''
        Send a notification that an episode was downloaded

        ep_name: The name of the episode that was downloaded
        title: The title of the notification (optional)
        '''
        ep_name = ss(ep_name)

        if sickbeard.USE_EMAIL and sickbeard.EMAIL_NOTIFY_ONDOWNLOAD:
            show = self._parseEp(ep_name)
            to = self._generate_recipients(show)
            if not to:
                logger.log(
                    'Skipping email notify because there are no configured recipients',
                    logger.DEBUG)
            else:
                try:
                    msg = MIMEMultipart('alternative')
                    msg.attach(
                        MIMEText(
                            'SickChill Notification - Downloaded\n'
                            'Show: {0}\nEpisode Number: {1}\nEpisode: {2}\nQuality: {3}\n\n'
                            'Powered by SickChill.'.format(
                                show[0], show[1], show[2], show[3])))
                    msg.attach(
                        MIMEText(
                            '<body style="font-family:Helvetica, Arial, sans-serif;">'
                            '<h3>SickChill Notification - Downloaded</h3>'
                            '<p>Show: <b>{0}</b></p><p>Episode Number: <b>{1}</b></p><p>Episode: <b>{2}</b></p><p>Quality: <b>{3}</b></p>'
                            '<h5 style="margin-top: 2.5em; padding: .7em 0; '
                            'color: #777; border-top: #BBB solid 1px;">'
                            'Powered by SickChill.</h5></body>'.format(
                                show[0], show[1], show[2], show[3]), 'html'))

                except Exception:
                    try:
                        msg = MIMEText(ep_name)
                    except Exception:
                        msg = MIMEText('Episode Downloaded')

                if sickbeard.EMAIL_SUBJECT:
                    msg[b'Subject'] = '[DL] ' + sickbeard.EMAIL_SUBJECT
                else:
                    msg[b'Subject'] = 'Downloaded: ' + ep_name
                msg[b'From'] = sickbeard.EMAIL_FROM
                msg[b'To'] = ','.join(to)
                msg[b'Date'] = formatdate(localtime=True)
                if self._sendmail(sickbeard.EMAIL_HOST, sickbeard.EMAIL_PORT,
                                  sickbeard.EMAIL_FROM, sickbeard.EMAIL_TLS,
                                  sickbeard.EMAIL_USER,
                                  sickbeard.EMAIL_PASSWORD, to, msg):
                    logger.log(
                        'Download notification sent to [{0}] for "{1}"'.format(
                            to, ep_name), logger.DEBUG)
                else:
                    logger.log(
                        'Download notification error: {0}'.format(
                            self.last_err), logger.WARNING)
Example #7
0
    def _parse_episode(ep_name):
        ep_name = ss(ep_name)

        sep = " - "
        titles = ep_name.split(sep)
        titles.sort(key=len, reverse=True)
        logger.log("TITLES: {0}".format(titles), logger.DEBUG)
        return titles
Example #8
0
    def _parse_episode(ep_name):
        ep_name = ss(ep_name)

        sep = " - "
        titles = ep_name.split(sep)
        titles.sort(key=len, reverse=True)
        logger.log("TITLES: {0}".format(titles), logger.DEBUG)
        return titles
Example #9
0
 def notify_subtitle_download(self, ep_name, lang):
     ep_name = ss(ep_name)
     if sickbeard.PROWL_NOTIFY_ONSUBTITLEDOWNLOAD:
         show = self._parse_episode(ep_name)
         recipients = self._generate_recipients(show)
         if not recipients:
             logger.log('Skipping prowl notify because there are no configured recipients', logger.DEBUG)
         else:
             for api in recipients:
                 self._send_prowl(prowl_api=api, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SUBTITLE_DOWNLOAD],
                                  message=ep_name + " [" + lang + "] :: " + time.strftime(sickbeard.DATE_PRESET + " " + sickbeard.TIME_PRESET))
Example #10
0
 def notify_snatch(self, ep_name):
     ep_name = ss(ep_name)
     if sickbeard.PROWL_NOTIFY_ONSNATCH:
         show = self._parse_episode(ep_name)
         recipients = self._generate_recipients(show)
         if not recipients:
             logger.log('Skipping prowl notify because there are no configured recipients', logger.DEBUG)
         else:
             for api in recipients:
                 self._send_prowl(prowl_api=api, prowl_priority=None, event=common.notifyStrings[common.NOTIFY_SNATCH],
                                  message=ep_name + " :: " + time.strftime(sickbeard.DATE_PRESET + " " + sickbeard.TIME_PRESET))
Example #11
0
def prepareFailedName(release):
    """Standardizes release name for failed DB"""

    fixed = urllib.parse.unquote(release)
    if fixed.endswith(".nzb"):
        fixed = fixed.rpartition(".")[0]

    fixed = re.sub(r"[\.\-\+\ ]", "_", fixed)
    fixed = ss(fixed)

    return fixed
Example #12
0
def prepareFailedName(release):
    """Standardizes release name for failed DB"""

    fixed = urllib.parse.unquote(release)
    if fixed.endswith(".nzb"):
        fixed = fixed.rpartition(".")[0]

    fixed = re.sub(r"[\.\-\+\ ]", "_", fixed)
    fixed = ss(fixed)

    return fixed
Example #13
0
    def processEpisode(self,
                       proc_dir=None,
                       nzbName=None,
                       quiet=None,
                       process_method=None,
                       force=None,
                       is_priority=None,
                       delete_on="0",
                       failed="0",
                       proc_type="manual",
                       force_next=False,
                       *args_,
                       **kwargs):

        mode = kwargs.get('type', proc_type)
        process_path = ss(
            xhtml_unescape(kwargs.get('dir', proc_dir or '') or ''))
        if not process_path:
            return self.redirect("/home/postprocess/")

        release_name = ss(xhtml_unescape(nzbName)) if nzbName else nzbName

        result = sickbeard.postProcessorTaskScheduler.action.add_item(
            process_path,
            release_name,
            method=process_method,
            force=force,
            is_priority=is_priority,
            delete=delete_on,
            failed=failed,
            mode=mode,
            force_next=force_next)

        if config.checkbox_to_value(quiet):
            return result

        if result:
            result = result.replace("\n", "<br>\n")

        return self._genericMessage("Postprocessing results", result)
Example #14
0
def create_nzb_string(file_elements, xmlns):
    """
    Extract extra info from file_elements.

    :param file_elements: to be processed
    :param xmlns: the xml namespace to be used
    :return: string containing all extra info extracted from the file_elements
    """
    root_element = ETree.Element("nzb")
    if xmlns:
        root_element.set("xmlns", xmlns)

    for cur_file in file_elements:
        root_element.append(strip_xmlns(cur_file, xmlns))

    return ETree.tostring(ss(root_element))
Example #15
0
def create_nzb_string(file_elements, xmlns):
    """
    Extract extra info from file_elements.

    :param file_elements: to be processed
    :param xmlns: the xml namespace to be used
    :return: string containing all extra info extracted from the file_elements
    """
    root_element = ETree.Element("nzb")
    if xmlns:
        root_element.set("xmlns", xmlns)

    for cur_file in file_elements:
        root_element.append(strip_xmlns(cur_file, xmlns))

    return ETree.tostring(ss(root_element))
    def _check_auth_from_data(self, data):
        """
        Checks that the returned data is valid
        Returns: _check_auth if valid otherwise False if there is an error
        """
        if data('categories') + data('item'):
            return self._check_auth()

        try:
            err_desc = data.error.attrs['description']
            if not err_desc:
                raise AttributeError
        except (AttributeError, TypeError):
            return self._check_auth()

        logger.log(ss(err_desc))

        return False
Example #17
0
    def notify_snatch(self, ep_name, title='Snatched:'):  # pylint: disable=unused-argument
        '''
        Send a notification that an episode was snatched

        ep_name: The name of the episode that was snatched
        title: The title of the notification (optional)
        '''
        ep_name = ss(ep_name)

        if sickbeard.USE_EMAIL and sickbeard.EMAIL_NOTIFY_ONSNATCH:
            show = self._parseEp(ep_name)
            to = self._generate_recipients(show)
            if not to:
                logger.log('Skipping email notify because there are no configured recipients', logger.DEBUG)
            else:
                try:
                    msg = MIMEMultipart('alternative')
                    msg.attach(MIMEText(
                        '<body style="font-family:Helvetica, Arial, sans-serif;">'
                        '<h3>SickChill Notification - Snatched</h3>'
                        '<p>Show: <b>{0}</b></p><p>Episode Number: <b>{1}</b></p><p>Episode: <b>{2}</b></p><p>Quality: <b>{3}</b></p>'
                        '<h5 style="margin-top: 2.5em; padding: .7em 0; '
                        'color: #777; border-top: #BBB solid 1px;">'
                        'Powered by SickChill.</h5></body>'.format(show[0], show[1], show[2], show[3]),
                        'html'))

                except Exception:
                    try:
                        msg = MIMEText(ep_name)
                    except Exception:
                        msg = MIMEText('Episode Snatched')

                if sickbeard.EMAIL_SUBJECT:
                    msg[b'Subject'] = '[SN] ' + sickbeard.EMAIL_SUBJECT
                else:
                    msg[b'Subject'] = 'Snatched: ' + ep_name
                msg[b'From'] = sickbeard.EMAIL_FROM
                msg[b'To'] = ','.join(to)
                msg[b'Date'] = formatdate(localtime=True)
                if self._sendmail(sickbeard.EMAIL_HOST, sickbeard.EMAIL_PORT, sickbeard.EMAIL_FROM, sickbeard.EMAIL_TLS,
                                  sickbeard.EMAIL_USER, sickbeard.EMAIL_PASSWORD, to, msg):
                    logger.log('Snatch notification sent to [{0}] for "{1}"'.format(to, ep_name), logger.DEBUG)
                else:
                    logger.log('Snatch notification error: {0}'.format(self.last_err), logger.WARNING)
Example #18
0
def _logHistoryItem(action, showid, season, episode, quality, resource, provider, version=-1):
    """
    Insert a history item in DB

    :param action: action taken (snatch, download, etc)
    :param showid: showid this entry is about
    :param season: show season
    :param episode: show episode
    :param quality: media quality
    :param resource: resource used
    :param provider: provider used
    :param version: tracked version of file (defaults to -1)
    """
    logDate = datetime.datetime.today().strftime(History.date_format)
    resource = ss(resource)

    main_db_con = db.DBConnection()
    main_db_con.action(
        "INSERT INTO history (action, date, showid, season, episode, quality, resource, provider, version) VALUES (?,?,?,?,?,?,?,?,?)",
        [action, logDate, showid, season, episode, quality, resource, provider, version])
Example #19
0
    def test_email(self):
        """
        Test email notifications
        """
        email_notifier = EmailNotifier()

        # Per-show-email notifications were added early on and utilized a different format than the other notifiers.
        # Therefore, to test properly (and ensure backwards compatibility), this routine will test shows that use
        # both the old and the new storage methodology
        legacy_test_emails = "[email protected],[email protected],[email protected]"
        test_emails = "[email protected],[email protected],[email protected]"

        for show in self.legacy_shows:
            showid = self._get_showid_by_showname(show.name)
            self.mydb.action(
                "UPDATE tv_shows SET notify_list = ? WHERE show_id = ?",
                [legacy_test_emails, showid])

        for show in self.shows:
            showid = self._get_showid_by_showname(show.name)
            Home.saveShowNotifyList(show=showid, emails=test_emails)

        # Now, iterate through all shows using the email list generation routines that are used in the notifier proper
        shows = self.legacy_shows + self.shows
        for show in shows:
            for episode in show.episodes:
                ep_name = ss(
                    episode._format_pattern('%SN - %Sx%0E - %EN - ') +
                    episode.quality)
                show_name = email_notifier._parseEp(ep_name)
                recipients = email_notifier._generate_recipients(show_name)
                self._debug_spew("- Email Notifications for " + show.name +
                                 " (episode: " + episode.name +
                                 ") will be sent to:")
                for email in recipients:
                    self._debug_spew("-- " + email.strip())
                self._debug_spew("\n\r")

        return True
Example #20
0
    def _send_to_kodi_json(command,
                           host=None,
                           username=None,
                           password=None,
                           dest_app="KODI"):
        """Handles communication to KODI servers via JSONRPC

        Args:
            command: Dictionary of field/data pairs, encoded via urllib and passed to the KODI JSON-RPC via HTTP
            host: KODI webserver host:port
            username: KODI webserver username
            password: KODI webserver password

        Returns:
            Returns response.result for successful commands or False if there was an error

        """

        # fill in omitted parameters
        if not username:
            username = sickbeard.KODI_USERNAME
        if not password:
            password = sickbeard.KODI_PASSWORD

        if not host:
            logger.log('No {0} host passed, aborting update'.format(dest_app),
                       logger.WARNING)
            return False

        command = command.encode('utf-8')
        logger.log("{0} JSON command: {1}".format(dest_app, command),
                   logger.DEBUG)

        url = 'http://{0}/jsonrpc'.format(host)
        try:
            req = urllib.request.Request(url, command)
            req.add_header("Content-type", "application/json")
            # if we have a password, use authentication
            if password:
                base64string = base64.encodestring('{0}:{1}'.format(
                    username, password))[:-1]
                authheader = "Basic {0}".format(base64string)
                req.add_header("Authorization", authheader)
                logger.log(
                    "Contacting {0} (with auth header) via url: {1}".format(
                        dest_app, ss(url)), logger.DEBUG)
            else:
                logger.log(
                    "Contacting {0} via url: {1}".format(dest_app, ss(url)),
                    logger.DEBUG)

            try:
                response = urllib.request.urlopen(req)
            except (http_client.BadStatusLine, urllib.error.URLError) as e:
                if sickbeard.KODI_ALWAYS_ON:
                    logger.log(
                        "Error while trying to retrieve {0} API version for {1}: {2!r}"
                        .format(dest_app, host, ex(e)), logger.WARNING)
                return False

            # parse the json result
            try:
                result = json.load(response)
                response.close()
                logger.log("{0} JSON response: {1}".format(dest_app, result),
                           logger.DEBUG)
                return result  # need to return response for parsing
            except ValueError as e:
                logger.log("Unable to decode JSON: " + str(response.read()),
                           logger.WARNING)
                return False

        except IOError as e:
            if sickbeard.KODI_ALWAYS_ON:
                logger.log(
                    "Warning: Couldn't contact {0} JSON API at {1}: {2!r}".
                    format(dest_app, ss(url), ex(e)), logger.WARNING)
            return False
Example #21
0
    def submit_errors(self):

        submitter_result = ''
        issue_id = None

        gh_credentials = (sickbeard.GIT_AUTH_TYPE == 0 and sickbeard.GIT_USERNAME and sickbeard.GIT_PASSWORD) \
            or (sickbeard.GIT_AUTH_TYPE == 1 and sickbeard.GIT_TOKEN)

        if not all((gh_credentials, sickbeard.DEBUG, sickbeard.gh,
                    classes.ErrorViewer.errors)):
            submitter_result = 'Please set your GitHub token or username and password in the config and enable debug. Unable to submit issue ticket to GitHub!'
            return submitter_result, issue_id

        try:
            from .versionChecker import CheckVersion
            checkversion = CheckVersion()
            checkversion.check_for_new_version()
            commits_behind = checkversion.updater.get_num_commits_behind()
        except Exception:
            submitter_result = 'Could not check if your SickChill is updated, unable to submit issue ticket to GitHub!'
            return submitter_result, issue_id

        if commits_behind is None or commits_behind > 0:
            submitter_result = 'Please update SickChill, unable to submit issue ticket to GitHub with an outdated version!'
            return submitter_result, issue_id

        if self.submitter_running:
            submitter_result = 'Issue submitter is running, please wait for it to complete'
            return submitter_result, issue_id

        self.submitter_running = True

        try:
            # read log file
            __log_data = None

            if ek(os.path.isfile, self.log_file):
                with io.open(self.log_file, encoding='utf-8') as log_f:
                    __log_data = log_f.readlines()

            for i in range(1, int(sickbeard.LOG_NR)):
                f_name = '{0}.{1:d}'.format(self.log_file, i)
                if ek(os.path.isfile, f_name) and (len(__log_data) <= 500):
                    with io.open(f_name, encoding='utf-8') as log_f:
                        __log_data += log_f.readlines()

            __log_data = list(reversed(__log_data))

            # parse and submit errors to issue tracker
            for cur_error in sorted(classes.ErrorViewer.errors,
                                    key=lambda error: error.time,
                                    reverse=True)[:500]:
                try:
                    title_error = ss(str(cur_error.title))
                    if not title_error or title_error == 'None':
                        title_error = re.match(
                            r'^[A-Za-z0-9\-\[\] :]+::\s(?:\[[\w]{7}\])\s*(.*)$',
                            ss(cur_error.message)).group(1)

                    if len(title_error) > 1000:
                        title_error = title_error[0:1000]

                except Exception as err_msg:
                    self.log(
                        'Unable to get error title : {0}'.format(ex(err_msg)),
                        ERROR)
                    title_error = 'UNKNOWN'

                gist = None
                regex = r'^(?P<time>{time})\s+(?P<level>[A-Z]+)\s+[A-Za-z0-9\-\[\] :]+::.*$'.format(
                    time=re.escape(cur_error.time))
                for i, data in enumerate(__log_data):
                    match = re.match(regex, data)
                    if match:
                        level = match.group('level')
                        if LOGGING_LEVELS[level] == ERROR:
                            paste_data = ''.join(__log_data[i:i + 50])
                            if paste_data:
                                gist = sickbeard.gh.get_user().create_gist(
                                    False, {
                                        'sickchill.log':
                                        InputFileContent(paste_data)
                                    })
                            break
                    else:
                        gist = 'No ERROR found'

                try:
                    locale_name = locale.getdefaultlocale()[1]
                except Exception:
                    locale_name = 'unknown'

                if gist and gist != 'No ERROR found':
                    log_link = 'Link to Log: {0}'.format(gist.html_url)
                else:
                    log_link = 'No Log available with ERRORS:'

                msg = [
                    '### INFO',
                    'Python Version: **{0}**'.format(sys.version[:120].replace(
                        '\n', '')),
                    'Operating System: **{0}**'.format(platform.platform()),
                    'Locale: {0}'.format(locale_name),
                    'Branch: **{0}**'.format(sickbeard.BRANCH),
                    'Commit: SickChill/SickChill@{0}'.format(
                        sickbeard.CUR_COMMIT_HASH),
                    log_link,
                    '### ERROR',
                    '```',
                    cur_error.message,
                    '```',
                    '---',
                    '_STAFF NOTIFIED_: @SickChill/owners @SickChill/moderators',
                ]

                message = '\n'.join(msg)
                title_error = '[APP SUBMITTED]: {0}'.format(title_error)

                repo = sickbeard.gh.get_organization(
                    sickbeard.GIT_ORG).get_repo(sickbeard.GIT_REPO)
                reports = repo.get_issues(state='all')

                def is_ascii_error(title):
                    # [APP SUBMITTED]: 'ascii' codec can't encode characters in position 00-00: ordinal not in range(128)
                    # [APP SUBMITTED]: 'charmap' codec can't decode byte 0x00 in position 00: character maps to <undefined>
                    return re.search(
                        r'.* codec can\'t .*code .* in position .*:',
                        title) is not None

                def is_malformed_error(title):
                    # [APP SUBMITTED]: not well-formed (invalid token): line 0, column 0
                    return re.search(
                        r'.* not well-formed \(invalid token\): line .* column .*',
                        title) is not None

                ascii_error = is_ascii_error(title_error)
                malformed_error = is_malformed_error(title_error)

                issue_found = False
                for report in reports:
                    if title_error.rsplit(' :: ')[-1] in report.title or \
                        (malformed_error and is_malformed_error(report.title)) or \
                            (ascii_error and is_ascii_error(report.title)):

                        issue_id = report.number
                        if not report.raw_data['locked']:
                            if report.create_comment(message):
                                submitter_result = 'Commented on existing issue #{0} successfully!'.format(
                                    issue_id)
                            else:
                                submitter_result = 'Failed to comment on found issue #{0}!'.format(
                                    issue_id)
                        else:
                            submitter_result = 'Issue #{0} is locked, check GitHub to find info about the error.'.format(
                                issue_id)

                        issue_found = True
                        break

                if not issue_found:
                    issue = repo.create_issue(title_error, message)
                    if issue:
                        issue_id = issue.number
                        submitter_result = 'Your issue ticket #{0} was submitted successfully!'.format(
                            issue_id)
                    else:
                        submitter_result = 'Failed to create a new issue!'

                if issue_id and cur_error in classes.ErrorViewer.errors:
                    # clear error from error list
                    classes.ErrorViewer.errors.remove(cur_error)
        except RateLimitExceededException:
            submitter_result = 'Your Github user has exceeded its API rate limit, please try again later'
            issue_id = None
        except TwoFactorException:
            submitter_result = (
                'Your Github account requires Two-Factor Authentication, '
                'please change your auth method in the config')
            issue_id = None
        except Exception:
            self.log(traceback.format_exc(), ERROR)
            submitter_result = 'Exception generated in issue submitter, please check the log'
            issue_id = None
        finally:
            self.submitter_running = False

        return submitter_result, issue_id
Example #22
0
    def submit_errors(self):  # pylint: disable=too-many-branches,too-many-locals

        submitter_result = ''
        issue_id = None

        gh_credentials = (sickbeard.GIT_AUTH_TYPE == 0 and sickbeard.GIT_USERNAME and sickbeard.GIT_PASSWORD) \
            or (sickbeard.GIT_AUTH_TYPE == 1 and sickbeard.GIT_TOKEN)

        if not all((gh_credentials, sickbeard.DEBUG, sickbeard.gh, classes.ErrorViewer.errors)):
            submitter_result = 'Please set your GitHub token or username and password in the config and enable debug. Unable to submit issue ticket to GitHub!'
            return submitter_result, issue_id

        try:
            from sickbeard.versionChecker import CheckVersion
            checkversion = CheckVersion()
            checkversion.check_for_new_version()
            commits_behind = checkversion.updater.get_num_commits_behind()
        except Exception:  # pylint: disable=broad-except
            submitter_result = 'Could not check if your SickChill is updated, unable to submit issue ticket to GitHub!'
            return submitter_result, issue_id

        if commits_behind is None or commits_behind > 0:
            submitter_result = 'Please update SickChill, unable to submit issue ticket to GitHub with an outdated version!'
            return submitter_result, issue_id

        if self.submitter_running:
            submitter_result = 'Issue submitter is running, please wait for it to complete'
            return submitter_result, issue_id

        self.submitter_running = True

        try:
            # read log file
            __log_data = None

            if ek(os.path.isfile, self.log_file):
                with io.open(self.log_file, encoding='utf-8') as log_f:
                    __log_data = log_f.readlines()

            for i in range(1, int(sickbeard.LOG_NR)):
                f_name = '{0}.{1:d}'.format(self.log_file, i)
                if ek(os.path.isfile, f_name) and (len(__log_data) <= 500):
                    with io.open(f_name, encoding='utf-8') as log_f:
                        __log_data += log_f.readlines()

            __log_data = list(reversed(__log_data))

            # parse and submit errors to issue tracker
            for cur_error in sorted(classes.ErrorViewer.errors, key=lambda error: error.time, reverse=True)[:500]:
                try:
                    title_error = ss(str(cur_error.title))
                    if not title_error or title_error == 'None':
                        title_error = re.match(r'^[A-Za-z0-9\-\[\] :]+::\s(?:\[[\w]{7}\])\s*(.*)$', ss(cur_error.message)).group(1)

                    if len(title_error) > 1000:
                        title_error = title_error[0:1000]

                except Exception as err_msg:  # pylint: disable=broad-except
                    self.log('Unable to get error title : {0}'.format(ex(err_msg)), ERROR)
                    title_error = 'UNKNOWN'

                gist = None
                regex = r'^(?P<time>{time})\s+(?P<level>[A-Z]+)\s+[A-Za-z0-9\-\[\] :]+::.*$'.format(time=re.escape(cur_error.time))
                for i, data in enumerate(__log_data):
                    match = re.match(regex, data)
                    if match:
                        level = match.group('level')
                        if LOGGING_LEVELS[level] == ERROR:
                            paste_data = ''.join(__log_data[i:i + 50])
                            if paste_data:
                                gist = sickbeard.gh.get_user().create_gist(False, {'sickchill.log': InputFileContent(paste_data)})
                            break
                    else:
                        gist = 'No ERROR found'

                try:
                    locale_name = locale.getdefaultlocale()[1]
                except Exception:  # pylint: disable=broad-except
                    locale_name = 'unknown'

                if gist and gist != 'No ERROR found':
                    log_link = 'Link to Log: {0}'.format(gist.html_url)
                else:
                    log_link = 'No Log available with ERRORS:'

                msg = [
                    '### INFO',
                    'Python Version: **{0}**'.format(sys.version[:120].replace('\n', '')),
                    'Operating System: **{0}**'.format(platform.platform()),
                    'Locale: {0}'.format(locale_name),
                    'Branch: **{0}**'.format(sickbeard.BRANCH),
                    'Commit: SickChill/SickChill@{0}'.format(sickbeard.CUR_COMMIT_HASH),
                    log_link,
                    '### ERROR',
                    '```',
                    cur_error.message,
                    '```',
                    '---',
                    '_STAFF NOTIFIED_: @SickChill/owners @SickChill/moderators',
                ]

                message = '\n'.join(msg)
                title_error = '[APP SUBMITTED]: {0}'.format(title_error)

                repo = sickbeard.gh.get_organization(sickbeard.GIT_ORG).get_repo(sickbeard.GIT_REPO)
                reports = repo.get_issues(state='all')

                def is_ascii_error(title):
                    # [APP SUBMITTED]: 'ascii' codec can't encode characters in position 00-00: ordinal not in range(128)
                    # [APP SUBMITTED]: 'charmap' codec can't decode byte 0x00 in position 00: character maps to <undefined>
                    return re.search(r'.* codec can\'t .*code .* in position .*:', title) is not None

                def is_malformed_error(title):
                    # [APP SUBMITTED]: not well-formed (invalid token): line 0, column 0
                    return re.search(r'.* not well-formed \(invalid token\): line .* column .*', title) is not None

                ascii_error = is_ascii_error(title_error)
                malformed_error = is_malformed_error(title_error)

                issue_found = False
                for report in reports:
                    if title_error.rsplit(' :: ')[-1] in report.title or \
                        (malformed_error and is_malformed_error(report.title)) or \
                            (ascii_error and is_ascii_error(report.title)):

                        issue_id = report.number
                        if not report.raw_data['locked']:
                            if report.create_comment(message):
                                submitter_result = 'Commented on existing issue #{0} successfully!'.format(issue_id)
                            else:
                                submitter_result = 'Failed to comment on found issue #{0}!'.format(issue_id)
                        else:
                            submitter_result = 'Issue #{0} is locked, check GitHub to find info about the error.'.format(issue_id)

                        issue_found = True
                        break

                if not issue_found:
                    issue = repo.create_issue(title_error, message)
                    if issue:
                        issue_id = issue.number
                        submitter_result = 'Your issue ticket #{0} was submitted successfully!'.format(issue_id)
                    else:
                        submitter_result = 'Failed to create a new issue!'

                if issue_id and cur_error in classes.ErrorViewer.errors:
                    # clear error from error list
                    classes.ErrorViewer.errors.remove(cur_error)
        except RateLimitExceededException:
            submitter_result = 'Your Github user has exceeded its API rate limit, please try again later'
            issue_id = None
        except TwoFactorException:
            submitter_result = ('Your Github account requires Two-Factor Authentication, '
                                'please change your auth method in the config')
            issue_id = None
        except Exception:  # pylint: disable=broad-except
            self.log(traceback.format_exc(), ERROR)
            submitter_result = 'Exception generated in issue submitter, please check the log'
            issue_id = None
        finally:
            self.submitter_running = False

        return submitter_result, issue_id
Example #23
0
    def _send_to_kodi_json(command, host=None, username=None, password=None, dest_app="KODI"):
        """Handles communication to KODI servers via JSONRPC

        Args:
            command: Dictionary of field/data pairs, encoded via urllib and passed to the KODI JSON-RPC via HTTP
            host: KODI webserver host:port
            username: KODI webserver username
            password: KODI webserver password

        Returns:
            Returns response.result for successful commands or False if there was an error

        """

        # fill in omitted parameters
        if not username:
            username = sickbeard.KODI_USERNAME
        if not password:
            password = sickbeard.KODI_PASSWORD

        if not host:
            logger.log('No {0} host passed, aborting update'.format(dest_app), logger.WARNING)
            return False

        command = command.encode('utf-8')
        logger.log("{0} JSON command: {1}".format(dest_app, command), logger.DEBUG)

        url = 'http://{0}/jsonrpc'.format(host)
        try:
            req = urllib.request.Request(url, command)
            req.add_header("Content-type", "application/json")
            # if we have a password, use authentication
            if password:
                base64string = base64.encodestring('{0}:{1}'.format(username, password))[:-1]
                authheader = "Basic {0}".format(base64string)
                req.add_header("Authorization", authheader)
                logger.log("Contacting {0} (with auth header) via url: {1}".format(dest_app, ss(url)), logger.DEBUG)
            else:
                logger.log("Contacting {0} via url: {1}".format(dest_app, ss(url)), logger.DEBUG)

            try:
                response = urllib.request.urlopen(req)
            except (http_client.BadStatusLine, urllib.error.URLError) as e:
                if sickbeard.KODI_ALWAYS_ON:
                    logger.log("Error while trying to retrieve {0} API version for {1}: {2!r}".format(dest_app, host, ex(e)), logger.WARNING)
                return False

            # parse the json result
            try:
                result = json.load(response)
                response.close()
                logger.log("{0} JSON response: {1}".format(dest_app, result), logger.DEBUG)
                return result  # need to return response for parsing
            except ValueError as e:
                logger.log("Unable to decode JSON: " + str(response.read()), logger.WARNING)
                return False

        except IOError as e:
            if sickbeard.KODI_ALWAYS_ON:
                logger.log("Warning: Couldn't contact {0} JSON API at {1}: {2!r}".format(dest_app, ss(url), ex(e)), logger.WARNING)
            return False