diff --git a/.env.example b/.env.example index 301cf65..130e83e 100644 --- a/.env.example +++ b/.env.example @@ -46,3 +46,8 @@ TAPIR_SECRETS_FILE= # --- run loop ------------------------------------------------------------- # Empty/0 = single pass. Set (e.g. 15m) to poll on that cadence. TAPIR_POLL_INTERVAL= +# How long to wait before re-fetching a transcript that returned HTTP 429 +# (rate_limited). Inside the window the video is skipped without hitting the +# caption endpoint; after it expires the video is retried. 0 = always retry. +# Go duration; default 1h. +TAPIR_FETCH_BACKOFF= diff --git a/internal/config/config.go b/internal/config/config.go index 527d433..8895bfe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -59,6 +59,12 @@ type Config struct { // PollInterval, when > 0, makes `run` loop on that cadence; 0 means run once. PollInterval time.Duration + // FetchBackoff is how long the run loop waits before re-fetching a transcript + // that previously returned HTTP 429 (rate_limited). Inside the window the video + // is skipped without hitting the caption endpoint, saving requests; after it + // expires the video is retried. Zero means "always retry" (no backoff). + FetchBackoff time.Duration + // HTTPAddr is the listen address for `tapir serve` (the Stage-0 web UI). HTTPAddr string @@ -85,6 +91,7 @@ const ( defaultYTConnectRedirectURL = "https://tapir.d-ma.be/oauth/youtube/callback" defaultOAuthRedirectAddr = "localhost:8080" defaultHTTPAddr = ":8080" + defaultFetchBackoff = time.Hour ) // Load reads the environment into a Config, applying defaults. It does not @@ -124,6 +131,12 @@ func Load() (Config, error) { } c.PollInterval = interval + backoff, err := durationOr("TAPIR_FETCH_BACKOFF", defaultFetchBackoff) + if err != nil { + return Config{}, err + } + c.FetchBackoff = backoff + return c, nil } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index c8fce0d..59e0849 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -45,6 +45,9 @@ func TestLoad_AppliesDefaults(t *testing.T) { if c.PollInterval != 0 { t.Errorf("PollInterval = %v, want 0 (run once)", c.PollInterval) } + if c.FetchBackoff != defaultFetchBackoff { + t.Errorf("FetchBackoff = %v, want default %v", c.FetchBackoff, defaultFetchBackoff) + } } func TestLoad_ParsesValues(t *testing.T) { @@ -56,6 +59,7 @@ func TestLoad_ParsesValues(t *testing.T) { "TAPIR_SUMMARIZER_TIMEOUT": "90s", "TAPIR_DB_DSN": "postgres://x", "TAPIR_POLL_INTERVAL": "10m", + "TAPIR_FETCH_BACKOFF": "30m", }) c, err := Load() @@ -77,6 +81,9 @@ func TestLoad_ParsesValues(t *testing.T) { if c.PollInterval != 10*time.Minute { t.Errorf("PollInterval = %v, want 10m", c.PollInterval) } + if c.FetchBackoff != 30*time.Minute { + t.Errorf("FetchBackoff = %v, want 30m", c.FetchBackoff) + } } func TestLoad_RejectsBadDuration(t *testing.T) {