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
22 changes: 14 additions & 8 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,6 @@ min_positive_priority = 1
max_positive_priority = 720
bootstrap_priority = 10
bootstrap_ttl_hours = 24
bootstrap_label_content_types = ["vocaloid", "maybe_vocaloid"]
bootstrap_label_origin = "rule"
bootstrap_label_writers = ["classification_apply", "classification_trigger"]
bootstrap_tid_v2_allowlist = [2022, 2061]
processed_backfill_new_video_age_days = 7
collection_business_timezone = "Asia/Shanghai"

Expand All @@ -110,11 +106,21 @@ max_recommendation_depth = 1

[processing.filtering]
# Content filtering settings
type_id_whitelist = [] # Array of video type IDs to include (e.g., [28, 30, 130])
copyright_whitelist = [] # Array of copyright types to include (1: Original, 2: Repost)
content_blacklist = [] # Array of content blacklist keywords
content_whitelist = [] # Array of content whitelist keywords
pid_v2_whitelist = [] # Explicit pid_v2 values eligible for recommendation admission

[whitelist.video]
type_ids = [] # Video type IDs to include (e.g., [28, 30, 130])
copyright_types = [] # Copyright types to include (1: Original, 2: Repost)
content_keywords = [] # Keywords that bypass type and copyright checks

[whitelist.recommendation]
pid_v2 = [] # Explicit pid_v2 values eligible for recommendation admission

[whitelist.minute_bootstrap]
label_content_types = ["vocaloid", "maybe_vocaloid"]
label_origin = "rule"
label_writers = ["classification_apply", "classification_trigger"]
tid_v2 = [2022, 2061]

[export]
[export.mysql]
Expand Down
11 changes: 9 additions & 2 deletions docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,15 @@ Start by copying the example file:
cp config.toml.example config.toml
```

Then fill only the sections you use.
Then fill only the sections you use. On startup, legacy whitelist TOML keys are
moved to `[whitelist.*]` automatically. Migration keeps a temporary backup
and replaces the file only after validating the migrated values. A conflicting
destination, quoted/dotted key, symlinked file needing migration, or
non-comment multiline string requires a manual edit. Invalid TOML produces a
warning and falls back to environment variables. Run migration when no other
process or editor is writing `config.toml`. If a process stops during migration
and leaves `config.toml.migration-lock`, remove that lock after confirming no
instance is migrating.

## Sections

Expand All @@ -33,4 +41,3 @@ Each setting follows this order:
This means a value in `config.toml` overrides the matching environment variable.
Remove the TOML value or leave it empty when you want the environment variable to
take effect.

46 changes: 34 additions & 12 deletions docs/config/minute.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,6 @@ min_positive_priority = 1
max_positive_priority = 720
bootstrap_priority = 10
bootstrap_ttl_hours = 24
bootstrap_label_content_types = ["vocaloid", "maybe_vocaloid"]
bootstrap_label_origin = "rule"
bootstrap_label_writers = ["classification_apply", "classification_trigger"]
bootstrap_tid_v2_allowlist = [2022, 2061]
processed_backfill_new_video_age_days = 7
collection_business_timezone = "Asia/Shanghai"
```
Expand Down Expand Up @@ -121,21 +117,47 @@ rescheduling and increments
| `max_positive_priority` | `MINUTE_MAX_POSITIVE_PRIORITY` | `720` | Maximum positive interval in minutes. |
| `bootstrap_priority` | `MINUTE_BOOTSTRAP_PRIORITY` | `10` | Initial interval for newly tracked videos. |
| `bootstrap_ttl_hours` | `MINUTE_BOOTSTRAP_TTL_HOURS` | `24` | Maximum bootstrap window. |
| `bootstrap_label_content_types` | `MINUTE_BOOTSTRAP_LABEL_CONTENT_TYPES` | `["vocaloid", "maybe_vocaloid"]` | Label content types eligible for bootstrap. |
| `bootstrap_label_origin` | `MINUTE_BOOTSTRAP_LABEL_ORIGIN` | `rule` | Required label origin for bootstrap. |
| `bootstrap_label_writers` | `MINUTE_BOOTSTRAP_LABEL_WRITERS` | `["classification_apply", "classification_trigger"]` | Label writers eligible for bootstrap. |
| `bootstrap_tid_v2_allowlist` | `MINUTE_BOOTSTRAP_TID_V2_ALLOWLIST` | `[2022, 2061]` | Fallback type IDs eligible for bootstrap. |
| `processed_backfill_new_video_age_days` | `MINUTE_PROCESSED_BACKFILL_NEW_VIDEO_AGE_DAYS` | `7` | Age cutoff for processed-video bootstrap. |
| `collection_business_timezone` | `MINUTE_COLLECTION_BUSINESS_TIMEZONE` | `Asia/Shanghai` | Business date timezone for daily refresh. |

