From 5943bc6e4f96b9a52ac06f89e2e2ac31b4ebace3 Mon Sep 17 00:00:00 2001 From: EliasLd Date: Wed, 23 Sep 2026 16:21:23 +0200 Subject: [PATCH] fix(weebcentral): rely on html fixed id for chapters label scraping --- .../source/weebcentral/scraper/chapters.go | 71 +++++++++---------- .../weebcentral/scraper/chapters_test.go | 50 +++++++++++++ 2 files changed, 83 insertions(+), 38 deletions(-) create mode 100644 internal/source/weebcentral/scraper/chapters_test.go diff --git a/internal/source/weebcentral/scraper/chapters.go b/internal/source/weebcentral/scraper/chapters.go index 5666a58..75c5774 100644 --- a/internal/source/weebcentral/scraper/chapters.go +++ b/internal/source/weebcentral/scraper/chapters.go @@ -25,8 +25,11 @@ type ChapterInfo struct { URL string } +// Extract the first chapter number from its dedicated label. +// Accepts "Chapter 271.5", "Episode 139.5", "#100", +// "Part 12", or simply "12.5". var chapterLabelPattern = regexp.MustCompile( - `(?i)^(?:chapter\s*|#\s*)(\d+(?:\.\d+)?)(?:$|[\s:-])`, + `^[^0-9]*([0-9]+(?:\.[0-9]+)?)(?:$|[\s:;,()\-–—])`, ) func parseChapterLabel( @@ -34,7 +37,10 @@ func parseChapterLabel( ) (common.ChapterNumber, string, bool) { var zero common.ChapterNumber - label := strings.TrimSpace(text) + label := strings.Join( + strings.Fields(text), + " ", + ) match := chapterLabelPattern.FindStringSubmatch(label) if len(match) != 2 { @@ -136,50 +142,39 @@ func GetScanInfo( ) } + chapterList := doc.Find("#chapter-list") + + // The full-chapter-list endpoint may return an HTML fragment + // without the outer #chapter-list container. + if chapterList.Length() == 0 { + log.Debug( + "Chapter list container not found; parsing response as an HTML fragment\n", + ) + + chapterList = doc.Selection + } + var chapters []ChapterInfo - doc.Find(`a[href^="/chapters/"]`).Each( - func(i int, selection *goquery.Selection) { + chapterList.Find(`a[href^="/chapters/"]`).Each( + func(_ int, selection *goquery.Selection) { href, exists := selection.Attr("href") if !exists { return } - var ( - chapterNumber common.ChapterNumber - label string - found bool - ) - - // The first direct spans inside span.grow may contain: - // - // "Chapter 100" - // "Chapter 100 - Some title" - // "# 100" - // "#100" - // - // Other nested spans such as "Last Read" are ignored. - selection.Find("span.grow > span").EachWithBreak( - func(i int, span *goquery.Selection) bool { - text := strings.TrimSpace(span.Text()) - - number, parsedLabel, ok := parseChapterLabel(text) - if !ok { - return true - } - - chapterNumber = number - label = parsedLabel - found = true - - return false - }, - ) + // Read only the chapter's dedicated label. + // Ignore nested status elements such as "Last Read". + rawLabel := selection. + Find("span.grow > span"). + First(). + Text() - if !found { + number, label, ok := parseChapterLabel(rawLabel) + if !ok { log.Debug( - "Skipping chapter with unrecognized label: %s\n", - strings.TrimSpace(selection.Text()), + "Skipping chapter with unrecognized label: %q\n", + strings.TrimSpace(rawLabel), ) return } @@ -200,7 +195,7 @@ func GetScanInfo( chapters = append( chapters, ChapterInfo{ - Number: chapterNumber, + Number: number, Label: label, URL: chapterURL, }, diff --git a/internal/source/weebcentral/scraper/chapters_test.go b/internal/source/weebcentral/scraper/chapters_test.go new file mode 100644 index 0000000..ff8fc70 --- /dev/null +++ b/internal/source/weebcentral/scraper/chapters_test.go @@ -0,0 +1,50 @@ +package weebcentralscraper + +import "testing" + +func TestParseChapterLabel(t *testing.T) { + tests := []struct { + label string + want string + valid bool + }{ + {"Chapter 271.5", "271.5", true}, + {"Episode 139.5", "139.5", true}, + {"Episode 9", "9", true}, + {"#100", "100", true}, + {"# 12.5", "12.5", true}, + {"Part 12 - Bonus", "12", true}, + {"Chapter 0", "0", true}, + {"12.5", "12.5", true}, + {"Chapter 12: Bonus 2", "12", true}, + {"Last Read", "", false}, + {"Chapter ???", "", false}, + {"Special", "", false}, + } + + for _, tt := range tests { + t.Run(tt.label, func(t *testing.T) { + number, _, ok := parseChapterLabel(tt.label) + + if ok != tt.valid { + t.Fatalf( + "valid = %v, want %v", + ok, + tt.valid, + ) + } + + if !tt.valid { + return + } + + if got := number.String(); got != tt.want { + t.Errorf( + "number = %q, want %q", + got, + tt.want, + ) + } + }) + } +}