fix: improve discover and manual scrape flows

This commit is contained in:
ShukeBta
2026-06-24 13:58:04 +08:00
parent 34396a3a8b
commit 34c9cb4244
8 changed files with 255 additions and 79 deletions
+12 -4
View File
@@ -399,8 +399,8 @@ func (s *ScraperService) manualDoubanMatch(ctx context.Context, query string) *M
if s.douban == nil || !s.douban.Enabled() {
return nil
}
if _, ok := parsePositiveInt(query); ok {
if match, err := s.douban.GetMatchByID(ctx, query); err == nil && match != nil {
if id, ok := parsePositiveIDString(query); ok {
if match, err := s.douban.GetMatchByID(ctx, id); err == nil && match != nil {
return match
}
}
@@ -431,8 +431,8 @@ func (s *ScraperService) manualTheTVDBMatch(ctx context.Context, query string) *
if s.thetvdb == nil || !s.thetvdb.Enabled() {
return nil
}
if _, ok := parsePositiveInt(normalizeTheTVDBSeriesID(query)); ok {
if match, err := s.thetvdb.GetSeriesMatchByID(ctx, query); err == nil && match != nil {
if id, ok := parsePositiveIDString(normalizeTheTVDBSeriesID(query)); ok {
if match, err := s.thetvdb.GetSeriesMatchByID(ctx, id); err == nil && match != nil {
return match
}
}
@@ -508,6 +508,14 @@ func parsePositiveInt(value string) (int, bool) {
return id, err == nil && id > 0
}
func parsePositiveIDString(value string) (string, bool) {
id, ok := parsePositiveInt(value)
if !ok {
return "", false
}
return strconv.Itoa(id), true
}
func manualScrapeBatchName(ids []string) string {
if len(ids) == 1 {
return ids[0]
+18
View File
@@ -32,6 +32,24 @@ func TestManualRequestMatchFallsBackToCandidatePayload(t *testing.T) {
}
}
func TestParsePositiveIDStringAcceptsProviderPrefixes(t *testing.T) {
cases := map[string]string{
"12345": "12345",
"tmdb:12345": "12345",
"douban:67890": "67890",
"thetvdb:24680": "24680",
}
for input, want := range cases {
got, ok := parsePositiveIDString(input)
if !ok || got != want {
t.Fatalf("parsePositiveIDString(%q) = %q,%v; want %q,true", input, got, ok, want)
}
}
if got, ok := parsePositiveIDString("tmdb:not-a-number"); ok || got != "" {
t.Fatalf("parsePositiveIDString invalid = %q,%v; want empty,false", got, ok)
}
}
func TestManualSearchReturnsTMDbCandidatePage(t *testing.T) {
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
+15 -5
View File
@@ -169,10 +169,11 @@ func compactSeriesKey(raw string) string {
}
var (
seriesYearRE = regexp.MustCompile(`\s*\((?:19|20)\d{2}\)\s*`)
seriesIDRE = regexp.MustCompile(`(?i)\s*\[(?:tmdb|tmdbid)[=-]\d+\]\s*`)
seriesBraceRE = regexp.MustCompile(`(?i)\s*\{(?:tmdb|tmdbid|douban|bangumi|bgm|thetvdb|tvdb)[\s:=#-]*[a-z0-9_-]+\}\s*`)
seriesSpacerRE = regexp.MustCompile(`[\s._-]+`)
seriesYearRE = regexp.MustCompile(`\s*\((?:19|20)\d{2}\)\s*`)
seriesIDRE = regexp.MustCompile(`(?i)\s*\[(?:tmdb|tmdbid)[=-]\d+\]\s*`)
seriesBraceRE = regexp.MustCompile(`(?i)\s*\{(?:tmdb|tmdbid|douban|bangumi|bgm|thetvdb|tvdb)[\s:=#-]*[a-z0-9_-]+\}\s*`)
seriesSpacerRE = regexp.MustCompile(`[\s._-]+`)
seriesSpecialSuffixRE = regexp.MustCompile(`(?i)(?:\s+(?:specials?|sp|ova|oad|extra|extras)|\s*(?:特别篇|特別篇|番外|特典|外传|外傳))$`)
)
func normalizeSeriesTitle(value string) string {
@@ -184,6 +185,15 @@ func normalizeSeriesTitle(value string) string {
return strings.TrimSpace(value)
}
func normalizeSeriesPathTitle(value string) string {
title := normalizeSeriesTitle(value)
stripped := strings.TrimSpace(seriesSpecialSuffixRE.ReplaceAllString(title, ""))
if stripped != "" {
return stripped
}
return title
}
func seriesTitleFromMediaPath(path string) string {
if strings.TrimSpace(path) == "" {
return ""
@@ -199,7 +209,7 @@ func seriesTitleFromMediaPath(path string) string {
if dirIndex < 0 {
return ""
}
return normalizeSeriesTitle(parts[dirIndex])
return normalizeSeriesPathTitle(parts[dirIndex])
}
func seriesDisplayTitle(media model.Media) string {
+34
View File
@@ -27,3 +27,37 @@ func TestMediaSeriesKeyCollapsesNestedSpecialFolders(t *testing.T) {
t.Fatalf("cards=%#v, want one merged series card with two items", cards)
}
}
func TestMediaSeriesKeyCollapsesSpecialTitleSuffix(t *testing.T) {
main := model.Media{
LibraryID: "lib-tv",
Path: `cloud://openlist/电视剧/欧美剧/Example Show/Season 01/Example.Show.S01E01.mkv`,
SeasonNum: 1,
EpisodeNum: 1,
}
special := model.Media{
LibraryID: "lib-tv",
Path: `cloud://openlist/电视剧/欧美剧/Example Show Specials/Example.Show.Special.01.mkv`,
SeasonNum: 0,
EpisodeNum: 1,
}
chineseSpecial := model.Media{
LibraryID: "lib-tv",
Path: `cloud://openlist/动漫/国漫/示例剧 特别篇/示例剧.SP01.mkv`,
SeasonNum: 0,
EpisodeNum: 1,
}
chineseMain := model.Media{
LibraryID: "lib-tv",
Path: `cloud://openlist/动漫/国漫/示例剧/Season 01/示例剧.S01E01.mkv`,
SeasonNum: 1,
EpisodeNum: 1,
}
if got, want := mediaSeriesKey(special), mediaSeriesKey(main); got != want {
t.Fatalf("english special key=%q, want main key=%q", got, want)
}
if got, want := mediaSeriesKey(chineseSpecial), mediaSeriesKey(chineseMain); got != want {
t.Fatalf("chinese special key=%q, want main key=%q", got, want)
}
}