timestamp and unknown episodes
This commit is contained in:
parent
7a9e4babeb
commit
73be70bd4c
@ -3,6 +3,13 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
utc_timestamp = 1699546201
|
||||||
|
dt = from_timestamp(utc_timestamp)
|
||||||
|
assert dt == datetime(2023, 11, 9, 16, 10, 1, tzinfo=timezone.utc)
|
||||||
|
assert to_timestamp(dt) == utc_timestamp
|
||||||
|
|
||||||
|
|
||||||
def now() -> datetime:
|
def now() -> datetime:
|
||||||
return datetime.now(timezone.utc)
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
@ -16,7 +23,7 @@ def now_timestamp() -> int:
|
|||||||
|
|
||||||
|
|
||||||
def to_timestamp(dt: datetime) -> int:
|
def to_timestamp(dt: datetime) -> int:
|
||||||
return round(datetime.timestamp(dt)) * 1000
|
return round(datetime.timestamp(dt))
|
||||||
|
|
||||||
|
|
||||||
def from_timestamp(timestamp: int) -> datetime:
|
def from_timestamp(timestamp: int) -> datetime:
|
||||||
@ -34,3 +41,7 @@ def from_str(dt_str: str) -> datetime:
|
|||||||
|
|
||||||
def fmt() -> str:
|
def fmt() -> str:
|
||||||
return '%Y-%m-%dT%H:%M:%S'
|
return '%Y-%m-%dT%H:%M:%S'
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
|
@ -56,6 +56,13 @@ def cache(file_url: str, cache_dir: Path) -> Path:
|
|||||||
"""
|
"""
|
||||||
file = hashed_location(file_url, cache_dir)
|
file = hashed_location(file_url, cache_dir)
|
||||||
|
|
||||||
|
# Replace plain HTTP with HTTPS
|
||||||
|
http = 'http://'
|
||||||
|
https = 'https://'
|
||||||
|
if file_url.startswith(http):
|
||||||
|
file_url = https + file_url[len(http):]
|
||||||
|
assert file_url.startswith(https), file_url
|
||||||
|
|
||||||
# If the file is missing locally, it is downloaded.
|
# If the file is missing locally, it is downloaded.
|
||||||
if not file.is_file():
|
if not file.is_file():
|
||||||
Log.info(f'Downloading {file_url} ...')
|
Log.info(f'Downloading {file_url} ...')
|
||||||
|
@ -106,6 +106,9 @@ class EpisodeAction:
|
|||||||
def position(self, value: int):
|
def position(self, value: int):
|
||||||
self._position = value
|
self._position = value
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return json.dumps(self.to_json())
|
||||||
|
|
||||||
|
|
||||||
class NextcloudApi:
|
class NextcloudApi:
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -156,22 +159,21 @@ class NextcloudApi:
|
|||||||
return content['add']
|
return content['add']
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fetch_play_actions(cls, server: str, login_name: str, app_password: str, user_id: str):
|
def fetch_play_actions(cls, server: str, login_name: str, app_password: str, user_id: str) -> list[EpisodeAction]:
|
||||||
"""
|
"""
|
||||||
:return: Return new episode actions since last function call where EpisodeActionType is PLAY.
|
:return: Return new episode actions since last function call where EpisodeActionType is PLAY.
|
||||||
"""
|
"""
|
||||||
timestamp_dict = read_timestamp()
|
timestamp_dict = read_timestamp()
|
||||||
timestamp = timestamp_dict.get(user_id, 0)
|
timestamp = datetime_util.from_str(timestamp_dict.get(user_id, '1970-01-01T00:00:00'))
|
||||||
|
|
||||||
episode_actions = NextcloudApi.fetch_episode_actions(
|
new_timestamp, episode_actions = NextcloudApi.fetch_episode_actions(
|
||||||
server=server,
|
server=server,
|
||||||
login_name=login_name,
|
login_name=login_name,
|
||||||
app_password=app_password,
|
app_password=app_password,
|
||||||
types=[EpisodeActionType.PLAY],
|
types=[EpisodeActionType.PLAY],
|
||||||
since=timestamp)
|
since=timestamp)
|
||||||
|
|
||||||
timestamp = datetime_util.now_timestamp()
|
timestamp_dict[user_id] = datetime_util.to_str(new_timestamp)
|
||||||
timestamp_dict[user_id] = timestamp
|
|
||||||
write_timestamp(timestamp_dict)
|
write_timestamp(timestamp_dict)
|
||||||
|
|
||||||
return episode_actions
|
return episode_actions
|
||||||
@ -182,25 +184,26 @@ class NextcloudApi:
|
|||||||
login_name: str,
|
login_name: str,
|
||||||
app_password: str,
|
app_password: str,
|
||||||
types: list[EpisodeActionType] = None,
|
types: list[EpisodeActionType] = None,
|
||||||
since: int = 0) -> list[EpisodeAction]:
|
since: datetime = datetime_util.from_timestamp(0)) \
|
||||||
|
-> tuple[datetime, list[EpisodeAction]]:
|
||||||
"""
|
"""
|
||||||
https://github.com/thrillfall/nextcloud-gpodder/blob/main/README.md#episode-action
|
https://github.com/thrillfall/nextcloud-gpodder/blob/main/README.md#episode-action
|
||||||
"""
|
"""
|
||||||
if types is None:
|
if types is None:
|
||||||
types = [ea_type for ea_type in EpisodeActionType]
|
types = [ea_type for ea_type in EpisodeActionType]
|
||||||
|
|
||||||
url = f'{server}/apps/gpoddersync/episode_action?since={since}'
|
url = f'{server}/apps/gpoddersync/episode_action?since={datetime_util.to_timestamp(since)}'
|
||||||
Log.info(f'Downloading {url} ...')
|
Log.info(f'Downloading {url} ...')
|
||||||
# auth parameter: https://requests.readthedocs.io/en/latest/user/authentication/#basic-authentication
|
# auth parameter: https://requests.readthedocs.io/en/latest/user/authentication/#basic-authentication
|
||||||
r = requests.get(url, auth=(login_name, app_password))
|
r = requests.get(url, auth=(login_name, app_password))
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
content = json.loads(r.text)
|
content = json.loads(r.text)
|
||||||
|
|
||||||
_timestamp = datetime_util.from_timestamp(content['timestamp'])
|
timestamp = datetime_util.from_timestamp(content['timestamp'])
|
||||||
|
|
||||||
episode_actions = [EpisodeAction.from_json(action) for action in content['actions']]
|
episode_actions = [EpisodeAction.from_json(action) for action in content['actions']]
|
||||||
return [episode_action for episode_action in episode_actions
|
return timestamp, [episode_action for episode_action in episode_actions
|
||||||
if episode_action.action in types]
|
if episode_action.action in types]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def push_episode_actions(cls, server: str, login_name: str, app_password: str,
|
def push_episode_actions(cls, server: str, login_name: str, app_password: str,
|
||||||
|
@ -73,13 +73,25 @@ class User:
|
|||||||
episodes_by_id: dict[str, Episode] = {e.episode_id: e
|
episodes_by_id: dict[str, Episode] = {e.episode_id: e
|
||||||
for p in self._podcasts
|
for p in self._podcasts
|
||||||
for e in p.episodes}
|
for e in p.episodes}
|
||||||
|
|
||||||
|
# fix broken "guid" of podcast "Forklart" which includes heading and tailing newlines
|
||||||
|
for episode_action in episode_actions:
|
||||||
|
episode_action.guid = episode_action.guid.strip()
|
||||||
|
# fix local files on Android phone
|
||||||
|
header = 'antennapod_local:content://com.android'
|
||||||
|
episode_actions = [ea for ea in episode_actions if not ea.podcast.startswith(header)]
|
||||||
|
|
||||||
for episode_action in episode_actions:
|
for episode_action in episode_actions:
|
||||||
if episode_action.episode_id in episodes_by_id:
|
if episode_action.episode_id in episodes_by_id:
|
||||||
episode = episodes_by_id[episode_action.episode_id]
|
episode = episodes_by_id[episode_action.episode_id]
|
||||||
|
print(f'\t{episode.title} {episode.get_position()} -> {episode_action.position}')
|
||||||
activity = PlaybackActivity(self.user_id, episode_action.position, episode_action.timestamp)
|
activity = PlaybackActivity(self.user_id, episode_action.position, episode_action.timestamp)
|
||||||
episode.set_activity(activity)
|
episode.set_activity(activity)
|
||||||
episode.set_duration(episode_action.total)
|
episode.set_duration(episode_action.total)
|
||||||
episode.write()
|
episode.write()
|
||||||
|
else:
|
||||||
|
# TODO
|
||||||
|
Log.error(f'Action refers locally unknown episode: {episode_action}')
|
||||||
except requests.exceptions.ConnectionError as _e:
|
except requests.exceptions.ConnectionError as _e:
|
||||||
Log.error(f'Could not fetch new actions.')
|
Log.error(f'Could not fetch new actions.')
|
||||||
|
|
||||||
@ -266,6 +278,9 @@ class Episode:
|
|||||||
def set_activity(self, new_activity: PlaybackActivity):
|
def set_activity(self, new_activity: PlaybackActivity):
|
||||||
prev_activity = self.get_activity()
|
prev_activity = self.get_activity()
|
||||||
|
|
||||||
|
if new_activity.timestamp == prev_activity.timestamp and new_activity.position == prev_activity.position:
|
||||||
|
# This is the current activity, nothing has changed.
|
||||||
|
return
|
||||||
if new_activity.timestamp <= prev_activity.timestamp:
|
if new_activity.timestamp <= prev_activity.timestamp:
|
||||||
raise ValueError(f'Expected timestamp of new activity to be greater.'
|
raise ValueError(f'Expected timestamp of new activity to be greater.'
|
||||||
f' Previous: {prev_activity.timestamp}.'
|
f' Previous: {prev_activity.timestamp}.'
|
||||||
|
Loading…
Reference in New Issue
Block a user