Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions internal/search/audiobookbay.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/PuerkitoBio/goquery"
)

const abbBrowserUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"

// AudioBookBay searches an AudioBookBay-style scrape source for audiobook torrents.
// Mirrors and trackers are loaded from the runtime sources registry.
type AudioBookBay struct {
Expand Down Expand Up @@ -59,7 +61,7 @@ func (a *AudioBookBay) searchDomain(ctx context.Context, domain, query string) (
q.Set("s", query)
q.Set("tt", "1")
req.URL.RawQuery = q.Encode()
req.Header.Set("User-Agent", a.cfg.UserAgent)
setABBRequestHeaders(req)

resp, err := a.client.Do(req)
if err != nil {
Expand Down Expand Up @@ -137,6 +139,7 @@ func ResolveABBMagnet(ctx context.Context, client *http.Client, userAgent, abbPa
if len(fallbackTrackers) == 0 {
return "", fmt.Errorf("no AudioBookBay fallback trackers configured (registry not loaded?)")
}
_ = userAgent // retained for API compatibility; ABB now uses a browser-like header set.

infoHashRe := regexp.MustCompile(`(?i)Info\s*Hash:.*?<td[^>]*>\s*([0-9a-fA-F]{40})`)
trackerRe := regexp.MustCompile(`<td>((?:udp|http)://[^<]+)</td>`)
Expand All @@ -149,7 +152,7 @@ func ResolveABBMagnet(ctx context.Context, client *http.Client, userAgent, abbPa
if err != nil {
continue
}
req.Header.Set("User-Agent", userAgent)
setABBRequestHeaders(req)

resp, err := client.Do(req)
if err != nil {
Expand Down Expand Up @@ -206,3 +209,11 @@ func ResolveABBMagnet(ctx context.Context, client *http.Client, userAgent, abbPa
}
return "", fmt.Errorf("failed to resolve ABB magnet from all domains")
}

func setABBRequestHeaders(req *http.Request) {
req.Header.Set("User-Agent", abbBrowserUserAgent)
req.Header.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8")
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
req.Header.Set("Upgrade-Insecure-Requests", "1")
req.Header.Set("Accept-Encoding", "identity")
}
92 changes: 92 additions & 0 deletions internal/search/audiobookbay_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package search

import (
"context"
"io"
"net/http"
"strings"
"testing"

"github.com/JeremiahM37/librarr/internal/config"
"github.com/JeremiahM37/librarr/internal/sources/sourcestest"
)

type abbRoundTripper struct {
body string
req *http.Request
}

func (r *abbRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
r.req = req.Clone(req.Context())
return &http.Response{
StatusCode: http.StatusOK,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(r.body)),
Request: req,
}, nil
}

func TestAudioBookBaySearchSetsBrowserHeaders(t *testing.T) {
rt := &abbRoundTripper{
body: `<html><body>
<div class="post">
<div class="postTitle"><h2><a href="/abss/the-martian-andy-weir/">The Martian - Andy Weir</a></h2></div>
<div class="postInfo">Language: English <span style="margin-left:100px;">Keywords:</div>
</div>
</body></html>`,
}
reg, err := sourcestest.Registry()
if err != nil {
t.Fatalf("load registry: %v", err)
}
cfg := &config.Config{
UserAgent: "test",
Sources: reg,
}
cfg.Sources.AudioBookBay.Mirrors = []string{"audiobookbay.lu"}
a := &AudioBookBay{cfg: cfg, client: &http.Client{Transport: rt}}

results, err := a.searchDomain(context.Background(), "audiobookbay.lu", "The Martian")
if err != nil {
t.Fatalf("searchDomain returned error: %v", err)
}
if len(results) != 1 {
t.Fatalf("expected 1 result, got %d", len(results))
}
if got := rt.req.Header.Get("User-Agent"); got != abbBrowserUserAgent {
t.Fatalf("User-Agent = %q, want %q", got, abbBrowserUserAgent)
}
if got := rt.req.Header.Get("Accept-Language"); got != "en-US,en;q=0.9" {
t.Fatalf("Accept-Language = %q, want en-US,en;q=0.9", got)
}
if got := rt.req.Header.Get("Upgrade-Insecure-Requests"); got != "1" {
t.Fatalf("Upgrade-Insecure-Requests = %q, want 1", got)
}
}

func TestResolveABBMagnetUsesBrowserHeaders(t *testing.T) {
rt := &abbRoundTripper{
body: `<html><body>
<h1>The Martian</h1>
<table>
<tr><td>Info Hash:</td><td>0123456789ABCDEF0123456789ABCDEF01234567</td></tr>
<tr><td>udp://tracker.example:1337/announce</td></tr>
</table>
</body></html>`,
}
client := &http.Client{Transport: rt}

magnet, err := ResolveABBMagnet(context.Background(), client, "test", "/abss/the-martian-andy-weir/", []string{"audiobookbay.lu"}, []string{"udp://fallback.example:1337/announce"})
if err != nil {
t.Fatalf("ResolveABBMagnet returned error: %v", err)
}
if !strings.HasPrefix(magnet, "magnet:?xt=urn:btih:0123456789ABCDEF0123456789ABCDEF01234567") {
t.Fatalf("unexpected magnet: %s", magnet)
}
if got := rt.req.Header.Get("User-Agent"); got != abbBrowserUserAgent {
t.Fatalf("User-Agent = %q, want %q", got, abbBrowserUserAgent)
}
if got := rt.req.Header.Get("Accept-Encoding"); got != "identity" {
t.Fatalf("Accept-Encoding = %q, want identity", got)
}
}
Loading