From 820f354ed2a5cc86f0c1903647127ca5db8f7aac Mon Sep 17 00:00:00 2001 From: Marco <130363067+dutchdevil-83@users.noreply.github.com> Date: Thu, 23 Apr 2026 00:16:29 +0200 Subject: [PATCH 1/3] fix(scraper): prevent nil pointer dereference in scrapeScene When scrapeScene finds no results, ret remains nil and is passed to processSceneRelationships, which unconditionally dereferences it at line 89 (ret.Performers = ...), causing a panic. Initialize ret to an empty ScrapedScene so processSceneRelationships always has a valid pointer. This also preserves the intent of #3953: returning a scene with only relationship fields set when scraped non-relationship data is absent. Fixes panic: runtime error: invalid memory address or nil pointer dereference at pkg/scraper/mapped.go:89 in processSceneRelationships --- pkg/scraper/mapped.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/scraper/mapped.go b/pkg/scraper/mapped.go index d92415c61..5e8fba744 100644 --- a/pkg/scraper/mapped.go +++ b/pkg/scraper/mapped.go @@ -184,7 +184,10 @@ func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*models. logger.Debug(`Processing scene:`) results := sceneMap.process(ctx, q, s.Common, urlsIsMulti) - var ret *models.ScrapedScene + // Initialize ret to a non-nil empty scene so that processSceneRelationships + // can safely populate relationship fields even when no direct results were found. + // This preserves the intent of #3953: returning a scene with only relationships. + ret := &models.ScrapedScene{} if len(results) > 0 { ret = results[0].scrapedScene() } From c371dbb717ba01d56763de29bd672280e769d6a2 Mon Sep 17 00:00:00 2001 From: Marco <130363067+dutchdevil-83@users.noreply.github.com> Date: Sat, 25 Apr 2026 11:30:59 +0200 Subject: [PATCH 2/3] Update mapped.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/scraper/mapped.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/scraper/mapped.go b/pkg/scraper/mapped.go index 5e8fba744..dc3c7381e 100644 --- a/pkg/scraper/mapped.go +++ b/pkg/scraper/mapped.go @@ -184,12 +184,14 @@ func (s mappedScraper) scrapeScene(ctx context.Context, q mappedQuery) (*models. logger.Debug(`Processing scene:`) results := sceneMap.process(ctx, q, s.Common, urlsIsMulti) - // Initialize ret to a non-nil empty scene so that processSceneRelationships - // can safely populate relationship fields even when no direct results were found. + // Ensure ret is non-nil before calling processSceneRelationships so it can + // safely populate relationship fields even when no direct results were found. // This preserves the intent of #3953: returning a scene with only relationships. - ret := &models.ScrapedScene{} + var ret *models.ScrapedScene if len(results) > 0 { ret = results[0].scrapedScene() + } else { + ret = &models.ScrapedScene{} } hasRelationships := s.processSceneRelationships(ctx, q, 0, ret) From 8c81c8ee85537db0ce93eb3bb5132e28c12ee467 Mon Sep 17 00:00:00 2001 From: Marco <130363067+dutchdevil-83@users.noreply.github.com> Date: Wed, 29 Apr 2026 13:11:36 +0000 Subject: [PATCH 3/3] test(scraper): add relationships-only scene regression test Signed-off-by: Marco <130363067+dutchdevil-83@users.noreply.github.com> --- pkg/scraper/xpath_test.go | 107 +++++++++++++++++--------------------- 1 file changed, 49 insertions(+), 58 deletions(-) diff --git a/pkg/scraper/xpath_test.go b/pkg/scraper/xpath_test.go index 42ee2227b..4536495ac 100644 --- a/pkg/scraper/xpath_test.go +++ b/pkg/scraper/xpath_test.go @@ -683,74 +683,65 @@ func verifyPerformers(t *testing.T, expectedNames []string, expectedURLs []strin } } -func TestApplySceneXPathConfig(t *testing.T) { - reader := strings.NewReader(sceneHTML) - doc, err := htmlquery.Parse(reader) +func TestApplySceneXPathConfigRelationshipsOnly(t *testing.T) { + reader := strings.NewReader(sceneHTML) + doc, err := htmlquery.Parse(reader) + if err != nil { + t.Fatalf("Error loading document: %s", err.Error()) + } - if err != nil { - t.Errorf("Error loading document: %s", err.Error()) - return - } + scraper := makeSceneXPathConfig() - scraper := makeSceneXPathConfig() + // Simulate the relationships-only scrape path: + // no direct scene fields, but populated Performers/Tags mappings. + scraper.Scene.mappedConfig = make(mappedConfig) + scraper.Scene.Studio = nil + scraper.Scene.Movies = nil + scraper.Scene.Groups = nil - q := &xpathQuery{ - doc: doc, - } - scene, err := scraper.scrapeScene(context.Background(), q) + q := &xpathQuery{ + doc: doc, + } - if err != nil { - t.Errorf("Error scraping scene: %s", err.Error()) - return - } + var scene *models.ScrapedScene + assert.NotPanics(t, func() { + scene, err = scraper.scrapeScene(context.Background(), q) + }, "relationships-only scene scrape should not panic") - const title = "Test Video" + assert.NoError(t, err) + assert.NotNil(t, scene) - verifyField(t, title, scene.Title, "Title") + // No direct scene fields should be populated. + assert.Nil(t, scene.Title) + assert.Nil(t, scene.Date) - // verify tags - expectedTags := []string{ - "Amateur", - "Babe", - "Blowjob", - "Exclusive", - "HD Porn", - "Pornstar", - "Public", - "Pussy Licking", - "Threesome", - "Verified Models", - } - verifyTags(t, expectedTags, scene.Tags) + expectedTags := []string{ + "Amateur", + "Babe", + "Blowjob", + "Exclusive", + "HD Porn", + "Pornstar", + "Public", + "Pussy Licking", + "Threesome", + "Verified Models", + } + verifyTags(t, expectedTags, scene.Tags) - // verify movies - expectedMovies := []string{ - "Video", - "of", - "verified", - "member", - } - verifyMovies(t, expectedMovies, scene.Movies) + expectedPerformerNames := []string{ + "Alex D", + "Mia Malkova", + "Riley Reid", + } - expectedPerformerNames := []string{ - "Alex D", - "Mia Malkova", - "Riley Reid", - } + expectedPerformerURLs := []string{ + "/pornstar/alex-d", + "/pornstar/mia-malkova", + "/pornstar/riley-reid", + } - expectedPerformerURLs := []string{ - "/pornstar/alex-d", - "/pornstar/mia-malkova", - "/pornstar/riley-reid", - } - - verifyPerformers(t, expectedPerformerNames, expectedPerformerURLs, scene.Performers) - - const expectedStudioName = "Sis Loves Me" - const expectedStudioURL = "/channels/sis-loves-me" - - verifyField(t, expectedStudioName, &scene.Studio.Name, "Studio.Name") - verifyField(t, expectedStudioURL, scene.Studio.URL, "Studio.URL") + verifyPerformers(t, expectedPerformerNames, expectedPerformerURLs, scene.Performers) } func TestLoadXPathScraperFromYAML(t *testing.T) {