Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 59e8339e94 | |||
| 9277db97e5 | |||
| e755dc6641 | |||
| 782f5c15d6 | |||
| 2f4fae7d02 | |||
| 4b7c5aa58d |
15
PROJECT.org
15
PROJECT.org
@ -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:
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 = ""
|
||||
|
||||
@ -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 = ""
|
||||
|
||||
@ -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"])
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user