`target_delta_per_sample` is clamped into the
`target_delta_lower`..`target_delta_upper` range after parsing.
`MINUTE_ENABLED` accepts `1`, `true`, `yes`, and `on` as true values.

Use TOML for array settings such as `bootstrap_label_content_types`,
`bootstrap_label_writers`, and `bootstrap_tid_v2_allowlist`. Unlike
`BILIBILI_COOKIE_FILES` and the processing filter lists, these minute array
settings are not parsed from comma-separated environment strings.
## Bootstrap eligibility

Bootstrap eligibility is configured under `[whitelist.minute_bootstrap]`:

```toml
[whitelist.minute_bootstrap]
label_content_types = ["vocaloid", "maybe_vocaloid"]
label_origin = "rule"
label_writers = ["classification_apply", "classification_trigger"]
tid_v2 = [2022, 2061]
```

| TOML key | Environment variable | Default | Effect |
| --- | --- | --- | --- |
| `label_content_types` | `MINUTE_BOOTSTRAP_LABEL_CONTENT_TYPES` | `["vocaloid", "maybe_vocaloid"]` | Eligible formal label types. |
| `label_origin` | `MINUTE_BOOTSTRAP_LABEL_ORIGIN` | `rule` | Required label origin. |
| `label_writers` | `MINUTE_BOOTSTRAP_LABEL_WRITERS` | `["classification_apply", "classification_trigger"]` | Eligible label writers. |
| `tid_v2` | `MINUTE_BOOTSTRAP_TID_V2_ALLOWLIST` | `[2022, 2061]` | Fallback values when no formal label exists. |

On startup, the app automatically moves these former TOML keys in `config.toml`
to their current locations:

| Former TOML key | Current TOML key |
| --- | --- |
| `minute.bootstrap_label_content_types` | `whitelist.minute_bootstrap.label_content_types` |
| `minute.bootstrap_label_origin` | `whitelist.minute_bootstrap.label_origin` |
| `minute.bootstrap_label_writers` | `whitelist.minute_bootstrap.label_writers` |
| `minute.bootstrap_tid_v2_allowlist` | `whitelist.minute_bootstrap.tid_v2` |

Existing environment variable names remain valid. After changing
minute-bootstrap values, run `pnpm init-schema` against the database so stored
SQL function defaults use the new values, then restart the application. Without
schema initialization, calls that rely on the stored SQL defaults can continue
using the previously installed bootstrap values.

## Metrics

Expand Down
57 changes: 49 additions & 8 deletions docs/config/processing.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Processing Configuration

`[processing]` controls feature flags and filtering rules.
`[processing]` controls feature flags and the content blacklist. Video and
recommendation allowlists are configured under `[whitelist]`.

## Feature flags

Expand All @@ -25,17 +26,57 @@ max_recommendation_depth = 1

```toml
[processing.filtering]
type_id_whitelist = []
copyright_whitelist = []
content_blacklist = []
content_whitelist = []
```

| TOML key | Environment variable | Default | Meaning |
| --- | --- | --- | --- |
| `type_id_whitelist` | `TYPE_ID_WHITE_LIST` | `[]` | Type IDs to include. |
| `copyright_whitelist` | `COPYRIGHT_WHITE_LIST` | `[]` | Copyright types to include. |
| `content_blacklist` | `CONTENT_BLACK_LIST` | `[]` | Keywords to exclude. |
| `content_whitelist` | `CONTENT_WHITE_LIST` | `[]` | Keywords to include. |

For environment variables, list values are comma-separated.
For environment variables, list values are comma-separated. The blacklist is
applied after the video allowlists; matching an allowlist never bypasses it.

