From f38ab058e025f8c642845e488c5ea0e9c7e302ad Mon Sep 17 00:00:00 2001 From: mkelvers Date: Sat, 11 Apr 2026 22:16:32 +0200 Subject: [PATCH] fix: log watch-order parse fallback --- internal/jikan/relations.go | 4 ++++ internal/watchorder/watch_order.go | 9 +++++++++ internal/watchorder/watch_order_test.go | 22 ++++++++++++++++++++++ 3 files changed, 35 insertions(+) diff --git a/internal/jikan/relations.go b/internal/jikan/relations.go index 0fb68fa..276df27 100644 --- a/internal/jikan/relations.go +++ b/internal/jikan/relations.go @@ -3,6 +3,7 @@ package jikan import ( "context" "fmt" + "log" "strings" "time" @@ -48,6 +49,7 @@ func (c *Client) getWatchOrder(ctx context.Context, id int) (watchorder.WatchOrd result, err := watchorder.FetchWatchOrder(requestCtx, c.httpClient, watchOrderURL) if err != nil { + log.Printf("relations: watch-order fetch failed for %d (%s): %v", id, watchOrderURL, err) return watchorder.WatchOrderResult{}, err } @@ -72,6 +74,7 @@ func (c *Client) currentOnlyRelation(ctx context.Context, id int) ([]RelationEnt func (c *Client) GetFullRelations(ctx context.Context, id int) ([]RelationEntry, error) { result, err := c.getWatchOrder(ctx, id) if err != nil { + log.Printf("relations: using current-only fallback for %d: %v", id, err) return c.currentOnlyRelation(ctx, id) } @@ -93,6 +96,7 @@ func (c *Client) GetFullRelations(ctx context.Context, id int) ([]RelationEntry, anime, err := c.GetAnimeByID(ctx, watchOrderEntry.ID) if err != nil { + log.Printf("relations: skipping related anime %d for root %d: %v", watchOrderEntry.ID, id, err) continue } diff --git a/internal/watchorder/watch_order.go b/internal/watchorder/watch_order.go index a847f51..e41a830 100644 --- a/internal/watchorder/watch_order.go +++ b/internal/watchorder/watch_order.go @@ -17,6 +17,7 @@ const defaultUserAgent = "anime-relations-scraper/1.0 (+https://github.com/mkelv var idPattern = regexp.MustCompile(`/id/(\d+)`) var ErrInvalidWatchOrderURL = errors.New("invalid watch order url") +var ErrWatchOrderMarkupNotFound = errors.New("watch order markup not found") type WatchOrderEntry struct { ID int `json:"id"` @@ -150,6 +151,10 @@ func extractRows(doc *goquery.Document) []watchOrderRow { return rows } +func hasWatchOrderTable(doc *goquery.Document) bool { + return doc.Find("#wo_list").Length() > 0 +} + func FetchWatchOrder(ctx context.Context, httpClient *http.Client, url string) (WatchOrderResult, error) { rootID, err := parseRootID(url) if err != nil { @@ -161,6 +166,10 @@ func FetchWatchOrder(ctx context.Context, httpClient *http.Client, url string) ( return WatchOrderResult{}, err } + if !hasWatchOrderTable(doc) { + return WatchOrderResult{}, ErrWatchOrderMarkupNotFound + } + rows := extractRows(doc) if len(rows) == 0 { return WatchOrderResult{ID: rootID, WatchOrder: []WatchOrderEntry{}}, nil diff --git a/internal/watchorder/watch_order_test.go b/internal/watchorder/watch_order_test.go index 5949321..77139ab 100644 --- a/internal/watchorder/watch_order_test.go +++ b/internal/watchorder/watch_order_test.go @@ -2,6 +2,7 @@ package watchorder import ( "context" + "errors" "net/http" "net/http/httptest" "testing" @@ -52,6 +53,16 @@ func testHTMLEmptyRows() string { ` } +func testHTMLWithoutWatchOrderTable() string { + return ` + + + +

challenge page

+ +` +} + func TestFetchWatchOrder_OutputShape(t *testing.T) { server := testServer(testHTMLWithMetadata()) defer server.Close() @@ -103,3 +114,14 @@ func TestFetchWatchOrder_NoRowsReturnsEmpty(t *testing.T) { t.Fatalf("expected no entries, got %d", len(result.WatchOrder)) } } + +func TestFetchWatchOrder_MissingMarkupReturnsError(t *testing.T) { + server := testServer(testHTMLWithoutWatchOrderTable()) + defer server.Close() + + url := server.URL + "/?/tools/watch_order/id/1535" + _, err := FetchWatchOrder(context.Background(), &http.Client{Timeout: time.Second}, url) + if !errors.Is(err, ErrWatchOrderMarkupNotFound) { + t.Fatalf("expected ErrWatchOrderMarkupNotFound, got %v", err) + } +}