diff --git a/README.md b/README.md index 2a7777cb..34df89d0 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ Automatically scrobble all TV episodes and movies you are watching to Trakt.tv! - Automatically scrobble what you're watching - [Mobile apps](http://trakt.tv/downloads) for iPhone, iPad, Android, and Windows Phone -- Share what you're watching (in real time) and rating to facebook and twitter +- Share what you're watching (in real time) and rating to Facebook and Twitter - Personalized calendar so you never miss a TV show -- Follow your friends and people you're interesed in +- Follow your friends and people you're interested in - Use watchlists so you don't forget what to watch - Track your media collections and impress your friends - Create custom lists around any topics you choose @@ -51,7 +51,7 @@ Remote streaming content will scrobble assuming the metadata is correctly set in ### Installation -If your not a developer, you should only install this from the official Kodi repo via Kodi itself. If you are a dev, here is how you install the dev version: +If you're not a developer, you should only install this from the official Kodi repo via Kodi itself. If you are a dev, here is how you install the dev version: 1. Download the zip ([download it here](../../zipball/main)) 2. Install script.trakt by zip. Go to _Settings_ > _Add-ons_ > _Install from zip file_ > Choose the just downloaded zip diff --git a/resources/language/resource.language.en_GB/strings.po b/resources/language/resource.language.en_GB/strings.po index df3d6b30..8352be59 100644 --- a/resources/language/resource.language.en_GB/strings.po +++ b/resources/language/resource.language.en_GB/strings.po @@ -774,6 +774,14 @@ msgctxt "#32191" msgid "Password" msgstr "" +msgctxt "#32192" +msgid "Remove watched status from Kodi when rewatching" +msgstr "" + +msgctxt "#32193" +msgid "Include specials" +msgstr "" + msgctxt "#42191" msgid "Optional password needed to authenticate with the proxy." msgstr "" diff --git a/resources/lib/kodiUtilities.py b/resources/lib/kodiUtilities.py index 6f55806b..3900539f 100644 --- a/resources/lib/kodiUtilities.py +++ b/resources/lib/kodiUtilities.py @@ -185,9 +185,9 @@ def kodiRpcToTraktMediaObject(type, data, mode="collected"): data["ids"] = utilities.guessBestTraktId(id, type)[0] if "lastplayed" in data: - episode["watched_at"] = utilities.convertDateTimeToUTC(data["lastplayed"]) + episode["watched_at"] = utilities.to_iso8601_datetime(utilities.from_datetime(data["lastplayed"])) if "dateadded" in data: - episode["collected_at"] = utilities.convertDateTimeToUTC(data["dateadded"]) + episode["collected_at"] = utilities.to_iso8601_datetime(utilities.from_datetime(data["dateadded"])) if "runtime" in data: episode["runtime"] = data["runtime"] episode["rating"] = ( @@ -204,9 +204,9 @@ def kodiRpcToTraktMediaObject(type, data, mode="collected"): if checkExclusion(data.pop("file")): return if "lastplayed" in data: - data["watched_at"] = utilities.convertDateTimeToUTC(data.pop("lastplayed")) + data["watched_at"] = utilities.to_iso8601_datetime(utilities.from_datetime(data.pop("lastplayed"))) if "dateadded" in data: - data["collected_at"] = utilities.convertDateTimeToUTC(data.pop("dateadded")) + data["collected_at"] = utilities.to_iso8601_datetime(utilities.from_datetime(data.pop("dateadded"))) if data["playcount"] is None: data["plays"] = 0 else: diff --git a/resources/lib/syncEpisodes.py b/resources/lib/syncEpisodes.py index 09be8f42..f87c4bb3 100644 --- a/resources/lib/syncEpisodes.py +++ b/resources/lib/syncEpisodes.py @@ -281,9 +281,11 @@ def __traktLoadShows(self): ) # will keep the data in python structures - just like the KODI response - show = show.to_dict() - - showsWatched["shows"].append(show) + show_dict = show.to_dict() + # reset_at is not included when calling `.to_dict()` + # but needed for watched shows to know whether to reset the watched state + show_dict["reset_at"] = utilities.to_iso8601_datetime(show.reset_at) if hasattr(show, "reset_at") else None + showsWatched["shows"].append(show_dict) i = 0 x = float(len(traktShowsRated)) @@ -580,6 +582,12 @@ def __addEpisodesToKodiWatched( updateKodiTraktShows = copy.deepcopy(traktShows) updateKodiKodiShows = copy.deepcopy(kodiShows) + if kodiUtilities.getSettingAsBool("kodi_episode_reset"): + utilities.updateTraktLastWatchedBasedOnResetAt( + updateKodiTraktShows, + kodiUtilities.getSettingAsBool("kodi_episode_reset_specials") + ) + kodiShowsUpdate = utilities.compareEpisodes( updateKodiTraktShows, updateKodiKodiShows, @@ -617,8 +625,8 @@ def __addEpisodesToKodiWatched( { "episodeid": episode["ids"]["episodeid"], "playcount": episode["plays"], - "lastplayed": utilities.convertUtcToDateTime( - episode["last_watched_at"] + "lastplayed": utilities.to_datetime( + utilities.from_iso8601_datetime(episode["last_watched_at"]) ), } ) diff --git a/resources/lib/syncMovies.py b/resources/lib/syncMovies.py index 56af70ca..43a5c431 100644 --- a/resources/lib/syncMovies.py +++ b/resources/lib/syncMovies.py @@ -394,8 +394,8 @@ def __addMoviesToKodiWatched(self, traktMovies, kodiMovies, fromPercent, toPerce "params": { "movieid": kodiMoviesToUpdate[i]["movieid"], "playcount": kodiMoviesToUpdate[i]["plays"], - "lastplayed": utilities.convertUtcToDateTime( - kodiMoviesToUpdate[i]["last_watched_at"] + "lastplayed": utilities.to_datetime( + utilities.from_iso8601_datetime(kodiMoviesToUpdate[i]["last_watched_at"]) ), }, "id": i, diff --git a/resources/lib/utilities.py b/resources/lib/utilities.py index f89ee570..bed54087 100644 --- a/resources/lib/utilities.py +++ b/resources/lib/utilities.py @@ -10,6 +10,7 @@ import dateutil.parser from datetime import datetime from dateutil.tz import tzutc, tzlocal +import arrow # make strptime call prior to doing anything, to try and prevent threading # errors @@ -213,42 +214,49 @@ def findEpisodeMatchInList(id, seasonNumber, episodeNumber, list, idType): return {} -def convertDateTimeToUTC(toConvert): - if toConvert: - dateFormat = "%Y-%m-%d %H:%M:%S" - try: - naive = datetime.strptime(toConvert, dateFormat) - except TypeError: - naive = datetime(*(time.strptime(toConvert, dateFormat)[0:6])) - - try: - local = naive.replace(tzinfo=tzlocal()) - utc = local.astimezone(tzutc()) - except ValueError: - logger.debug( - "convertDateTimeToUTC() ValueError: movie/show was collected/watched outside of the unix timespan. Fallback to datetime utcnow" - ) - utc = datetime.utcnow() - return str(utc) - else: - return toConvert - - -def convertUtcToDateTime(toConvert): - if toConvert: - dateFormat = "%Y-%m-%d %H:%M:%S" - try: - naive = dateutil.parser.parse(toConvert) - utc = naive.replace(tzinfo=tzutc()) - local = utc.astimezone(tzlocal()) - except ValueError: - logger.debug( - "convertUtcToDateTime() ValueError: movie/show was collected/watched outside of the unix timespan. Fallback to datetime now" - ) - local = datetime.now() - return local.strftime(dateFormat) - else: - return toConvert +def to_datetime(value): + if not value: + return None + + return value.strftime('%Y-%m-%d %H:%M:%S') + + +def from_datetime(value): + if not value: + return None + + if arrow is None: + raise Exception('"arrow" module is not available') + + # Parse datetime + dt = arrow.get(value, 'YYYY-MM-DD HH:mm:ss') + + # Return datetime object + return dt.datetime + + +def to_iso8601_datetime(value): + if not value: + return None + + return value.strftime('%Y-%m-%dT%H:%M:%S') + '.000-00:00' + + +def from_iso8601_datetime(value): + if not value: + return None + + if arrow is None: + raise Exception('"arrow" module is not available') + + # Parse ISO8601 datetime + dt = arrow.get(value, 'YYYY-MM-DDTHH:mm:ss.SZZ') + + # Convert to UTC + dt = dt.to('UTC') + + # Return datetime object + return dt.datetime def createError(ex): @@ -484,6 +492,14 @@ def compareEpisodes( if season in season_col2: b = season_col2[season] diff = list(set(a).difference(set(b))) + # only for removing plays from kodi + if watched and restrict: + for key in a: + # update lastplayed in KODI if they don't match trakt + if not key in b or a[key]["plays"] != b[key]["plays"]: + diff.append(key) + # make unique + diff = list(set(diff)) if playback: t = list(set(a).intersection(set(b))) if len(t) > 0: @@ -700,3 +716,17 @@ def _fuzzyMatch(string1, string2, match_percent=55.0): return ( difflib.SequenceMatcher(None, string1, string2).ratio() * 100 ) >= match_percent + + +def updateTraktLastWatchedBasedOnResetAt(trakt_shows, update_specials=False): + for show in trakt_shows["shows"]: + if show["reset_at"]: + reset_at = from_iso8601_datetime(show["reset_at"]) + for season in show["seasons"]: + if not update_specials and season["number"] == 0: + continue + for episode in season["episodes"]: + last_watched = from_iso8601_datetime(episode["last_watched_at"]) + if last_watched and last_watched < reset_at: + episode["last_watched_at"] = None + episode["plays"] = 0 diff --git a/resources/settings.xml b/resources/settings.xml index 60c10045..76c763f5 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -580,6 +580,23 @@ true 1 + + + false + 1 + + true + + + + + false + 1 + + true + true + + false