## Video and recommendation allowlists

Video and recommendation allowlists use these TOML keys. A TOML value takes
precedence over the corresponding environment variable; environment list values
are comma-separated. Empty TOML arrays explicitly disable a list.

```toml
[whitelist.video]
type_ids = []
copyright_types = []
content_keywords = []

[whitelist.recommendation]
pid_v2 = []
```

| TOML key | Environment variable | Default | Effect |
| --- | --- | --- | --- |
| `whitelist.video.type_ids` | `TYPE_ID_WHITE_LIST` | `[]` | Video types admitted by the type check. |
| `whitelist.video.copyright_types` | `COPYRIGHT_WHITE_LIST` | `[]` | Copyright types admitted by the copyright check. |
| `whitelist.video.content_keywords` | `CONTENT_WHITE_LIST` | `[]` | Keywords that bypass the type and copyright checks. Blank keywords are invalid. |
| `whitelist.recommendation.pid_v2` | `UPDATE_INFO_PID_V2_WHITELIST` | `[]` | Related videos admitted by recommendation collection and `--update-info`. |

An empty video type or copyright list disables that check. A matching content
keyword bypasses those checks, while `processing.filtering.content_blacklist`
still excludes matching videos. Recommendation admission requires a listed
`pid_v2`; `--update-info --pid-v2-whitelist` overrides the configured list for
that run.

On startup, the app automatically moves these former TOML keys in `config.toml`
to their current locations:

| Former TOML key | Current TOML key |
| --- | --- |
| `processing.filtering.type_id_whitelist` | `whitelist.video.type_ids` |
| `processing.filtering.copyright_whitelist` | `whitelist.video.copyright_types` |
| `processing.filtering.content_whitelist` | `whitelist.video.content_keywords` |
| `processing.filtering.pid_v2_whitelist` | `whitelist.recommendation.pid_v2` |

The migration preserves other configuration text and comments. It stops without
changing the file if a destination key already exists or the old key uses quoted
or dotted TOML syntax. Move those keys manually before restarting. Keep
`processing.filtering.content_blacklist` where it is. Existing environment
variable names remain valid.
28 changes: 17 additions & 11 deletions src/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFileSync } from "node:fs";
import { existsSync } from "node:fs";
import { resolve } from "node:path";
import { parse as parseToml } from "smol-toml";
import { z } from "zod";
import { ConfigTomlParseError, loadConfigToml } from "./migrate-whitelist";
import {
applicationSchema,
bilibiliSchema,
Expand All @@ -16,6 +16,7 @@ import {
createRepairConfig,
createServerConfig,
createSubtitleConfig,
createWhitelistConfig,
databaseSchema,
exportSchema,
metricsSchema,
Expand All @@ -25,18 +26,21 @@ import {
repairSchema,
serverSchema,
subtitleSchema,
whitelistSchema,
} from "./schemas";

const configPath = resolve(process.cwd(), "config.toml");
let tomlData: unknown = {};
try {
const configPath = resolve(process.cwd(), "config.toml");
const tomlString = readFileSync(configPath, "utf-8");
tomlData = parseToml(tomlString);
} catch (error) {
console.warn(
"Warning: config.toml not found or invalid. Using environment variables as fallback.",
);
console.warn("Actual error:", error);
if (existsSync(configPath)) {
try {
tomlData = loadConfigToml(configPath);
} catch (error) {
if (!(error instanceof ConfigTomlParseError)) throw error;
console.warn(
"Warning: config.toml not found or invalid. Using environment variables as fallback.",
);
console.warn("Actual error:", error.cause);
}
}

// Helper function to get configuration value from TOML or environment variable
Expand Down Expand Up @@ -90,6 +94,7 @@ const configSchema = z.object({
server: serverSchema,
subtitle: subtitleSchema,
notifications: notificationsSchema,
whitelist: whitelistSchema,
});

export const config = configSchema.parse({
Expand All @@ -104,4 +109,5 @@ export const config = configSchema.parse({
server: createServerConfig(getConfigValue),
subtitle: createSubtitleConfig(getConfigValue),
notifications: createNotificationsConfig(getConfigValue),
whitelist: createWhitelistConfig(getConfigValue),
});
Loading
Loading