Compare commits

..

6 Commits
30 ... 31

7 changed files with 82 additions and 15 deletions

View File

@ -92,7 +92,7 @@ fetching and simple saving.
:LOGBOOK:
CLOCK: [2025-07-09 Wed 09:55]--[2025-07-09 Wed 10:15] => 0:20
:END:
* Backlog [2/26]
* Backlog [3/27]
** TODO [#C] Create small utility to clean up tracks scrobbled with wonky playback times :vrobbler:personal:bug:music:scrobbles:
** TODO [#C] Move to using more robust mopidy-webhooks pacakge form pypi :utility:improvement:
:PROPERTIES:
@ -479,6 +479,19 @@ https://life.lab.unbl.ink/scrobble/e39779c8-62a5-46a6-bdef-fb7662810dc6/start/
- Note taken on [2025-09-30 Tue 09:33]
This may have already been resolved ... need to just confirm it.
* Version 31.0 [3/3]
** DONE [#A] Stop comic book webpage scrobbles from overwriting old scrobbles :vrobbler:personal:bug:books:scrobbling:
:PROPERTIES:
:ID: 4b2ec068-a281-a88b-c31d-6248d6eb0aa0
:END:
** DONE [#A] Add page calculation to manually scrobbled books :vrobbler:personal:feature:books:scrobbling:
:PROPERTIES:
:ID: b2e313b3-5c35-57e7-8933-627535baf34b
:END:
** DONE [#A] Fix bug in scrobbling comics where google fails :vrobbler:personal:bug:books:scrobbling:
:PROPERTIES:
:ID: 9a870c05-6d20-0803-d35d-c03fbe1d0ee1
:END:
* Version 30.0 [3/3]
** DONE [#A] Fix readcomicsonline browsing to update pages :vrobbler:books:feature:comicbook:personal:project:scrobbling:
:PROPERTIES:

View File

@ -262,12 +262,9 @@ class Book(LongPlayScrobblableMixin):
book_dict = lookup_comic_from_comicvine(title)
author_list = []
authors = book_dict.pop("authors")
cover_url = book_dict.pop("cover_url")
try:
genres = book_dict.pop("generes")
except:
genres = []
authors = book_dict.pop("authors", [])
cover_url = book_dict.pop("cover_url", "")
genres = book_dict.pop("generes", [])
if authors:
for author_str in authors:
@ -293,7 +290,7 @@ class Book(LongPlayScrobblableMixin):
return book
def save_image_from_url(self, url: str, force_update: bool = False):
if not self.cover or (force_update and url):
if url and (not self.cover or force_update):
r = requests.get(url)
if r.status_code == 200:
fname = f"{self.title}_{self.uuid}.jpg"

View File

@ -29,6 +29,9 @@ def lookup_book_from_google(title: str) -> dict:
google_result = (
json.loads(response.content).get("items", [{}])[0].get("volumeInfo")
)
if not google_result:
return {}
publish_date = pendulum.parse(google_result.get("publishedDate"))
isbn_13 = ""

View File

@ -1,7 +1,10 @@
from titlecase import titlecase
def parse_readcomicsonline_uri(uri: str) -> tuple:
path = uri.split("comic/")[1]
try:
path = uri.split("comic/")[1]
except IndexError:
return "", "", ""
parts = path.split('/')
title = ""

View File

@ -991,7 +991,7 @@ class Scrobble(TimeStampedModel):
@property
def can_be_updated(self) -> bool:
if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values():
if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values() and self.source != "readcomicsonline.ru":
logger.info(
"[scrobbling] cannot be updated, long play media",
extra={
@ -1172,7 +1172,7 @@ class Scrobble(TimeStampedModel):
# If it's marked as stopped, send it through our update mechanism, which will complete it
if scrobble and (
scrobble.can_be_updated
or read_log_page
or (read_log_page and scrobble.can_be_updated)
or scrobble_data["playback_status"] == "stopped"
):
if read_log_page:
@ -1181,6 +1181,7 @@ class Scrobble(TimeStampedModel):
for page in page_list:
if not page.get("end_ts", None):
page["end_ts"] = int(timezone.now().timestamp())
page["duration"] = page["end_ts"] - page.get("start_ts")
page_list.append(
BookPageLogData(
@ -1198,7 +1199,7 @@ class Scrobble(TimeStampedModel):
scrobble_data.pop("playback_status")
if read_log_page:
scrobble_data["log"] = BookLogData(page_data=BookPageLogData(page_number=read_log_page, start_ts=int(timezone.now().timestamp())))
scrobble_data["log"] = BookLogData(page_data=[BookPageLogData(page_number=read_log_page, start_ts=int(timezone.now().timestamp()))])
logger.info(
f"[scrobbling] creating new scrobble",
@ -1392,6 +1393,9 @@ class Scrobble(TimeStampedModel):
if class_name in LONG_PLAY_MEDIA.values():
self.finish_long_play()
if class_name == "Book":
self.calculate_reading_stats()
logger.info(
f"[scrobbling] stopped",
extra={
@ -1487,3 +1491,40 @@ class Scrobble(TimeStampedModel):
beyond_completion = False
return beyond_completion
def calculate_reading_stats(self, commit=True):
# --- Sort safely by numeric page_number ---
def safe_page_number(entry):
try:
return int(getattr("page_number", entry), 0)
except (ValueError, TypeError):
return float("inf") # push invalid entries to the end
page_data = self.log.get("page_data")
if not page_data:
logger.warning("No page data found to calculate")
return
if isinstance(page_data, dict):
logger.warning("Page data is dict, migrate koreader data")
return
page_data.sort(key=safe_page_number)
# --- Extract valid numeric page numbers ---
valid_pages = []
for page in page_data:
try:
valid_pages.append(int(page["page_number"]))
except (ValueError, TypeError):
continue
# --- Compute stats ---
if valid_pages:
self.log["page_start"] = min(valid_pages)
self.log["page_end"] = max(valid_pages)
self.log["pages_read"] = len(set(valid_pages))
if commit:
self.save(update_fields=["log"])

View File

@ -263,15 +263,25 @@ def manual_scrobble_book(
if READCOMICSONLINE_URL in title:
title, volume, page = parse_readcomicsonline_uri(title)
if not title:
logger.info(
"[scrobblers] manual book scrobble request failed",
extra={
"title": title,
"user_id": user_id,
"media_type": Scrobble.MediaType.BOOK,
},
)
return
title = f"{title} - Issue {volume}"
if not page:
page = 1
log = BookLogData(page_data=BookPageLogData(page_number=page, start_ts=int(timezone.now().timestamp())))
logger.info("[scrobblers] Book page included in scrobble, should update!")
source = READCOMICSONLINE_URL
source = READCOMICSONLINE_URL.replace("https://", "")
# TODO: Check for scrobble of this book already and if so, update the page count

View File

@ -625,7 +625,7 @@ def scrobble_start(request, uuid):
if (
user.profile.redirect_to_webpage
and media_obj.__class__.__name__ == Scrobble.MediaType.WEBPAGE
and (media_obj.__class__.__name__ == Scrobble.MediaType.WEBPAGE or media_obj.__class__.__name__ == Scrobble.MediaType.BOOK)
):
logger.info(f"Redirecting to {media_obj} detail page")
return HttpResponseRedirect(media_obj.url)