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