Add gated cover byte fetcher

This commit is contained in:
2026-08-10 00:36:19 +07:00
parent e8d1cba6c5
commit cf06801bc5
8 changed files with 559 additions and 25 deletions
+38 -11
View File
@@ -16,8 +16,8 @@ type Fetcher interface {
Get(ctx context.Context, url string) (body string, status int, err error)
}
// CoverFetcher retrieves one kagane cover through the browser-backed path.
type CoverFetcher interface {
// BrowserCoverFetcher retrieves one kagane cover through the browser-backed path.
type BrowserCoverFetcher interface {
Image(ctx context.Context, imageID string) (body []byte, contentType string, err error)
}
@@ -42,9 +42,11 @@ type Poller struct {
// cannot clear. Nil disables those sites entirely rather than falling back
// to Fetch, which would only ever retrieve a challenge page.
BrowserFetch Fetcher
// CoverFetch is optional: it prefetches kagane covers without changing the
// chapter poll's result when the browser or persistence fails.
CoverFetch CoverFetcher
// CoverFetch is optional; failures are logged and never affect the chapter poll.
CoverFetch BrowserCoverFetcher
// CoverBytesFetch is optional; it handles non-kagane sources through the same
// failure-isolated prefetch path.
CoverBytesFetch CoverBytesFetcher
Now func() time.Time // injected so tests can freeze it
Cooldown time.Duration
BrowserCooldown time.Duration
@@ -56,14 +58,39 @@ type Poller struct {
var browserBackedSites = []string{"kagane", "novelfull"}
func (p *Poller) prefetchCover(ctx context.Context, sr store.Series) {
if sr.Site != "kagane" || p.CoverFetch == nil {
if sr.Cover == "" {
return
}
imageID, ok := store.KaganeImageID(sr.Cover)
if !ok {
if sr.Site == "kagane" {
if p.CoverFetch == nil {
return
}
imageID, ok := store.KaganeImageID(sr.Cover)
if !ok {
return
}
_, _, found, err := p.Store.GetKaganeCover(imageID)
if err != nil {
log.Printf("latest poll %q: read cover: %v", sr.Key(), err)
return
}
if found {
return
}
body, contentType, err := p.CoverFetch.Image(ctx, imageID)
if err != nil {
log.Printf("latest poll %q: fetch cover: %v", sr.Key(), err)
return
}
if err := p.Store.PutKaganeCover(imageID, body, contentType); err != nil {
log.Printf("latest poll %q: persist cover: %v", sr.Key(), err)
}
return
}
_, _, found, err := p.Store.GetKaganeCover(imageID)
if p.CoverBytesFetch == nil {
return
}
_, _, found, err := p.Store.GetCover(sr.Cover)
if err != nil {
log.Printf("latest poll %q: read cover: %v", sr.Key(), err)
return
@@ -71,12 +98,12 @@ func (p *Poller) prefetchCover(ctx context.Context, sr store.Series) {
if found {
return
}
body, contentType, err := p.CoverFetch.Image(ctx, imageID)
body, contentType, err := p.CoverBytesFetch.Fetch(ctx, sr.Cover)
if err != nil {
log.Printf("latest poll %q: fetch cover: %v", sr.Key(), err)
return
}
if err := p.Store.PutKaganeCover(imageID, body, contentType); err != nil {
if err := p.Store.PutCover(sr.Cover, body, contentType); err != nil {
log.Printf("latest poll %q: persist cover: %v", sr.Key(), err)
}
}