diff --git a/vrobbler/apps/scrobbles/constants.py b/vrobbler/apps/scrobbles/constants.py index e24d9fd..91d1684 100644 --- a/vrobbler/apps/scrobbles/constants.py +++ b/vrobbler/apps/scrobbles/constants.py @@ -55,6 +55,7 @@ MANUAL_SCROBBLE_FNS = { "-p": "manual_scrobble_puzzle", "-l": "manual_scrobble_brickset", "-c": "manual_scrobble_book", + "-f": "manual_scrobble_food", } diff --git a/vrobbler/apps/scrobbles/forms.py b/vrobbler/apps/scrobbles/forms.py index 4029320..6ea5e01 100644 --- a/vrobbler/apps/scrobbles/forms.py +++ b/vrobbler/apps/scrobbles/forms.py @@ -21,7 +21,7 @@ class ScrobbleForm(forms.Form): widget=forms.TextInput( attrs={ "class": "form-control form-control-dark w-100", - "placeholder": "Scrobble something (ttIMDB, -v Video Game title, -b Book title, -s TheSportsDB ID)", + "placeholder": "Scrobble something (ttIMDB, -v Video Game title, -b Book title, -s TheSportsDB ID, -f Food name - calories)", "aria-label": "Scrobble something", } ), @@ -56,9 +56,7 @@ def django_form_field_from_type(field_type, required=True): if type(None) in args: required = False non_none_type = [arg for arg in args if arg is not type(None)][0] - return django_form_field_from_type( - non_none_type, required=required - ) + return django_form_field_from_type(non_none_type, required=required) # Determine actual type base_type = origin if origin else field_type @@ -84,9 +82,7 @@ def form_from_dataclass(dataclass): continue required = f.default is None and f.default_factory is None - form_fields[f.name] = django_form_field_from_type( - f.type, required=required - ) + form_fields[f.name] = django_form_field_from_type(f.type, required=required) if f.name in dataclass._excluded_fields: form_fields[f.name].disabled = True @@ -101,9 +97,7 @@ def form_from_dataclass(dataclass): def clean_notes(self): notes_str = self.cleaned_data.get("notes", "") - return [ - line.strip() for line in notes_str.splitlines() if line.strip() - ] + return [line.strip() for line in notes_str.splitlines() if line.strip()] form_cls.clean_notes = clean_notes return form_cls diff --git a/vrobbler/apps/scrobbles/scrobblers.py b/vrobbler/apps/scrobbles/scrobblers.py index 98a4ebf..a71e1b8 100644 --- a/vrobbler/apps/scrobbles/scrobblers.py +++ b/vrobbler/apps/scrobbles/scrobblers.py @@ -84,11 +84,7 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: run_time_seconds=post_data.get("run_time", 900000), ) try: - album_id = ( - Album.objects.filter(name=post_data.get("album", "")) - .first() - .id - ) + album_id = Album.objects.filter(name=post_data.get("album", "")).first().id except Exception: pass @@ -108,17 +104,14 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble: user_id, source="Mopidy", playback_position_seconds=int( - post_data.get(MOPIDY_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) - / 1000 + post_data.get(MOPIDY_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) / 1000 ), status=post_data.get(MOPIDY_POST_KEYS.get("STATUS"), ""), log=log, ) -def jellyfin_scrobble_media( - post_data: dict, user_id: int -) -> Optional[Scrobble]: +def jellyfin_scrobble_media(post_data: dict, user_id: int) -> Optional[Scrobble]: media_type = Scrobble.MediaType.VIDEO if post_data.pop("ItemType", "") in JELLYFIN_AUDIO_ITEM_TYPES: media_type = Scrobble.MediaType.TRACK @@ -136,13 +129,12 @@ def jellyfin_scrobble_media( ) return - timestamp = parse( - post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "") - ).replace(tzinfo=pytz.utc) + timestamp = parse(post_data.get(JELLYFIN_POST_KEYS.get("TIMESTAMP"), "")).replace( + tzinfo=pytz.utc + ) playback_position_seconds = int( - post_data.get(JELLYFIN_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) - / 10000000 + post_data.get(JELLYFIN_POST_KEYS.get("PLAYBACK_POSITION_TICKS"), 1) / 10000000 ) album_id = None if media_type == Scrobble.MediaType.VIDEO: @@ -153,16 +145,10 @@ def jellyfin_scrobble_media( title=post_data.get("Name", ""), artist_name=post_data.get("Artist", ""), album_name=post_data.get("Album", ""), - run_time_seconds=convert_to_seconds( - post_data.get("RunTime", 900000) - ), + run_time_seconds=convert_to_seconds(post_data.get("RunTime", 900000)), ) try: - album_id = ( - Album.objects.filter(name=post_data.get("Album", "")) - .first() - .id - ) + album_id = Album.objects.filter(name=post_data.get("Album", "")).first().id except Exception: pass # A hack because we don't worry about updating music ... we either finish it or we don't @@ -328,9 +314,7 @@ def manual_scrobble_book( if not page: page = 1 - logger.info( - "[scrobblers] Book page included in scrobble, should update!" - ) + logger.info("[scrobblers] Book page included in scrobble, should update!") source = READCOMICSONLINE_URL.replace("https://", "") @@ -447,9 +431,7 @@ def email_scrobble_board_game( if not location.name: location.name = location_dict.get("name") update_fields.append("name") - geoloc = GeoLocation.objects.filter( - title__icontains=location.name - ).first() + geoloc = GeoLocation.objects.filter(title__icontains=location.name).first() if geoloc: location.geo_location = geoloc update_fields.append("geo_location") @@ -501,9 +483,7 @@ def email_scrobble_board_game( log_data.pop("expansion_ids") if play_dict.get("locationRefId", False): - log_data["location_id"] = locations[ - play_dict.get("locationRefId") - ].id + log_data["location_id"] = locations[play_dict.get("locationRefId")].id if play_dict.get("rounds", False): log_data["rounds"] = play_dict.get("rounds") if play_dict.get("board", False): @@ -526,9 +506,7 @@ def email_scrobble_board_game( timestamp = parse(play_dict.get("playDate")) if hour and minute: logger.info(f"Scrobble playDate has manual start time {timestamp}") - timestamp = timestamp.replace( - hour=hour, minute=minute, second=second or 0 - ) + timestamp = timestamp.replace(hour=hour, minute=minute, second=second or 0) logger.info(f"Update to {timestamp}") profile = UserProfile.objects.filter(user_id=user_id).first() @@ -623,9 +601,7 @@ def manual_scrobble_from_url( scrobbler. Otherwise, return nothing.""" if RecipeScraperService.is_recipe_url(url): - return scrobble_from_recipe_website( - url, user_id, source=source, action=action - ) + return scrobble_from_recipe_website(url, user_id, source=source, action=action) content_key = "" domain = extract_domain(url) @@ -828,9 +804,7 @@ def emacs_scrobble_update_task( if not scrobble.log.get('notes"'): scrobble.log["notes"] = [] if note.get("timestamp") not in existing_note_ts: - scrobble.log["notes"].append( - {note.get("timestamp"): note.get("content")} - ) + scrobble.log["notes"].append({note.get("timestamp"): note.get("content")}) notes_updated = True if notes_updated: @@ -856,9 +830,7 @@ def emacs_scrobble_task( user_context_list: list[str] = [], ) -> Scrobble | None: orgmode_id = task_data.get("source_id") - title = get_title_from_labels( - task_data.get("labels", []), user_context_list - ) + title = get_title_from_labels(task_data.get("labels", []), user_context_list) task = Task.find_or_create(title) @@ -981,9 +953,7 @@ def manual_scrobble_webpage( ): if RecipeScraperService.is_recipe_url(url): - return scrobble_from_recipe_website( - url, user_id, source=source, action=action - ) + return scrobble_from_recipe_website(url, user_id, source=source, action=action) webpage = WebPage.find_or_create({"url": url}) @@ -1205,3 +1175,48 @@ def manual_scrobble_brickset( # TODO Kick out a process to enrich the media here, and in every scrobble event # TODO Need to check for past scrobbles and auto populate serial scrobble id if possible return Scrobble.create_or_update(brickset, user_id, scrobble_dict) + + +def manual_scrobble_food( + item_id: str, + user_id: int, + source: str = "Vrobbler", + action: Optional[str] = None, +): + match = re.match(r"(.+?)\s+-\s+(\d+)", item_id) + if not match: + logger.info( + "[manual_scrobble_food] invalid format, expected 'Food Name - calories'", + extra={"item_id": item_id, "user_id": user_id}, + ) + return + + food_name = match.group(1).strip() + calories = int(match.group(2)) + + food = Food.objects.filter(title=food_name).first() + if not food: + food = Food.objects.create( + title=food_name, + description=food_name, + calories=calories, + ) + + scrobble_dict = { + "user_id": user_id, + "timestamp": timezone.now(), + "playback_position_seconds": 0, + "source": source, + } + + logger.info( + "[scrobblers] manual food scrobble request received", + extra={ + "food_id": food.id, + "user_id": user_id, + "scrobble_dict": scrobble_dict, + "media_type": Scrobble.MediaType.FOOD, + }, + ) + + return Scrobble.create_or_update(food, user_id, scrobble_dict)