From 6641053bebe3d4458fd3920dfd78eaf373f10a64 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Sun, 8 Sep 2024 20:20:09 +0200 Subject: [PATCH] Added support for Jellyfin --- .../fetchers/jellyfin_watch_history.py | 73 +++++++++++++++++++ personal_data/main.py | 2 +- personal_data/secrets.py | 7 +- personal_data/util.py | 3 +- 4 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 personal_data/fetchers/jellyfin_watch_history.py diff --git a/personal_data/fetchers/jellyfin_watch_history.py b/personal_data/fetchers/jellyfin_watch_history.py new file mode 100644 index 0000000..7b5e4b1 --- /dev/null +++ b/personal_data/fetchers/jellyfin_watch_history.py @@ -0,0 +1,73 @@ +import dataclasses +import datetime +import logging +import re +import bs4 +from typing import Any +from collections.abc import Iterator +from jellyfin_apiclient_python import JellyfinClient + +from ..data import DeduplicateMode, Scraper +from .. import secrets, parse_util, html_util, _version + +logger = logging.getLogger(__name__) + +URL_SITE_ROOT = 'https://steamcommunity.com/' + +URL_GAME_ACHIVEMENTS = URL_SITE_ROOT+'id/{username}/stats/appid/{appid}' + +FORMAT_DATE_HEADER = '%d/%m/%YYYY' + +def iterate_series(client): + result = client.jellyfin.user_items(params = { + 'includeItemTypes': 'Series', + 'parentId': 'a656b907eb3a73532e40e44b968d0225', + 'userId': 'dd95c1085c1b4e83ba8e8853fbc644ab', + }) + yield from result['Items'] + +def iterate_watched_episodes_of_series(client, series_id: str): + result = client.jellyfin.user_items(params = { + 'filters': 'IsPlayed', + 'recursive': True, + 'includeItemTypes': 'Episode', + 'parentId': series_id, + 'userId': 'dd95c1085c1b4e83ba8e8853fbc644ab', + 'fields': 'AirTime', + }) + yield from result['Items'] + +@dataclasses.dataclass(frozen=True) +class JellyfinWatchHistoryScraper(Scraper): + dataset_name = 'show_episodes_watched' + deduplicate_mode = DeduplicateMode.BY_ALL_COLUMNS + + def scrape(self) -> Iterator[dict[str, Any]]: + client = JellyfinClient() + + client.config.app('personal_data', _version.__version__, + 'test_machine', 'unique_id_1') + + client.config.data["auth.ssl"] = False + client.auth.connect_to_address(secrets.JELLYFIN_URL) + client.auth.login(secrets.JELLYFIN_URL, secrets.JELLYFIN_USERNAME, secrets.JELLYFIN_PASSWORD) + + for series_data in iterate_series(client): + series_id = series_data['Id'] + for episode_data in iterate_watched_episodes_of_series(client, series_id): + episode_index = episode_data.get('IndexNumber') + if episode_index is None: + continue + yield { + 'series.name': episode_data['SeriesName'], + 'season.name': episode_data['SeasonName'], + 'episode.index': int(episode_index), + 'episode.name': episode_data['Name'], + 'me.last_played_time': episode_data['UserData']['LastPlayedDate'], + 'episode.duration_seconds': episode_data['RunTimeTicks'] / 10000000, + 'episode.premiere_date': episode_data.get('PremiereDate'), + } + + del episode_data + del series_data, series_id + diff --git a/personal_data/main.py b/personal_data/main.py index 59cdbbc..3cd2110 100644 --- a/personal_data/main.py +++ b/personal_data/main.py @@ -124,7 +124,7 @@ def main( OUTPUT_PATH / f'{scraper.dataset_name}.csv', result_rows, deduplicate_mode=scraper.deduplicate_mode, - deduplicate_ignore_columns=scraper.deduplicate_ignore_columns, + deduplicate_ignore_columns=scraper.deduplicate_ignore_columns(), ) logger.info('Scraper done: %s', scraper.dataset_name) diff --git a/personal_data/secrets.py b/personal_data/secrets.py index 83af2d5..14ef980 100644 --- a/personal_data/secrets.py +++ b/personal_data/secrets.py @@ -23,7 +23,7 @@ KUCOIN_KEY = load_secret('KUCOIN_KEY') KUCOIN_SECRET = load_secret('KUCOIN_SECRET') KUCOIN_PASS = load_secret('KUCOIN_PASS') -# KRAKEN +# Kraken KRAKEN_KEY = load_secret('KRAKEN_KEY') KRAKEN_SECRET = load_secret('KRAKEN_SECRET') @@ -35,3 +35,8 @@ HOME_ASSISTANT_LLAK = load_secret('HOME_ASSISTANT_LLAK') MAILGUN_API_KEY = load_secret('MAILGUN_API_KEY') MAILGUN_DOMAIN = load_secret('MAILGUN_DOMAIN') MAILGUN_RECIPIENT = load_secret('MAILGUN_RECIPIENT') + +# Jellyfin +JELLYFIN_URL = load_secret('JELLYFIN_URL') +JELLYFIN_USERNAME = load_secret('JELLYFIN_USERNAME') +JELLYFIN_PASSWORD = load_secret('JELLYFIN_PASSWORD') diff --git a/personal_data/util.py b/personal_data/util.py index 536ff89..69aa53c 100644 --- a/personal_data/util.py +++ b/personal_data/util.py @@ -89,6 +89,7 @@ def deduplicate_by_ignoring_certain_fields( Output order is stable. """ + to_remove = set() for idx1, first in enumerate(dicts): for idx2, second in enumerate(dicts[idx1 + 1 :], idx1 + 1): @@ -163,7 +164,7 @@ def load_csv_file(csv_file: Path) -> list[frozendict]: def extend_csv_file( csv_file: Path, - new_dicts: list[dict], + new_dicts: list[dict[str,typing.Any]], deduplicate_mode: data.DeduplicateMode, deduplicate_ignore_columns: list[str], ) -> dict: