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
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
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
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
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
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)
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
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))
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))
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
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)
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
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)
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])
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
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
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
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
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