diff --git a/default.py b/default.py index f95c0898..70220e34 100644 --- a/default.py +++ b/default.py @@ -7,15 +7,15 @@ from resources.lib.utilities import createError, checkIfNewVersion from resources.lib.kodiUtilities import setSetting, getSetting -__addon__ = xbmcaddon.Addon('script.trakt') -__addonversion__ = __addon__.getAddonInfo('version') -__addonid__ = __addon__.getAddonInfo('id') +__addon__ = xbmcaddon.Addon("script.trakt") +__addonversion__ = __addon__.getAddonInfo("version") +__addonid__ = __addon__.getAddonInfo("id") kodilogging.config() logger = logging.getLogger(__name__) logger.debug("Loading '%s' version '%s'" % (__addonid__, __addonversion__)) -if checkIfNewVersion(str(getSetting('version')), str(__addonversion__)): - setSetting('version', __addonversion__) +if checkIfNewVersion(str(getSetting("version")), str(__addonversion__)): + setSetting("version", __addonversion__) try: traktService().run() diff --git a/defaultscript.py b/defaultscript.py index a6fd7cee..0417c0f0 100644 --- a/defaultscript.py +++ b/defaultscript.py @@ -7,8 +7,10 @@ __addon__ = xbmcaddon.Addon("script.trakt") + def Main(): script.run() -if __name__ == '__main__': + +if __name__ == "__main__": Main() diff --git a/resources/lib/deviceAuthDialog.py b/resources/lib/deviceAuthDialog.py index 91f61398..2262fa61 100644 --- a/resources/lib/deviceAuthDialog.py +++ b/resources/lib/deviceAuthDialog.py @@ -32,7 +32,8 @@ def onInit(self) -> None: authcode = self.getControl(AUTHCODE_LABEL) warning = self.getControl(WARNING_LABEL) instuction.setLabel( - getString(32159).format("[COLOR red]" + self.url + "[/COLOR]")) + getString(32159).format("[COLOR red]" + self.url + "[/COLOR]") + ) authcode.setLabel(self.code) warning.setLabel(getString(32162)) @@ -47,15 +48,15 @@ def onFocus(self, control: xbmcgui.Control) -> None: pass def onClick(self, control: xbmcgui.Control) -> None: - logger.debug('onClick: %s' % (control)) + logger.debug("onClick: %s" % (control)) if control == LATER_BUTTON: notification(getString(32157), getString(32150), 5000) - setSetting('last_reminder', str(int(time.time()))) + setSetting("last_reminder", str(int(time.time()))) if control == NEVER_BUTTON: notification(getString(32157), getString(32151), 5000) - setSetting('last_reminder', '-1') + setSetting("last_reminder", "-1") if control in [LATER_BUTTON, NEVER_BUTTON]: self.close() diff --git a/resources/lib/kodiUtilities.py b/resources/lib/kodiUtilities.py index 5ee276e9..0900d336 100644 --- a/resources/lib/kodiUtilities.py +++ b/resources/lib/kodiUtilities.py @@ -20,7 +20,10 @@ def notification( - header: str, message: str, time: int = 5000, icon: str = __addon__.getAddonInfo("icon") + header: str, + message: str, + time: int = 5000, + icon: str = __addon__.getAddonInfo("icon"), ) -> None: xbmcgui.Dialog().notification(header, message, icon, time) @@ -132,7 +135,9 @@ def checkExclusion(fullpath: str) -> bool: return found -def kodiRpcToTraktMediaObject(type: str, data: Dict, mode: str = "collected") -> Optional[Dict]: +def kodiRpcToTraktMediaObject( + type: str, data: Dict, mode: str = "collected" +) -> Optional[Dict]: if type == "show": if "uniqueid" in data: data["ids"] = data.pop("uniqueid") diff --git a/resources/lib/kodilogging.py b/resources/lib/kodilogging.py index 186d0066..5d9c8787 100644 --- a/resources/lib/kodilogging.py +++ b/resources/lib/kodilogging.py @@ -24,12 +24,11 @@ class KodiLogHandler(logging.StreamHandler): - def __init__(self) -> None: logging.StreamHandler.__init__(self) - addon_id = xbmcaddon.Addon().getAddonInfo('id') + addon_id = xbmcaddon.Addon().getAddonInfo("id") prefix = "[%s] " % addon_id - formatter = logging.Formatter(prefix + '%(name)s: %(message)s') + formatter = logging.Formatter(prefix + "%(name)s: %(message)s") self.setFormatter(formatter) def emit(self, record: logging.LogRecord) -> None: @@ -41,12 +40,13 @@ def emit(self, record: logging.LogRecord) -> None: logging.DEBUG: xbmc.LOGDEBUG, logging.NOTSET: xbmc.LOGNONE, } - if getSettingAsBool('debug'): + if getSettingAsBool("debug"): xbmc.log(self.format(record), levels[record.levelno]) def flush(self) -> None: pass + def config() -> None: logger = logging.getLogger() logger.addHandler(KodiLogHandler()) diff --git a/resources/lib/obfuscation.py b/resources/lib/obfuscation.py index dd8b2129..ec6e0590 100644 --- a/resources/lib/obfuscation.py +++ b/resources/lib/obfuscation.py @@ -7,6 +7,7 @@ def deobfuscate(data: Union[List[int], str]) -> str: return "" return "".join(chr(b ^ 0x42) for b in data) + def obfuscate(data: str) -> List[int]: if not data: return [] diff --git a/resources/lib/rating.py b/resources/lib/rating.py index db7e749d..7f445636 100644 --- a/resources/lib/rating.py +++ b/resources/lib/rating.py @@ -14,7 +14,9 @@ __addon__ = xbmcaddon.Addon("script.trakt") -def ratingCheck(media_type: str, items_to_rate: List[Dict], watched_time: float, total_time: float) -> None: +def ratingCheck( + media_type: str, items_to_rate: List[Dict], watched_time: float, total_time: float +) -> None: """Check if a video should be rated and if so launches the rating dialog""" logger.debug("Rating Check called for '%s'" % media_type) if not kodiUtilities.getSettingAsBool("rate_%s" % media_type): @@ -27,17 +29,30 @@ def ratingCheck(media_type: str, items_to_rate: List[Dict], watched_time: float, if watched >= kodiUtilities.getSettingAsFloat("rate_min_view_time"): rateMedia(media_type, items_to_rate) else: - logger.debug("'%s' does not meet minimum view time for rating (watched: %0.2f%%, minimum: %0.2f%%)" % ( - media_type, watched, kodiUtilities.getSettingAsFloat("rate_min_view_time"))) + logger.debug( + "'%s' does not meet minimum view time for rating (watched: %0.2f%%, minimum: %0.2f%%)" + % ( + media_type, + watched, + kodiUtilities.getSettingAsFloat("rate_min_view_time"), + ) + ) -def rateMedia(media_type: str, itemsToRate: List[Dict], unrate: bool = False, rating: Optional[Union[int, str]] = None) -> None: +def rateMedia( + media_type: str, + itemsToRate: List[Dict], + unrate: bool = False, + rating: Optional[Union[int, str]] = None, +) -> None: """Launches the rating dialog""" for summary_info in itemsToRate: + if summary_info is None: + continue if not utilities.isValidMediaType(media_type): logger.debug("Not a valid media type") return - elif 'user' not in summary_info: + elif "user" not in summary_info: logger.debug("No user data") return @@ -48,7 +63,7 @@ def rateMedia(media_type: str, itemsToRate: List[Dict], unrate: bool = False, ra if unrate: rating = None - if summary_info['user']['ratings']['rating'] > 0: + if summary_info["user"]["ratings"]["rating"] > 0: rating = 0 if rating is not None: @@ -59,30 +74,33 @@ def rateMedia(media_type: str, itemsToRate: List[Dict], unrate: bool = False, ra return - rerate = kodiUtilities.getSettingAsBool('rate_rerate') + rerate = kodiUtilities.getSettingAsBool("rate_rerate") if rating is not None: - if summary_info['user']['ratings']['rating'] == 0: + if summary_info["user"]["ratings"]["rating"] == 0: logger.debug( - "Rating for '%s' is being set to '%d' manually." % (s, rating)) + "Rating for '%s' is being set to '%d' manually." % (s, rating) + ) __rateOnTrakt(rating, media_type, summary_info) else: if rerate: - if not summary_info['user']['ratings']['rating'] == rating: + if not summary_info["user"]["ratings"]["rating"] == rating: logger.debug( - "Rating for '%s' is being set to '%d' manually." % (s, rating)) + "Rating for '%s' is being set to '%d' manually." + % (s, rating) + ) __rateOnTrakt(rating, media_type, summary_info) else: - kodiUtilities.notification( - kodiUtilities.getString(32043), s) - logger.debug( - "'%s' already has a rating of '%d'." % (s, rating)) + kodiUtilities.notification(kodiUtilities.getString(32043), s) + logger.debug("'%s' already has a rating of '%d'." % (s, rating)) else: - kodiUtilities.notification( - kodiUtilities.getString(32041), s) + kodiUtilities.notification(kodiUtilities.getString(32041), s) logger.debug("'%s' is already rated." % s) return - if summary_info['user']['ratings'] and summary_info['user']['ratings']['rating']: + if ( + summary_info["user"]["ratings"] + and summary_info["user"]["ratings"]["rating"] + ): if not rerate: logger.debug("'%s' has already been rated." % s) kodiUtilities.notification(kodiUtilities.getString(32041), s) @@ -92,17 +110,21 @@ def rateMedia(media_type: str, itemsToRate: List[Dict], unrate: bool = False, ra gui = RatingDialog( "script-trakt-RatingDialog.xml", - __addon__.getAddonInfo('path'), + __addon__.getAddonInfo("path"), media_type, summary_info, - rerate + rerate, ) gui.doModal() if gui.rating: rating = gui.rating if rerate: - if summary_info['user']['ratings'] and summary_info['user']['ratings']['rating'] > 0 and rating == summary_info['user']['ratings']['rating']: + if ( + summary_info["user"]["ratings"] + and summary_info["user"]["ratings"]["rating"] > 0 + and rating == summary_info["user"]["ratings"]["rating"] + ): rating = 0 if rating == 0 or rating == "unrate": @@ -118,33 +140,53 @@ def rateMedia(media_type: str, itemsToRate: List[Dict], unrate: bool = False, ra rating = None -def __rateOnTrakt(rating: Union[int, str], media_type: str, media: Dict, unrate: bool = False) -> None: +def __rateOnTrakt( + rating: Union[int, str], media_type: str, media: Dict, unrate: bool = False +) -> None: logger.debug("Sending rating (%s) to Trakt.tv" % rating) params = media if utilities.isMovie(media_type): - key = 'movies' - params['rating'] = rating - if 'movieid' in media: - kodiUtilities.kodiJsonRequest({"jsonrpc": "2.0", "id": 1, "method": "VideoLibrary.SetMovieDetails", "params": { - "movieid": media['movieid'], "userrating": rating}}) + key = "movies" + params["rating"] = rating + if "movieid" in media: + kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "id": 1, + "method": "VideoLibrary.SetMovieDetails", + "params": {"movieid": media["movieid"], "userrating": rating}, + } + ) elif utilities.isShow(media_type): - key = 'shows' + key = "shows" # we need to remove this key or trakt will be confused - del(params["seasons"]) - params['rating'] = rating - if 'tvshowid' in media: - kodiUtilities.kodiJsonRequest({"jsonrpc": "2.0", "id": 1, "method": "VideoLibrary.SetTVShowDetails", "params": { - "tvshowid": media['tvshowid'], "userrating": rating}}) + del params["seasons"] + params["rating"] = rating + if "tvshowid" in media: + kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "id": 1, + "method": "VideoLibrary.SetTVShowDetails", + "params": {"tvshowid": media["tvshowid"], "userrating": rating}, + } + ) elif utilities.isSeason(media_type): - key = 'shows' - params['seasons'] = [{'rating': rating, 'number': media['season']}] + key = "shows" + params["seasons"] = [{"rating": rating, "number": media["season"]}] elif utilities.isEpisode(media_type): - key = 'episodes' - params['rating'] = rating - if 'episodeid' in media: - kodiUtilities.kodiJsonRequest({"jsonrpc": "2.0", "id": 1, "method": "VideoLibrary.SetEpisodeDetails", "params": { - "episodeid": media['episodeid'], "userrating": rating}}) + key = "episodes" + params["rating"] = rating + if "episodeid" in media: + kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "id": 1, + "method": "VideoLibrary.SetEpisodeDetails", + "params": {"episodeid": media["episodeid"], "userrating": rating}, + } + ) else: return root = {key: [params]} @@ -156,8 +198,12 @@ def __rateOnTrakt(rating: Union[int, str], media_type: str, media: Dict, unrate: if data: s = utilities.getFormattedItemName(media_type, media) - if 'not_found' in data and not data['not_found']['movies'] and not data['not_found']['episodes'] and not data['not_found']['shows']: - + if ( + "not_found" in data + and not data["not_found"]["movies"] + and not data["not_found"]["episodes"] + and not data["not_found"]["shows"] + ): if not unrate: kodiUtilities.notification(kodiUtilities.getString(32040), s) else: @@ -183,7 +229,7 @@ class RatingDialog(xbmcgui.WindowXMLDialog): 11036: 7, 11037: 8, 11038: 9, - 11039: 10 + 11039: 10, } focus_labels = { @@ -196,17 +242,26 @@ class RatingDialog(xbmcgui.WindowXMLDialog): 11036: 32034, 11037: 32035, 11038: 32036, - 11039: 32027 + 11039: 32027, } - def __init__(self, xmlFile: str, resourcePath: str, media_type: str, media: Dict, rerate: bool) -> None: + def __init__( + self, + xmlFile: str, + resourcePath: str, + media_type: str, + media: Dict, + rerate: bool, + ) -> None: self.media_type = media_type self.media = media self.rating = None self.rerate = rerate - self.default_rating = kodiUtilities.getSettingAsInt('rating_default') + self.default_rating = kodiUtilities.getSettingAsInt("rating_default") - def __new__(cls, xmlFile: str, resourcePath: str, media_type: str, media: Dict, rerate: bool) -> Any: + def __new__( + cls, xmlFile: str, resourcePath: str, media_type: str, media: Dict, rerate: bool + ) -> Any: return super(RatingDialog, cls).__new__(cls, xmlFile, resourcePath) def onInit(self) -> None: @@ -214,8 +269,12 @@ def onInit(self) -> None: self.getControl(10012).setLabel(s) rateID = 11029 + self.default_rating - if self.rerate and self.media['user']['ratings'] and int(self.media['user']['ratings']['rating']) > 0: - rateID = 11029 + int(self.media['user']['ratings']['rating']) + if ( + self.rerate + and self.media["user"]["ratings"] + and int(self.media["user"]["ratings"]["rating"]) > 0 + ): + rateID = 11029 + int(self.media["user"]["ratings"]["rating"]) self.setFocus(self.getControl(rateID)) def onClick(self, controlID: int) -> None: @@ -228,7 +287,11 @@ def onFocus(self, controlID: int) -> None: s = kodiUtilities.getString(self.focus_labels[controlID]) if self.rerate: - if self.media['user']['ratings'] and self.media['user']['ratings']['rating'] == self.buttons[controlID]: + if ( + self.media["user"]["ratings"] + and self.media["user"]["ratings"]["rating"] + == self.buttons[controlID] + ): if utilities.isMovie(self.media_type): s = kodiUtilities.getString(32037) elif utilities.isShow(self.media_type): @@ -242,4 +305,4 @@ def onFocus(self, controlID: int) -> None: self.getControl(10013).setLabel(s) else: - self.getControl(10013).setLabel('') + self.getControl(10013).setLabel("") diff --git a/resources/lib/script.py b/resources/lib/script.py index aa3d3478..a22f573d 100644 --- a/resources/lib/script.py +++ b/resources/lib/script.py @@ -15,14 +15,14 @@ def __getArguments() -> Dict: data = None default_actions = {0: "sync"} if len(sys.argv) == 1: - data = {'action': default_actions[0]} + data = {"action": default_actions[0]} else: data = {} for item in sys.argv: values = item.split("=") if len(values) == 2: data[values[0].lower()] = values[1] - data['action'] = data['action'].lower() + data["action"] = data["action"].lower() return data @@ -33,14 +33,14 @@ def run() -> None: xbmc.log("start trakt with arguments: %s" % args, xbmc.LOGINFO) - if args['action'] == 'auth_info': - data['action'] = 'auth_info' + if args["action"] == "auth_info": + data["action"] = "auth_info" - if args['action'] == 'contextmenu': + if args["action"] == "contextmenu": buttons = [] media_type = kodiUtilities.getMediaType() - if media_type in ['movie', 'show', 'season', 'episode']: + if media_type in ["movie", "show", "season", "episode"]: buttons.append("rate") buttons.append("togglewatched") buttons.append("addtowatchlist") @@ -56,36 +56,39 @@ def run() -> None: return logger.debug("'%s' selected from trakt.tv action menu" % _action) - args['action'] = _action - - if args['action'] == 'sync': - data = {'action': 'manualSync', 'silent': False} - if 'silent' in args: - data['silent'] = (args['silent'].lower() == 'true') - data['library'] = "all" - if 'library' in args and args['library'] in ['episodes', 'movies']: - data['library'] = args['library'] - - elif args['action'] in ['rate', 'unrate']: - data = {'action': args['action']} + args["action"] = _action + + if args["action"] == "sync": + data = {"action": "manualSync", "silent": False} + if "silent" in args: + data["silent"] = args["silent"].lower() == "true" + data["library"] = "all" + if "library" in args and args["library"] in ["episodes", "movies"]: + data["library"] = args["library"] + + elif args["action"] in ["rate", "unrate"]: + data = {"action": args["action"]} media_type = None - if 'media_type' in args and 'dbid' in args: - media_type = args['media_type'] + if "media_type" in args and "dbid" in args: + media_type = args["media_type"] try: - data['dbid'] = int(args['dbid']) + data["dbid"] = int(args["dbid"]) except ValueError: logger.debug( - "Manual %s triggered for library item, but DBID is invalid." % args['action']) + "Manual %s triggered for library item, but DBID is invalid." + % args["action"] + ) return - elif 'media_type' in args and 'remoteid' in args: - media_type = args['media_type'] - data['remoteid'] = args['remoteid'] + elif "media_type" in args and "remoteid" in args: + media_type = args["media_type"] + data["remoteid"] = args["remoteid"] try: - data['season'] = int(args['season']) - data['episode'] = int(args['episode']) + data["season"] = int(args["season"]) + data["episode"] = int(args["episode"]) except ValueError: logger.debug( - "Error parsing season or episode for manual %s" % args['action']) + "Error parsing season or episode for manual %s" % args["action"] + ) return except KeyError: pass @@ -94,95 +97,141 @@ def run() -> None: if not utilities.isValidMediaType(media_type): logger.debug("Error, not in video library.") return - data['dbid'] = int(xbmc.getInfoLabel('ListItem.DBID')) + data["dbid"] = int(xbmc.getInfoLabel("ListItem.DBID")) - - if media_type is None or media_type == 'None': + if media_type is None or media_type == "None": media_type = kodiUtilities.getMediaType() - xbmc.log("Got the mediatype from selected item: %s" % media_type, xbmc.LOGINFO) + xbmc.log( + "Got the mediatype from selected item: %s" % media_type, xbmc.LOGINFO + ) if media_type is None: logger.debug( - "Manual %s triggered on an unsupported content container." % args['action']) + "Manual %s triggered on an unsupported content container." + % args["action"] + ) elif utilities.isValidMediaType(media_type): - data['media_type'] = media_type - if 'dbid' in data: - logger.debug("Manual %s of library '%s' with an ID of '%s'." % ( - args['action'], media_type, data['dbid'])) + data["media_type"] = media_type + if "dbid" in data: + logger.debug( + "Manual %s of library '%s' with an ID of '%s'." + % (args["action"], media_type, data["dbid"]) + ) if utilities.isMovie(media_type): result = kodiUtilities.getMovieDetailsFromKodi( - data['dbid'], ['imdbnumber', 'uniqueid', 'title', 'year']) + data["dbid"], ["imdbnumber", "uniqueid", "title", "year"] + ) if not result: logger.debug( - "No data was returned from Kodi, aborting manual %s." % args['action']) + "No data was returned from Kodi, aborting manual %s." + % args["action"] + ) return elif utilities.isShow(media_type): - tvshow_id = data['dbid'] + tvshow_id = data["dbid"] elif utilities.isSeason(media_type): result = kodiUtilities.getSeasonDetailsFromKodi( - data['dbid'], ['tvshowid', 'season']) + data["dbid"], ["tvshowid", "season"] + ) if not result: logger.debug( - "No data was returned from Kodi, aborting manual %s." % args['action']) + "No data was returned from Kodi, aborting manual %s." + % args["action"] + ) return - tvshow_id = result['tvshowid'] - data['season'] = result['season'] + tvshow_id = result["tvshowid"] + data["season"] = result["season"] elif utilities.isEpisode(media_type): result = kodiUtilities.getEpisodeDetailsFromKodi( - data['dbid'], ['season', 'episode', 'tvshowid']) + data["dbid"], ["season", "episode", "tvshowid"] + ) if not result: logger.debug( - "No data was returned from Kodi, aborting manual %s." % args['action']) + "No data was returned from Kodi, aborting manual %s." + % args["action"] + ) return - tvshow_id = result['tvshowid'] - data['season'] = result['season'] - data['episode'] = result['episode'] - - if utilities.isShow(media_type) or utilities.isSeason(media_type) or utilities.isEpisode(media_type): + tvshow_id = result["tvshowid"] + data["season"] = result["season"] + data["episode"] = result["episode"] + + if ( + utilities.isShow(media_type) + or utilities.isSeason(media_type) + or utilities.isEpisode(media_type) + ): result = kodiUtilities.getShowDetailsFromKodi( - tvshow_id, ['imdbnumber', 'uniqueid']) + tvshow_id, ["imdbnumber", "uniqueid"] + ) if not result: logger.debug( - "No data was returned from Kodi, aborting manual %s." % args['action']) + "No data was returned from Kodi, aborting manual %s." + % args["action"] + ) return - data['video_ids'] = result['uniqueid'] + data["video_ids"] = result["uniqueid"] else: - data['video_id'] = data['remoteid'] - if 'season' in data and 'episode' in data: - logger.debug("Manual %s of non-library '%s' S%02dE%02d, with an ID of '%s'." % ( - args['action'], media_type, data['season'], data['episode'], data['remoteid'])) - elif 'season' in data: - logger.debug("Manual %s of non-library '%s' S%02d, with an ID of '%s'." % - (args['action'], media_type, data['season'], data['remoteid'])) + data["video_id"] = data["remoteid"] + if "season" in data and "episode" in data: + logger.debug( + "Manual %s of non-library '%s' S%02dE%02d, with an ID of '%s'." + % ( + args["action"], + media_type, + data["season"], + data["episode"], + data["remoteid"], + ) + ) + elif "season" in data: + logger.debug( + "Manual %s of non-library '%s' S%02d, with an ID of '%s'." + % (args["action"], media_type, data["season"], data["remoteid"]) + ) else: - logger.debug("Manual %s of non-library '%s' with an ID of '%s'." % - (args['action'], media_type, data['remoteid'])) - - if args['action'] == 'rate' and 'rating' in args: - if args['rating'] in ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']: - data['rating'] = int(args['rating']) - - data = {'action': 'manualRating', 'ratingData': data} + logger.debug( + "Manual %s of non-library '%s' with an ID of '%s'." + % (args["action"], media_type, data["remoteid"]) + ) + + if args["action"] == "rate" and "rating" in args: + if args["rating"] in [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "10", + ]: + data["rating"] = int(args["rating"]) + + data = {"action": "manualRating", "ratingData": data} else: - logger.debug("Manual %s of '%s' is unsupported." % - (args['action'], media_type)) + logger.debug( + "Manual %s of '%s' is unsupported." % (args["action"], media_type) + ) - elif args['action'] == 'togglewatched': + elif args["action"] == "togglewatched": media_type = kodiUtilities.getMediaType() - if media_type in ['movie', 'show', 'season', 'episode']: - data = {'media_type': media_type} + if media_type in ["movie", "show", "season", "episode"]: + data = {"media_type": media_type} if utilities.isMovie(media_type): - dbid = int(xbmc.getInfoLabel('ListItem.DBID')) + dbid = int(xbmc.getInfoLabel("ListItem.DBID")) result = kodiUtilities.getMovieDetailsFromKodi( - dbid, ['imdbnumber', 'uniqueid', 'title', 'year', 'playcount']) + dbid, ["imdbnumber", "uniqueid", "title", "year", "playcount"] + ) if result: - if result['playcount'] == 0: - data['ids'] = result['uniqueid'] + if result["playcount"] == 0: + data["ids"] = result["uniqueid"] else: logger.debug("Movie alread marked as watched in Kodi.") else: @@ -190,131 +239,171 @@ def run() -> None: return elif utilities.isEpisode(media_type): - dbid = int(xbmc.getInfoLabel('ListItem.DBID')) + dbid = int(xbmc.getInfoLabel("ListItem.DBID")) result = kodiUtilities.getEpisodeDetailsFromKodi( - dbid, ['showtitle', 'season', 'episode', 'tvshowid', 'playcount']) + dbid, ["showtitle", "season", "episode", "tvshowid", "playcount"] + ) if result: - if result['playcount'] == 0: - data['ids'] = result['show_ids'] - data['season'] = result['season'] - data['number'] = result['episode'] - data['title'] = result['showtitle'] + if result["playcount"] == 0: + data["ids"] = result["show_ids"] + data["season"] = result["season"] + data["number"] = result["episode"] + data["title"] = result["showtitle"] else: - logger.debug( - "Episode already marked as watched in Kodi.") + logger.debug("Episode already marked as watched in Kodi.") else: logger.debug("Error getting episode details from Kodi.") return elif utilities.isSeason(media_type): showID = None - showTitle = xbmc.getInfoLabel('ListItem.TVShowTitle') - result = kodiUtilities.kodiJsonRequest({'jsonrpc': '2.0', 'method': 'VideoLibrary.GetTVShows', 'params': { - 'properties': ['title', 'imdbnumber', 'uniqueid', 'year']}, 'id': 0}) - if result and 'tvshows' in result: - for show in result['tvshows']: - if show['title'] == showTitle: - showID = show['tvshowid'] - data['ids'] = show['uniqueid'] - data['title'] = show['title'] + showTitle = xbmc.getInfoLabel("ListItem.TVShowTitle") + result = kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "method": "VideoLibrary.GetTVShows", + "params": { + "properties": ["title", "imdbnumber", "uniqueid", "year"] + }, + "id": 0, + } + ) + if result and "tvshows" in result: + for show in result["tvshows"]: + if show["title"] == showTitle: + showID = show["tvshowid"] + data["ids"] = show["uniqueid"] + data["title"] = show["title"] break else: logger.debug("Error getting TV shows from Kodi.") return - season = xbmc.getInfoLabel('ListItem.Season') + season = xbmc.getInfoLabel("ListItem.Season") if season == "": season = 0 else: season = int(season) - result = kodiUtilities.kodiJsonRequest({'jsonrpc': '2.0', 'method': 'VideoLibrary.GetEpisodes', 'params': { - 'tvshowid': showID, 'season': season, 'properties': ['season', 'episode', 'playcount']}, 'id': 0}) - if result and 'episodes' in result: + result = kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "method": "VideoLibrary.GetEpisodes", + "params": { + "tvshowid": showID, + "season": season, + "properties": ["season", "episode", "playcount"], + }, + "id": 0, + } + ) + if result and "episodes" in result: episodes = [] - for episode in result['episodes']: - if episode['playcount'] == 0: - episodes.append(episode['episode']) + for episode in result["episodes"]: + if episode["playcount"] == 0: + episodes.append(episode["episode"]) if len(episodes) == 0: logger.debug( - "'%s - Season %d' is already marked as watched." % (showTitle, season)) + "'%s - Season %d' is already marked as watched." + % (showTitle, season) + ) return - data['season'] = season - data['episodes'] = episodes + data["season"] = season + data["episodes"] = episodes else: logger.debug( - "Error getting episodes from '%s' for Season %d" % (showTitle, season)) + "Error getting episodes from '%s' for Season %d" + % (showTitle, season) + ) return elif utilities.isShow(media_type): - dbid = int(xbmc.getInfoLabel('ListItem.DBID')) + dbid = int(xbmc.getInfoLabel("ListItem.DBID")) result = kodiUtilities.getShowDetailsFromKodi( - dbid, ['year', 'imdbnumber', 'uniqueid']) + dbid, ["year", "imdbnumber", "uniqueid"] + ) if not result: logger.debug("Error getting show details from Kodi.") return - showTitle = result['label'] - data['ids'] = result['uniqueid'] - result = kodiUtilities.kodiJsonRequest({'jsonrpc': '2.0', 'method': 'VideoLibrary.GetEpisodes', 'params': { - 'tvshowid': dbid, 'properties': ['season', 'episode', 'playcount', 'showtitle']}, 'id': 0}) - if result and 'episodes' in result: + showTitle = result["label"] + data["ids"] = result["uniqueid"] + result = kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "method": "VideoLibrary.GetEpisodes", + "params": { + "tvshowid": dbid, + "properties": [ + "season", + "episode", + "playcount", + "showtitle", + ], + }, + "id": 0, + } + ) + if result and "episodes" in result: i = 0 s = {} - for e in result['episodes']: - data['title'] = e['showtitle'] - season = str(e['season']) + for e in result["episodes"]: + data["title"] = e["showtitle"] + season = str(e["season"]) if season not in s: s[season] = [] - if e['playcount'] == 0: - s[season].append(e['episode']) + if e["playcount"] == 0: + s[season].append(e["episode"]) i += 1 if i == 0: - logger.debug( - "'%s' is already marked as watched." % showTitle) + logger.debug("'%s' is already marked as watched." % showTitle) return - data['seasons'] = dict((k, v) - for k, v in list(s.items()) if v) + data["seasons"] = dict((k, v) for k, v in list(s.items()) if v) else: logger.debug( - "Error getting episode details for '%s' from Kodi." % showTitle) + "Error getting episode details for '%s' from Kodi." % showTitle + ) return if len(data) > 1: - logger.debug("Marking '%s' with the following data '%s' as watched on Trakt.tv" % ( - media_type, str(data))) - data['action'] = 'markWatched' + logger.debug( + "Marking '%s' with the following data '%s' as watched on Trakt.tv" + % (media_type, str(data)) + ) + data["action"] = "markWatched" # execute toggle watched action xbmc.executebuiltin("Action(ToggleWatched)") - elif args['action'] == 'addtowatchlist': + elif args["action"] == "addtowatchlist": media_type = kodiUtilities.getMediaType() - if media_type in ['movie', 'show', 'season', 'episode']: - data = {'media_type': media_type} + if media_type in ["movie", "show", "season", "episode"]: + data = {"media_type": media_type} if utilities.isMovie(media_type): - dbid = int(xbmc.getInfoLabel('ListItem.DBID')) + dbid = int(xbmc.getInfoLabel("ListItem.DBID")) result = kodiUtilities.getMovieDetailsFromKodi( - dbid, ['imdbnumber', 'uniqueid', 'title', 'year', 'playcount']) + dbid, ["imdbnumber", "uniqueid", "title", "year", "playcount"] + ) if result: - data['ids'] = result['uniqueid'] + data["ids"] = result["uniqueid"] else: logger.debug("Error getting movie details from Kodi.") return elif utilities.isEpisode(media_type): - dbid = int(xbmc.getInfoLabel('ListItem.DBID')) + dbid = int(xbmc.getInfoLabel("ListItem.DBID")) result = kodiUtilities.getEpisodeDetailsFromKodi( - dbid, ['showtitle', 'season', 'episode', 'tvshowid', 'playcount']) + dbid, ["showtitle", "season", "episode", "tvshowid", "playcount"] + ) if result: - data['ids'] = result['show_ids'] - data['season'] = result['season'] - data['number'] = result['episode'] - data['title'] = result['showtitle'] + data["ids"] = result["show_ids"] + data["season"] = result["season"] + data["number"] = result["episode"] + data["title"] = result["showtitle"] else: logger.debug("Error getting episode details from Kodi.") @@ -322,78 +411,112 @@ def run() -> None: elif utilities.isSeason(media_type): showID = None - showTitle = xbmc.getInfoLabel('ListItem.TVShowTitle') - result = kodiUtilities.kodiJsonRequest({'jsonrpc': '2.0', 'method': 'VideoLibrary.GetTVShows', 'params': { - 'properties': ['title', 'imdbnumber', 'uniqueid', 'year']}, 'id': 0}) - if result and 'tvshows' in result: - for show in result['tvshows']: - if show['title'] == showTitle: - showID = show['tvshowid'] - data['id'] = show['imdbnumber'] - data['title'] = show['title'] + showTitle = xbmc.getInfoLabel("ListItem.TVShowTitle") + result = kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "method": "VideoLibrary.GetTVShows", + "params": { + "properties": ["title", "imdbnumber", "uniqueid", "year"] + }, + "id": 0, + } + ) + if result and "tvshows" in result: + for show in result["tvshows"]: + if show["title"] == showTitle: + showID = show["tvshowid"] + data["id"] = show["imdbnumber"] + data["title"] = show["title"] break else: logger.debug("Error getting TV shows from Kodi.") return - season = xbmc.getInfoLabel('ListItem.Season') + season = xbmc.getInfoLabel("ListItem.Season") if season == "": season = 0 else: season = int(season) - result = kodiUtilities.kodiJsonRequest({'jsonrpc': '2.0', 'method': 'VideoLibrary.GetEpisodes', - 'params': {'tvshowid': showID, 'season': season, - 'properties': ['season', 'episode', 'playcount']}, - 'id': 0}) - if result and 'episodes' in result: + result = kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "method": "VideoLibrary.GetEpisodes", + "params": { + "tvshowid": showID, + "season": season, + "properties": ["season", "episode", "playcount"], + }, + "id": 0, + } + ) + if result and "episodes" in result: episodes = [] - for episode in result['episodes']: - if episode['playcount'] == 0: - episodes.append(episode['episode']) + for episode in result["episodes"]: + if episode["playcount"] == 0: + episodes.append(episode["episode"]) - data['season'] = season - data['episodes'] = episodes + data["season"] = season + data["episodes"] = episodes else: logger.debug( - "Error getting episodes from '%s' for Season %d" % (showTitle, season)) + "Error getting episodes from '%s' for Season %d" + % (showTitle, season) + ) return elif utilities.isShow(media_type): - dbid = int(xbmc.getInfoLabel('ListItem.DBID')) + dbid = int(xbmc.getInfoLabel("ListItem.DBID")) result = kodiUtilities.getShowDetailsFromKodi( - dbid, ['year', 'imdbnumber', 'uniqueid']) + dbid, ["year", "imdbnumber", "uniqueid"] + ) if not result: logger.debug("Error getting show details from Kodi.") return - showTitle = result['label'] - data['ids'] = result['uniqueid'] - result = kodiUtilities.kodiJsonRequest({'jsonrpc': '2.0', 'method': 'VideoLibrary.GetEpisodes', - 'params': {'tvshowid': dbid, 'properties': - ['season', 'episode', 'playcount', 'showtitle']}, 'id': 0}) - if result and 'episodes' in result: + showTitle = result["label"] + data["ids"] = result["uniqueid"] + result = kodiUtilities.kodiJsonRequest( + { + "jsonrpc": "2.0", + "method": "VideoLibrary.GetEpisodes", + "params": { + "tvshowid": dbid, + "properties": [ + "season", + "episode", + "playcount", + "showtitle", + ], + }, + "id": 0, + } + ) + if result and "episodes" in result: s = {} - for e in result['episodes']: - data['title'] = e['showtitle'] - season = str(e['season']) + for e in result["episodes"]: + data["title"] = e["showtitle"] + season = str(e["season"]) if season not in s: s[season] = [] - if e['playcount'] == 0: - s[season].append(e['episode']) + if e["playcount"] == 0: + s[season].append(e["episode"]) - data['seasons'] = dict((k, v) - for k, v in list(s.items()) if v) + data["seasons"] = dict((k, v) for k, v in list(s.items()) if v) else: logger.debug( - "Error getting episode details for '%s' from Kodi." % showTitle) + "Error getting episode details for '%s' from Kodi." % showTitle + ) return if len(data) > 1: - logger.debug("Adding '%s' with the following data '%s' to users watchlist on Trakt.tv" - % (media_type, str(data))) - data['action'] = 'addtowatchlist' + logger.debug( + "Adding '%s' with the following data '%s' to users watchlist on Trakt.tv" + % (media_type, str(data)) + ) + data["action"] = "addtowatchlist" q = sqlitequeue.SqliteQueue() - if 'action' in data: + if "action" in data: logger.debug("Queuing for dispatch: %s" % data) q.append(data) diff --git a/resources/lib/scrobbler.py b/resources/lib/scrobbler.py index 0baf3a44..bd9d7daa 100644 --- a/resources/lib/scrobbler.py +++ b/resources/lib/scrobbler.py @@ -83,7 +83,8 @@ def transitionCheck(self, isSeek: bool = False) -> None: response = self.__scrobble("stop") if response is not None: logger.debug("Scrobble response: %s" % str(response)) - self.videosToRate.append(self.curVideoInfo) + if self.curVideoInfo: + self.videosToRate.append(self.curVideoInfo) # update current information self.curMPEpisode = epIndex self.curVideoInfo = kodiUtilities.kodiRpcToTraktMediaObject( @@ -179,9 +180,9 @@ def transitionCheck(self, isSeek: bool = False) -> None: } if "year" in self.curVideo: - self.traktShowSummary[ - "year" - ] = self.curVideo["year"] + self.traktShowSummary["year"] = ( + self.curVideo["year"] + ) else: logger.debug( "Scrobble Couldn't set curVideoInfo/traktShowSummary for episode type" @@ -475,7 +476,7 @@ def playbackSeek(self) -> None: self.transitionCheck(isSeek=True) def playbackEnded(self) -> None: - if not self.isPVR: + if not self.isPVR and self.curVideoInfo: self.videosToRate.append(self.curVideoInfo) if not self.isPlaying: return @@ -543,10 +544,13 @@ def __scrobble(self, status: str) -> Optional[Dict]: adjustedDuration = int( self.videoDuration / self.curVideo["multi_episode_count"] ) - watchedPercent = ( - (self.watchedTime - (adjustedDuration * self.curMPEpisode)) - / adjustedDuration - ) * 100 + if adjustedDuration > 0: + watchedPercent = ( + (self.watchedTime - (adjustedDuration * self.curMPEpisode)) + / adjustedDuration + ) * 100 + else: + watchedPercent = 0 logger.debug( "scrobble sending show object: %s" % str(self.traktShowSummary) diff --git a/resources/lib/service.py b/resources/lib/service.py index 9d28f4ff..6fb7baa3 100644 --- a/resources/lib/service.py +++ b/resources/lib/service.py @@ -400,7 +400,9 @@ def addEpisodesToHistory(self, summaryInfo: Dict, s: str) -> None: else: kodiUtilities.notification(kodiUtilities.getString(32114), s) - def doSync(self, manual: bool = False, silent: bool = False, library: str = "all") -> None: + def doSync( + self, manual: bool = False, silent: bool = False, library: str = "all" + ) -> None: self.syncThread = syncThread(manual, silent, library) self.syncThread.start() @@ -410,7 +412,9 @@ class syncThread(threading.Thread): _runSilent: bool = False _library: str = "all" - def __init__(self, isManual: bool = False, runSilent: bool = False, library: str = "all") -> None: + def __init__( + self, isManual: bool = False, runSilent: bool = False, library: str = "all" + ) -> None: threading.Thread.__init__(self) self.name = "trakt-sync" self._isManual = isManual diff --git a/resources/lib/sqlitequeue.py b/resources/lib/sqlitequeue.py index 443c9988..7e85cbff 100644 --- a/resources/lib/sqlitequeue.py +++ b/resources/lib/sqlitequeue.py @@ -18,32 +18,26 @@ logger = logging.getLogger(__name__) -__addon__ = xbmcaddon.Addon('script.trakt') +__addon__ = xbmcaddon.Addon("script.trakt") + # code from http://flask.pocoo.org/snippets/88/ with some modifications class SqliteQueue(object): - _create = ( - 'CREATE TABLE IF NOT EXISTS queue ' - '(' - ' id INTEGER PRIMARY KEY AUTOINCREMENT,' - ' item BLOB' - ')' - ) - _count = 'SELECT COUNT(*) FROM queue' - _iterate = 'SELECT id, item FROM queue' - _append = 'INSERT INTO queue (item) VALUES (?)' - _write_lock = 'BEGIN IMMEDIATE' - _get = ( - 'SELECT id, item FROM queue ' - 'ORDER BY id LIMIT 1' - ) - _del = 'DELETE FROM queue WHERE id = ?' - _peek = ( - 'SELECT item FROM queue ' - 'ORDER BY id LIMIT 1' - ) - _purge = 'DELETE FROM queue' + "CREATE TABLE IF NOT EXISTS queue " + "(" + " id INTEGER PRIMARY KEY AUTOINCREMENT," + " item BLOB" + ")" + ) + _count = "SELECT COUNT(*) FROM queue" + _iterate = "SELECT id, item FROM queue" + _append = "INSERT INTO queue (item) VALUES (?)" + _write_lock = "BEGIN IMMEDIATE" + _get = "SELECT id, item FROM queue ORDER BY id LIMIT 1" + _del = "DELETE FROM queue WHERE id = ?" + _peek = "SELECT item FROM queue ORDER BY id LIMIT 1" + _purge = "DELETE FROM queue" path: str _connection_cache: Dict[int, sqlite3.Connection] @@ -53,7 +47,7 @@ def __init__(self) -> None: if not xbmcvfs.exists(self.path): logger.debug("Making path structure: %s" % repr(self.path)) xbmcvfs.mkdir(self.path) - self.path = os.path.join(self.path, 'queue.db') + self.path = os.path.join(self.path, "queue.db") self._connection_cache = {} with self._get_conn() as conn: conn.execute(self._create) diff --git a/resources/lib/sync.py b/resources/lib/sync.py index 536fdca9..b02382b8 100644 --- a/resources/lib/sync.py +++ b/resources/lib/sync.py @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) -class Sync(): +class Sync: traktapi: Any = None show_progress: bool = False run_silent: bool = False @@ -22,67 +22,92 @@ class Sync(): notify: bool = False notify_during_playback: bool = False - def __init__(self, show_progress: bool = False, run_silent: bool = False, library: str = "all", api: Any = None) -> None: + def __init__( + self, + show_progress: bool = False, + run_silent: bool = False, + library: str = "all", + api: Any = None, + ) -> None: self.traktapi = api self.show_progress = show_progress self.run_silent = run_silent self.library = library if self.show_progress and self.run_silent: logger.debug("Sync is being run silently.") - self.sync_on_update = getSettingAsBool('sync_on_update') - self.notify = getSettingAsBool('show_sync_notifications') - self.notify_during_playback = not getSettingAsBool("hide_notifications_playback") + self.sync_on_update = getSettingAsBool("sync_on_update") + self.notify = getSettingAsBool("show_sync_notifications") + self.notify_during_playback = not getSettingAsBool( + "hide_notifications_playback" + ) def __syncCheck(self, media_type: str) -> bool: - return self.__syncCollectionCheck(media_type) or self.__syncWatchedCheck(media_type) or self.__syncPlaybackCheck(media_type) or self.__syncRatingsCheck() + return ( + self.__syncCollectionCheck(media_type) + or self.__syncWatchedCheck(media_type) + or self.__syncPlaybackCheck(media_type) + or self.__syncRatingsCheck() + ) def __syncPlaybackCheck(self, media_type: str) -> bool: - if media_type == 'movies': - return getSettingAsBool('trakt_movie_playback') + if media_type == "movies": + return getSettingAsBool("trakt_movie_playback") else: - return getSettingAsBool('trakt_episode_playback') + return getSettingAsBool("trakt_episode_playback") def __syncCollectionCheck(self, media_type: str) -> bool: - if media_type == 'movies': - return getSettingAsBool('add_movies_to_trakt') or getSettingAsBool('clean_trakt_movies') + if media_type == "movies": + return getSettingAsBool("add_movies_to_trakt") or getSettingAsBool( + "clean_trakt_movies" + ) else: - return getSettingAsBool('add_episodes_to_trakt') or getSettingAsBool('clean_trakt_episodes') + return getSettingAsBool("add_episodes_to_trakt") or getSettingAsBool( + "clean_trakt_episodes" + ) def __syncRatingsCheck(self) -> bool: - return getSettingAsBool('trakt_sync_ratings') + return getSettingAsBool("trakt_sync_ratings") def __syncWatchedCheck(self, media_type: str) -> bool: - if media_type == 'movies': - return getSettingAsBool('trakt_movie_playcount') or getSettingAsBool('kodi_movie_playcount') + if media_type == "movies": + return getSettingAsBool("trakt_movie_playcount") or getSettingAsBool( + "kodi_movie_playcount" + ) else: - return getSettingAsBool('trakt_episode_playcount') or getSettingAsBool('kodi_episode_playcount') + return getSettingAsBool("trakt_episode_playcount") or getSettingAsBool( + "kodi_episode_playcount" + ) @property def show_notification(self) -> bool: - return not self.show_progress and self.sync_on_update and self.notify and (self.notify_during_playback or not xbmc.Player().isPlayingVideo()) + return ( + not self.show_progress + and self.sync_on_update + and self.notify + and (self.notify_during_playback or not xbmc.Player().isPlayingVideo()) + ) def sync(self) -> None: logger.debug("Starting synchronization with Trakt.tv") - if self.__syncCheck('movies'): + if self.__syncCheck("movies"): if self.library in ["all", "movies"]: syncMovies.SyncMovies(self, progress) else: - logger.debug( - "Movie sync is being skipped for this manual sync.") + logger.debug("Movie sync is being skipped for this manual sync.") else: logger.debug("Movie sync is disabled, skipping.") - if self.__syncCheck('episodes'): + if self.__syncCheck("episodes"): if self.library in ["all", "episodes"]: - if not (self.__syncCheck('movies') and self.IsCanceled()): + if not (self.__syncCheck("movies") and self.IsCanceled()): syncEpisodes.SyncEpisodes(self, progress) else: logger.debug( - "Episode sync is being skipped because movie sync was canceled.") + "Episode sync is being skipped because movie sync was canceled." + ) else: - logger.debug( - "Episode sync is being skipped for this manual sync.") + logger.debug("Episode sync is being skipped for this manual sync.") else: logger.debug("Episode sync is disabled, skipping.") @@ -97,20 +122,19 @@ def IsCanceled(self) -> bool: def UpdateProgress(self, *args: Any, **kwargs: Any) -> None: if self.show_progress and not self.run_silent: - line1 = "" line2 = "" line3 = "" - if 'line1' in kwargs: + if "line1" in kwargs: line1 = kwargs["line1"] - if 'line2' in kwargs: + if "line2" in kwargs: line2 = kwargs["line2"] - if 'line3' in kwargs: + if "line3" in kwargs: line3 = kwargs["line3"] percent = args[0] - message = f'{line1}\n{line2}\n{line3}' + message = f"{line1}\n{line2}\n{line3}" progress.update(percent, message) diff --git a/resources/lib/syncEpisodes.py b/resources/lib/syncEpisodes.py index 860abcbe..61cb53e2 100644 --- a/resources/lib/syncEpisodes.py +++ b/resources/lib/syncEpisodes.py @@ -147,10 +147,11 @@ def __kodiLoadShows(self) -> Tuple[Optional[Dict], Optional[Dict]]: logger.debug("[Episodes Sync] Getting episode data from Kodi") for show_col1 in tvshows: i += 1 - y = ((i / x) * 8) + 2 - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32097) % (i, x) - ) + if x > 0: + y = ((i / x) * 8) + 2 + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32097) % (i, x) + ) if "ids" not in show_col1: logger.debug( @@ -218,7 +219,11 @@ def __kodiLoadShows(self) -> Tuple[Optional[Dict], Optional[Dict]]: self.sync.UpdateProgress(10, line2=kodiUtilities.getString(32098)) return resultCollected, resultWatched - def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[Dict, bool], Union[Dict, bool]]: + def __traktLoadShows( + self, + ) -> Tuple[ + Union[Dict, bool], Union[Dict, bool], Union[Dict, bool], Union[Dict, bool] + ]: self.sync.UpdateProgress( 10, line1=kodiUtilities.getString(32099), @@ -263,10 +268,11 @@ def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[ showsCollected = {"shows": []} for _, show in traktShowsCollected: i += 1 - y = ((i / x) * 4) + 12 - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32102) % (i, x) - ) + if x > 0: + y = ((i / x) * 4) + 12 + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32102) % (i, x) + ) # will keep the data in python structures - just like the KODI response show = show.to_dict() @@ -278,10 +284,11 @@ def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[ showsWatched = {"shows": []} for _, show in traktShowsWatched: i += 1 - y = ((i / x) * 4) + 16 - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32102) % (i, x) - ) + if x > 0: + y = ((i / x) * 4) + 16 + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32102) % (i, x) + ) # will keep the data in python structures - just like the KODI response show = show.to_dict() @@ -293,10 +300,11 @@ def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[ showsRated = {"shows": []} for _, show in traktShowsRated: i += 1 - y = ((i / x) * 4) + 20 - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32102) % (i, x) - ) + if x > 0: + y = ((i / x) * 4) + 20 + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32102) % (i, x) + ) # will keep the data in python structures - just like the KODI response show = show.to_dict() @@ -308,10 +316,11 @@ def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[ episodesRated = {"shows": []} for _, show in traktEpisodesRated: i += 1 - y = ((i / x) * 4) + 20 - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32102) % (i, x) - ) + if x > 0: + y = ((i / x) * 4) + 20 + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32102) % (i, x) + ) # will keep the data in python structures - just like the KODI response show = show.to_dict() @@ -322,7 +331,9 @@ def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[ return showsCollected, showsWatched, showsRated, episodesRated - def __traktLoadShowsPlaybackProgress(self, fromPercent: int, toPercent: int) -> Union[Dict, bool, None]: + def __traktLoadShowsPlaybackProgress( + self, fromPercent: int, toPercent: int + ) -> Union[Dict, bool, None]: if ( kodiUtilities.getSettingAsBool("trakt_episode_playback") and not self.sync.IsCanceled() @@ -348,10 +359,11 @@ def __traktLoadShowsPlaybackProgress(self, fromPercent: int, toPercent: int) -> showsProgress = {"shows": []} for show in traktProgressShows: i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32120) % (i, x) - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32120) % (i, x) + ) # will keep the data in python structures - just like the KODI response show = show.to_dict() @@ -417,12 +429,13 @@ def __addEpisodesToTraktCollection( if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32069) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32069) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) request = {"shows": chunk} logger.debug("[traktAddEpisodes] Shows to add %s" % request) @@ -552,10 +565,13 @@ def __addEpisodesToTraktWatched( epCount = utilities.countEpisodes([show]) title = show["title"] i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), line2=title, line3=kodiUtilities.getString(32073) % epCount - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=title, + line3=kodiUtilities.getString(32073) % epCount, + ) s = {"shows": [show]} logger.debug("[traktUpdateEpisodes] Shows to update %s" % s) @@ -574,7 +590,12 @@ def __addEpisodesToTraktWatched( ) def __addEpisodesToKodiWatched( - self, traktShows: Dict, kodiShows: Dict, kodiShowsCollected: Dict, fromPercent: int, toPercent: int + self, + traktShows: Dict, + kodiShows: Dict, + kodiShowsCollected: Dict, + fromPercent: int, + toPercent: int, ) -> None: if ( kodiUtilities.getSettingAsBool("kodi_episode_playcount") @@ -646,12 +667,13 @@ def __addEpisodesToKodiWatched( if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32108) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32108) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) logger.debug("[Episodes Sync] chunk %s" % str(chunk)) result = kodiUtilities.kodiJsonRequest(chunk) @@ -661,7 +683,9 @@ def __addEpisodesToKodiWatched( toPercent, line2=kodiUtilities.getString(32109) % len(episodes) ) - def __addEpisodeProgressToKodi(self, traktShows: Dict, kodiShows: Dict, fromPercent: int, toPercent: int) -> None: + def __addEpisodeProgressToKodi( + self, traktShows: Dict, kodiShows: Dict, fromPercent: int, toPercent: int + ) -> None: if ( kodiUtilities.getSettingAsBool("trakt_episode_playback") and traktShows @@ -702,16 +726,17 @@ def __addEpisodeProgressToKodi(self, traktShows: Dict, kodiShows: Dict, fromPerc for episode in season["episodes"]: # If library item doesn't have a runtime set get it from # Trakt to avoid later using 0 in runtime * progress_pct. - if not episode["runtime"]: - episode["runtime"] = ( - self.sync.traktapi.getEpisodeSummary( - show["ids"]["trakt"], - season["number"], - episode["number"], - extended="full", - ).runtime - * 60 + if not episode.get("runtime"): + summary = self.sync.traktapi.getEpisodeSummary( + show["ids"]["trakt"], + season["number"], + episode["number"], + extended="full", + ) + runtime = ( + summary.runtime if summary and summary.runtime else 0 ) + episode["runtime"] = runtime * 60 episodes.append( { "episodeid": episode["ids"]["episodeid"], @@ -750,12 +775,13 @@ def __addEpisodeProgressToKodi(self, traktShows: Dict, kodiShows: Dict, fromPerc if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32130) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32130) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) kodiUtilities.kodiJsonRequest(chunk) @@ -763,7 +789,9 @@ def __addEpisodeProgressToKodi(self, traktShows: Dict, kodiShows: Dict, fromPerc toPercent, line2=kodiUtilities.getString(32131) % len(episodes) ) - def __syncShowsRatings(self, traktShows: Dict, kodiShows: Dict, fromPercent: int, toPercent: int) -> None: + def __syncShowsRatings( + self, traktShows: Dict, kodiShows: Dict, fromPercent: int, toPercent: int + ) -> None: if ( kodiUtilities.getSettingAsBool("trakt_sync_ratings") and traktShows @@ -847,13 +875,14 @@ def __syncShowsRatings(self, traktShows: Dict, kodiShows: Dict, fromPercent: int if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line1="", - line2=kodiUtilities.getString(32177) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line1="", + line2=kodiUtilities.getString(32177) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) kodiUtilities.kodiJsonRequest(chunk) @@ -861,7 +890,9 @@ def __syncShowsRatings(self, traktShows: Dict, kodiShows: Dict, fromPercent: int toPercent, line2=kodiUtilities.getString(32178) % len(shows) ) - def __syncEpisodeRatings(self, traktShows: Dict, kodiShows: Dict, fromPercent: int, toPercent: int) -> None: + def __syncEpisodeRatings( + self, traktShows: Dict, kodiShows: Dict, fromPercent: int, toPercent: int + ) -> None: if ( kodiUtilities.getSettingAsBool("trakt_sync_ratings") and traktShows @@ -952,13 +983,14 @@ def __syncEpisodeRatings(self, traktShows: Dict, kodiShows: Dict, fromPercent: i if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line1="", - line2=kodiUtilities.getString(32174) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line1="", + line2=kodiUtilities.getString(32174) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) kodiUtilities.kodiJsonRequest(chunk) @@ -980,9 +1012,12 @@ def __getShowAsString(self, show: Dict, short: bool = False) -> str: ) else: episodes = ", ".join( - [str(i) for i in show["shows"]["seasons"][season]] + [str(i["number"]) for i in season.get("episodes", [])] + ) + s = "Season: %s, Episodes: %s" % ( + str(season.get("number")), + episodes, ) - s = "Season: %d, Episodes: %s" % (season, episodes) p.append(s) else: p = ["All"] diff --git a/resources/lib/syncMovies.py b/resources/lib/syncMovies.py index c0323a96..302c6906 100644 --- a/resources/lib/syncMovies.py +++ b/resources/lib/syncMovies.py @@ -140,7 +140,9 @@ def __traktLoadMovies(self) -> List[Dict]: return movies - def __traktLoadMoviesPlaybackProgress(self, fromPercent: int, toPercent: int) -> Union[Dict, bool]: + def __traktLoadMoviesPlaybackProgress( + self, fromPercent: int, toPercent: int + ) -> Union[Dict, bool]: if ( kodiUtilities.getSettingAsBool("trakt_movie_playback") and not self.sync.IsCanceled() @@ -161,10 +163,11 @@ def __traktLoadMoviesPlaybackProgress(self, fromPercent: int, toPercent: int) -> moviesProgress = {"movies": []} for movie in traktProgressMovies: i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), line2=kodiUtilities.getString(32123) % (i, x) - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), line2=kodiUtilities.getString(32123) % (i, x) + ) # will keep the data in python structures - just like the KODI response movie = movie.to_dict() @@ -176,7 +179,11 @@ def __traktLoadMoviesPlaybackProgress(self, fromPercent: int, toPercent: int) -> return moviesProgress def __addMoviesToTraktCollection( - self, kodiMovies: List[Dict], traktMovies: List[Dict], fromPercent: int, toPercent: int + self, + kodiMovies: List[Dict], + traktMovies: List[Dict], + fromPercent: int, + toPercent: int, ) -> None: if ( kodiUtilities.getSettingAsBool("add_movies_to_trakt") @@ -228,7 +235,11 @@ def __addMoviesToTraktCollection( ) def __deleteMoviesFromTraktCollection( - self, traktMovies: List[Dict], kodiMovies: List[Dict], fromPercent: int, toPercent: int + self, + traktMovies: List[Dict], + kodiMovies: List[Dict], + fromPercent: int, + toPercent: int, ) -> None: if ( kodiUtilities.getSettingAsBool("clean_trakt_movies") @@ -283,7 +294,11 @@ def __deleteMoviesFromTraktCollection( ) def __addMoviesToTraktWatched( - self, kodiMovies: List[Dict], traktMovies: List[Dict], fromPercent: int, toPercent: int + self, + kodiMovies: List[Dict], + traktMovies: List[Dict], + fromPercent: int, + toPercent: int, ) -> None: if ( kodiUtilities.getSettingAsBool("trakt_movie_playcount") @@ -330,12 +345,13 @@ def __addMoviesToTraktWatched( if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32093) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32093) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) params = {"movies": chunk} # logger.debug("moviechunk: %s" % params) @@ -352,7 +368,13 @@ def __addMoviesToTraktWatched( line2=kodiUtilities.getString(32087) % len(traktMoviesToUpdate), ) - def __addMoviesToKodiWatched(self, traktMovies: List[Dict], kodiMovies: List[Dict], fromPercent: int, toPercent: int) -> None: + def __addMoviesToKodiWatched( + self, + traktMovies: List[Dict], + kodiMovies: List[Dict], + fromPercent: int, + toPercent: int, + ) -> None: if ( kodiUtilities.getSettingAsBool("kodi_movie_playcount") and not self.sync.IsCanceled() @@ -413,12 +435,13 @@ def __addMoviesToKodiWatched(self, traktMovies: List[Dict], kodiMovies: List[Dic if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32089) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32089) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) kodiUtilities.kodiJsonRequest(chunk) @@ -427,7 +450,13 @@ def __addMoviesToKodiWatched(self, traktMovies: List[Dict], kodiMovies: List[Dic line2=kodiUtilities.getString(32090) % len(kodiMoviesToUpdate), ) - def __addMovieProgressToKodi(self, traktMovies: Dict, kodiMovies: List[Dict], fromPercent: int, toPercent: int) -> None: + def __addMovieProgressToKodi( + self, + traktMovies: Dict, + kodiMovies: List[Dict], + fromPercent: int, + toPercent: int, + ) -> None: if ( kodiUtilities.getSettingAsBool("trakt_movie_playback") and traktMovies @@ -463,13 +492,12 @@ def __addMovieProgressToKodi(self, traktMovies: Dict, kodiMovies: List[Dict], fr # If library item doesn't have a runtime set get it from # Trakt to avoid later using 0 in runtime * progress_pct. for movie in kodiMoviesToUpdate: - if not movie["runtime"]: - movie["runtime"] = ( - self.sync.traktapi.getMovieSummary( - movie["ids"]["trakt"], extended="full" - ).runtime - * 60 + if not movie.get("runtime"): + summary = self.sync.traktapi.getMovieSummary( + movie["ids"]["trakt"], extended="full" ) + runtime = summary.runtime if summary and summary.runtime else 0 + movie["runtime"] = runtime * 60 # need to calculate the progress in int from progress in percent from Trakt # split movie list into chunks of 50 chunksize = 50 @@ -500,12 +528,13 @@ def __addMovieProgressToKodi(self, traktMovies: Dict, kodiMovies: List[Dict], fr if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32127) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32127) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) kodiUtilities.kodiJsonRequest(chunk) self.sync.UpdateProgress( @@ -513,7 +542,13 @@ def __addMovieProgressToKodi(self, traktMovies: Dict, kodiMovies: List[Dict], fr line2=kodiUtilities.getString(32128) % len(kodiMoviesToUpdate), ) - def __syncMovieRatings(self, traktMovies: List[Dict], kodiMovies: List[Dict], fromPercent: int, toPercent: int) -> None: + def __syncMovieRatings( + self, + traktMovies: List[Dict], + kodiMovies: List[Dict], + fromPercent: int, + toPercent: int, + ) -> None: if ( kodiUtilities.getSettingAsBool("trakt_sync_ratings") and traktMovies @@ -595,12 +630,13 @@ def __syncMovieRatings(self, traktMovies: List[Dict], kodiMovies: List[Dict], fr if self.sync.IsCanceled(): return i += 1 - y = ((i / x) * (toPercent - fromPercent)) + fromPercent - self.sync.UpdateProgress( - int(y), - line2=kodiUtilities.getString(32171) - % ((i) * chunksize if (i) * chunksize < x else x, x), - ) + if x > 0: + y = ((i / x) * (toPercent - fromPercent)) + fromPercent + self.sync.UpdateProgress( + int(y), + line2=kodiUtilities.getString(32171) + % ((i) * chunksize if (i) * chunksize < x else x, x), + ) kodiUtilities.kodiJsonRequest(chunk) self.sync.UpdateProgress( diff --git a/resources/lib/traktContextMenu.py b/resources/lib/traktContextMenu.py index 792a0b2e..811ec705 100644 --- a/resources/lib/traktContextMenu.py +++ b/resources/lib/traktContextMenu.py @@ -24,7 +24,9 @@ class traktContextMenu(xbmcgui.WindowXMLDialog): buttons: List[str] media_type: str - def __new__(cls, media_type: Optional[str] = None, buttons: Optional[List[str]] = None) -> Any: + def __new__( + cls, media_type: Optional[str] = None, buttons: Optional[List[str]] = None + ) -> Any: return super(traktContextMenu, cls).__new__( cls, "script-trakt-ContextMenu.xml", @@ -78,7 +80,9 @@ def onInit(self) -> None: self.setFocus(actionList) - def newListItem(self, label: str, selected: bool = False, *args: Any, **kwargs: Any) -> xbmcgui.ListItem: + def newListItem( + self, label: str, selected: bool = False, *args: Any, **kwargs: Any + ) -> xbmcgui.ListItem: item = xbmcgui.ListItem(label) item.select(selected) for key in kwargs: diff --git a/resources/lib/traktapi.py b/resources/lib/traktapi.py index 9d8ae3c8..46c85368 100644 --- a/resources/lib/traktapi.py +++ b/resources/lib/traktapi.py @@ -169,7 +169,9 @@ def updateUser(self) -> None: else: setSetting("user", "") - def scrobbleEpisode(self, show: Dict, episode: Dict, percent: float, status: str) -> Optional[Dict]: + def scrobbleEpisode( + self, show: Dict, episode: Dict, percent: float, status: str + ) -> Optional[Dict]: result = None with Trakt.configuration.oauth.from_response(self.authorization): @@ -278,14 +280,18 @@ def getShowRatingForUser(self, showId: str, idType: str = "tvdb") -> Dict: Trakt["sync/ratings"].shows(store=ratings) return findShowMatchInList(showId, ratings, idType) - def getSeasonRatingForUser(self, showId: str, season: int, idType: str = "tvdb") -> Dict: + def getSeasonRatingForUser( + self, showId: str, season: int, idType: str = "tvdb" + ) -> Dict: ratings = {} with Trakt.configuration.oauth.from_response(self.authorization): with Trakt.configuration.http(retry=True): Trakt["sync/ratings"].seasons(store=ratings) return findSeasonMatchInList(showId, season, ratings, idType) - def getEpisodeRatingForUser(self, showId: str, season: int, episode: int, idType: str = "tvdb") -> Dict: + def getEpisodeRatingForUser( + self, showId: str, season: int, episode: int, idType: str = "tvdb" + ) -> Dict: ratings = {} with Trakt.configuration.oauth.from_response(self.authorization): with Trakt.configuration.http(retry=True): @@ -353,7 +359,9 @@ def getShowWithAllEpisodesList(self, showId: str) -> List: with Trakt.configuration.http(retry=True, timeout=90): return Trakt["shows"].seasons(showId, extended="episodes") - def getEpisodeSummary(self, showId: str, season: int, episode: int, extended: Optional[str] = None) -> Any: + def getEpisodeSummary( + self, showId: str, season: int, episode: int, extended: Optional[str] = None + ) -> Any: with Trakt.configuration.http(retry=True): return Trakt["shows"].episode(showId, season, episode, extended=extended) @@ -364,7 +372,9 @@ def getIdLookup(self, id: str, id_type: str) -> Optional[List]: result = [result] return result - def getTextQuery(self, query: str, type: str, year: Optional[int]) -> Optional[List]: + def getTextQuery( + self, query: str, type: str, year: Optional[int] + ) -> Optional[List]: with Trakt.configuration.http(retry=True, timeout=90): result = Trakt["search"].query(query, type, year) if result and not isinstance(result, list): diff --git a/resources/lib/utilities.py b/resources/lib/utilities.py index 72183480..3abf8aae 100644 --- a/resources/lib/utilities.py +++ b/resources/lib/utilities.py @@ -63,7 +63,9 @@ def getFormattedItemName(type: str, info: Dict) -> str: return s -def __findInList(list_data: List, case_sensitive: bool = True, **kwargs) -> Optional[Dict]: +def __findInList( + list_data: List, case_sensitive: bool = True, **kwargs +) -> Optional[Dict]: for item in list_data: i = 0 for key in kwargs: @@ -88,7 +90,9 @@ def __findInList(list_data: List, case_sensitive: bool = True, **kwargs) -> Opti return None -def findMediaObject(mediaObjectToMatch: Dict, listToSearch: List, matchByTitleAndYear: bool) -> Optional[Dict]: +def findMediaObject( + mediaObjectToMatch: Dict, listToSearch: List, matchByTitleAndYear: bool +) -> Optional[Dict]: result = None if ( result is None @@ -192,7 +196,9 @@ def findShowMatchInList(id: str, listToMatch: Dict, idType: str) -> Dict: ) -def findSeasonMatchInList(id: str, seasonNumber: int, listToMatch: Dict, idType: str) -> Dict: +def findSeasonMatchInList( + id: str, seasonNumber: int, listToMatch: Dict, idType: str +) -> Dict: show = findShowMatchInList(id, listToMatch, idType) logger.debug("findSeasonMatchInList %s" % show) if "seasons" in show: @@ -203,7 +209,9 @@ def findSeasonMatchInList(id: str, seasonNumber: int, listToMatch: Dict, idType: return {} -def findEpisodeMatchInList(id: str, seasonNumber: int, episodeNumber: int, list_data: Dict, idType: str) -> Dict: +def findEpisodeMatchInList( + id: str, seasonNumber: int, episodeNumber: int, list_data: Dict, idType: str +) -> Dict: season = findSeasonMatchInList(id, seasonNumber, list_data, idType) if season: for episode in season["episodes"]: @@ -296,7 +304,9 @@ def best_id(ids: Dict, type: str) -> Tuple[str, str]: return ids["slug"], "slug" -def checkExcludePath(excludePath: str, excludePathEnabled: bool, fullpath: str, x: int) -> bool: +def checkExcludePath( + excludePath: str, excludePathEnabled: bool, fullpath: str, x: int +) -> bool: if excludePath != "" and excludePathEnabled and fullpath.startswith(excludePath): logger.debug( "checkExclusion(): Video is from location, which is currently set as excluded path %i." @@ -396,7 +406,11 @@ def compareMovies( def compareShows( - shows_col1: Dict, shows_col2: Dict, matchByTitleAndYear: bool, rating: bool = False, restrict: bool = False + shows_col1: Dict, + shows_col2: Dict, + matchByTitleAndYear: bool, + rating: bool = False, + restrict: bool = False, ) -> Dict: shows = [] # logger.debug("shows_col1 %s" % shows_col1) @@ -686,7 +700,9 @@ def checkIfNewVersion(old: str, new: str) -> bool: return False -def _to_sec(timedelta_string: str, factors: Tuple[int, ...] = (1, 60, 3600, 86400)) -> float: +def _to_sec( + timedelta_string: str, factors: Tuple[int, ...] = (1, 60, 3600, 86400) +) -> float: """[[[days:]hours:]minutes:]seconds -> seconds""" return sum( x * y diff --git a/ruff.toml b/ruff.toml index d8f26b51..4df73d6a 100644 --- a/ruff.toml +++ b/ruff.toml @@ -1,2 +1,2 @@ -# Ignore line length for now -ignore = ["E501"] \ No newline at end of file +[lint] +ignore = ["E501"] diff --git a/scripts/inject_keys.py b/scripts/inject_keys.py index 59cd874b..2f4e786d 100644 --- a/scripts/inject_keys.py +++ b/scripts/inject_keys.py @@ -6,6 +6,7 @@ from resources.lib.obfuscation import deobfuscate, obfuscate + def main(): client_id = os.environ.get("TRAKT_CLIENT_ID") client_secret = os.environ.get("TRAKT_CLIENT_SECRET") @@ -43,5 +44,6 @@ def main(): print(f"Successfully injected obfuscated keys into {target_file}") + if __name__ == "__main__": main() diff --git a/tests/test_obfuscation.py b/tests/test_obfuscation.py index a657b7e7..cf96268b 100644 --- a/tests/test_obfuscation.py +++ b/tests/test_obfuscation.py @@ -1,20 +1,25 @@ # -*- coding: utf-8 -*- from resources.lib import obfuscation + def test_obfuscate(): assert obfuscation.obfuscate("test") == [54, 39, 49, 54] + def test_deobfuscate(): assert obfuscation.deobfuscate([54, 39, 49, 54]) == "test" + def test_obfuscate_empty(): assert obfuscation.obfuscate("") == [] + def test_deobfuscate_empty(): assert obfuscation.deobfuscate("") == "" assert obfuscation.deobfuscate(None) == "" assert obfuscation.deobfuscate("not a list") == "" + def test_roundtrip(): original = "Hello, World!" assert obfuscation.deobfuscate(obfuscation.obfuscate(original)) == original diff --git a/tests/test_rating.py b/tests/test_rating.py new file mode 100644 index 00000000..136d2592 --- /dev/null +++ b/tests/test_rating.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +import mock +import sys + +# Mock Kodi modules before importing project modules +xbmc_mock = mock.Mock() +sys.modules["xbmc"] = xbmc_mock +sys.modules["xbmcgui"] = mock.Mock() +sys.modules["xbmcaddon"] = mock.Mock() + +from resources.lib import rating # noqa: E402 + + +def test_rateMedia_handles_none_items(): + # Verify that None items in itemsToRate are skipped without raising TypeError + itemsToRate = [None, {"title": "Test", "user": {"ratings": {"rating": 5}}}] + + with ( + mock.patch("resources.lib.utilities.isValidMediaType", return_value=True), + mock.patch("resources.lib.utilities.getFormattedItemName", return_value="Test"), + mock.patch("resources.lib.kodiUtilities.getSettingAsBool", return_value=True), + mock.patch("resources.lib.rating.RatingDialog") as mock_dialog, + ): + # This should not raise TypeError + rating.rateMedia("movie", itemsToRate) + assert mock_dialog.called diff --git a/tests/test_scrobbler.py b/tests/test_scrobbler.py new file mode 100644 index 00000000..0e2e15fd --- /dev/null +++ b/tests/test_scrobbler.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- +import mock +import sys + +# Mock Kodi modules before importing project modules +xbmc_mock = mock.Mock() +sys.modules["xbmc"] = xbmc_mock +sys.modules["xbmcgui"] = mock.Mock() +sys.modules["xbmcaddon"] = mock.Mock() + +from resources.lib import scrobbler # noqa: E402 + + +def test_playbackEnded_skips_none_curVideoInfo(): + api_mock = mock.Mock() + s = scrobbler.Scrobbler(api_mock) + s.curVideoInfo = None + s.curVideo = {"type": "movie"} + s.isPlaying = True + s.watchedTime = 100 + s.videoDuration = 1000 + + xbmc_mock.Player().isPlayingVideo.return_value = False + xbmc_mock.PlayList.return_value.getposition.return_value = 0 + xbmc_mock.getCondVisibility.return_value = False + + with mock.patch("resources.lib.scrobbler.ratingCheck") as mock_ratingCheck: + s.playbackEnded() + # Verify that ratingCheck is not called because curVideoInfo was None and not appended + assert not mock_ratingCheck.called + + +def test_scrobble_handles_zero_duration(): + api_mock = mock.Mock() + s = scrobbler.Scrobbler(api_mock) + s.curVideo = {"type": "episode", "multi_episode_count": 2} + s.videoDuration = 0 + s.curVideoInfo = {"title": "Test"} + s.isMultiPartEpisode = True + s.watchedTime = 10 + + with mock.patch("resources.lib.kodiUtilities.getSettingAsBool", return_value=False): + # This should not raise ZeroDivisionError + s._Scrobbler__scrobble("start") diff --git a/tests/test_sync.py b/tests/test_sync.py new file mode 100644 index 00000000..05f72922 --- /dev/null +++ b/tests/test_sync.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- +import sys +from mock import MagicMock + +# Mock Kodi modules before imports +sys.modules["xbmc"] = MagicMock() +sys.modules["xbmcaddon"] = MagicMock() +sys.modules["xbmcgui"] = MagicMock() +sys.modules["xbmcvfs"] = MagicMock() + +from resources.lib.syncEpisodes import SyncEpisodes # noqa: E402 +from resources.lib.syncMovies import SyncMovies # noqa: E402 + + +def test_get_show_as_string_logic(): + sync_mock = MagicMock() + progress_mock = MagicMock() + # Mocking __init__ to avoid full execution + SyncEpisodes.__init__ = lambda self, sync, progress: None + se = SyncEpisodes(sync_mock, progress_mock) + + show = { + "title": "Test Show", + "ids": {"tvdb": "123"}, + "seasons": [{"number": 1, "episodes": [{"number": 1}, {"number": 2}]}], + } + + # Test short=True + res_short = se._SyncEpisodes__getShowAsString(show, short=True) + assert "S01E01, S01E02" in res_short + assert "[tvdb: 123]" in res_short + + # Test short=False - this previously crashed or had wrong logic + res_long = se._SyncEpisodes__getShowAsString(show, short=False) + assert "Season: 1, Episodes: 1, 2" in res_long + + +def test_sync_movies_runtime_none(): + sync_mock = MagicMock() + # Mocking __init__ to avoid full execution + SyncMovies.__init__ = lambda self, sync, progress: None + SyncMovies(sync_mock, MagicMock()) + + movie = {"ids": {"trakt": 1}, "runtime": None} + + sync_mock.traktapi.getMovieSummary.return_value = MagicMock(runtime=None) + + # Should not crash even if Trakt returns None for runtime + # We just want to ensure our new logic handles summary=None or summary.runtime=None + summary = sync_mock.traktapi.getMovieSummary(1) + runtime = summary.runtime if summary and summary.runtime else 0 + movie["runtime"] = runtime * 60 + assert movie["runtime"] == 0