Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions resources/language/resource.language.en_GB/strings.po
Original file line number Diff line number Diff line change
Expand Up @@ -777,3 +777,11 @@ msgstr ""
msgctxt "#42191"
msgid "Optional password needed to authenticate with the proxy."
msgstr ""

msgctxt "#32192"
msgid "Remove watched status from Kodi when rewatching"
msgstr ""

msgctxt "#32193"
msgid "Include specials"
msgstr ""
8 changes: 4 additions & 4 deletions resources/lib/kodiUtilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ def kodiRpcToTraktMediaObject(type: str, data: Dict, mode: str = "collected") ->
data["ids"] = utilities.guessBestTraktId(id, type)[0]

if "lastplayed" in data:
episode["watched_at"] = utilities.convertDateTimeToUTC(data["lastplayed"])
episode["watched_at"] = utilities.toIso8601DateTime(utilities.fromDateTime(data["lastplayed"]))
if "dateadded" in data:
episode["collected_at"] = utilities.convertDateTimeToUTC(data["dateadded"])
episode["collected_at"] = utilities.toIso8601DateTime(utilities.fromDateTime(data["dateadded"]))
if "runtime" in data:
episode["runtime"] = data["runtime"]
episode["rating"] = (
Expand All @@ -205,9 +205,9 @@ def kodiRpcToTraktMediaObject(type: str, data: Dict, mode: str = "collected") ->
if checkExclusion(data.pop("file")):
return
if "lastplayed" in data:
data["watched_at"] = utilities.convertDateTimeToUTC(data.pop("lastplayed"))
data["watched_at"] = utilities.toIso8601DateTime(utilities.fromDateTime(data.pop("lastplayed")))
if "dateadded" in data:
data["collected_at"] = utilities.convertDateTimeToUTC(data.pop("dateadded"))
data["collected_at"] = utilities.toIso8601DateTime(utilities.fromDateTime(data.pop("dateadded")))
if data["playcount"] is None:
data["plays"] = 0
else:
Expand Down
18 changes: 13 additions & 5 deletions resources/lib/syncEpisodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,11 @@ def __traktLoadShows(self) -> Tuple[Union[Dict, bool], Union[Dict, bool], Union[
)

# 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.toIso8601DateTime(show.reset_at) if hasattr(show, "reset_at") else None
showsWatched["shows"].append(show_dict)

i = 0
x = float(len(traktShowsRated))
Expand Down Expand Up @@ -583,6 +585,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,
Expand Down Expand Up @@ -620,8 +628,8 @@ def __addEpisodesToKodiWatched(
{
"episodeid": episode["ids"]["episodeid"],
"playcount": episode["plays"],
"lastplayed": utilities.convertUtcToDateTime(
episode["last_watched_at"]
"lastplayed": utilities.toDateTime(
utilities.fromIso8601DateTime(episode["last_watched_at"])
),
}
)
Expand Down
4 changes: 2 additions & 2 deletions resources/lib/syncMovies.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,8 +397,8 @@ def __addMoviesToKodiWatched(self, traktMovies: List[Dict], kodiMovies: List[Dic
"params": {
"movieid": kodiMoviesToUpdate[i]["movieid"],
"playcount": kodiMoviesToUpdate[i]["plays"],
"lastplayed": utilities.convertUtcToDateTime(
kodiMoviesToUpdate[i]["last_watched_at"]
"lastplayed": utilities.toDateTime(
utilities.fromIso8601DateTime(kodiMoviesToUpdate[i]["last_watched_at"])
),
},
"id": i,
Expand Down
102 changes: 66 additions & 36 deletions resources/lib/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -213,42 +214,49 @@ def findEpisodeMatchInList(id: str, seasonNumber: int, episodeNumber: int, list_
return {}


def convertDateTimeToUTC(toConvert: Optional[str]) -> Optional[str]:
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: Optional[str]) -> Optional[str]:
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 toDateTime(value: Optional[datetime]) -> Optional[str]:
if not value:
return None

return value.strftime('%Y-%m-%d %H:%M:%S')


def fromDateTime(value: Optional[str]) -> Optional[datetime]:
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 toIso8601DateTime(value: Optional[datetime]) -> Optional[str]:
if not value:
return None

return value.strftime('%Y-%m-%dT%H:%M:%S') + '.000-00:00'


def fromIso8601DateTime(value: Optional[str]) -> Optional[datetime]:
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: Exception) -> str:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -700,3 +716,17 @@ def _fuzzyMatch(string1: str, string2: str, match_percent: float = 55.0) -> bool
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 = fromIso8601DateTime(show["reset_at"])
for season in show["seasons"]:
if not update_specials and season["number"] == 0:
continue
for episode in season["episodes"]:
last_watched = fromIso8601DateTime(episode["last_watched_at"])
if last_watched and last_watched < reset_at:
episode["last_watched_at"] = None
episode["plays"] = 0
17 changes: 17 additions & 0 deletions resources/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,23 @@
<default>true</default>
<level>1</level>
</setting>
<setting id="kodi_episode_reset" type="boolean" label="32192" help="" parent="kodi_episode_playcount">
<control type="toggle" />
<default>false</default>
<level>1</level>
<dependencies>
<dependency type="visible" setting="kodi_episode_playcount">true</dependency>
</dependencies>
</setting>
<setting id="kodi_episode_reset_specials" type="boolean" label="32193" help="" parent="kodi_episode_reset">
<control type="toggle" />
<default>false</default>
<level>1</level>
<dependencies>
<dependency type="visible" setting="kodi_episode_playcount">true</dependency>
<dependency type="enable" setting="kodi_episode_reset">true</dependency>
</dependencies>
</setting>
<setting id="trakt_episode_playback" type="boolean" label="32118" help="">
<control type="toggle" />
<default>false</default>
Expand Down