diff --git a/.gitignore b/.gitignore index 3b2ddc1..7a45da6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ data/ .env pete config.yaml +config.toml node_modules/ diff --git a/Dockerfile b/Dockerfile index 5ff9374..ec89ca2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,4 +20,4 @@ WORKDIR /app COPY --from=builder /app/pete . VOLUME /app/data EXPOSE 8080 -CMD ["./pete", "-config", "/app/config.yaml"] +CMD ["./pete", "-config", "/app/config.toml"] diff --git a/README.md b/README.md index b480655..cc0bbf4 100644 --- a/README.md +++ b/README.md @@ -39,17 +39,17 @@ git clone && cd pete go build -tags goolm . # Create config from example -cp config.example.yaml config.yaml -# Edit config.yaml — fill in Matrix credentials, room IDs, and a direct_route for every source +cp config.example.yaml config.toml +# Edit config.toml — fill in Matrix credentials, room IDs, and a direct_route for every source # First run: seed current feed items as seen (prevents flood) -./pete -config config.yaml -seed +./pete -config config.toml -seed # Post one story to verify the pipeline -./pete -config config.yaml -test +./pete -config config.toml -test # Run -./pete -config config.yaml +./pete -config config.toml ``` ## Configuration @@ -62,7 +62,7 @@ See `config.example.yaml` for the full structure. Key sections: - `sources` — RSS feeds with tier, polling interval, and **required** `direct_route` (must match a key in `matrix.channels`) - `web` — read-only HTTP UI (enabled toggle, listen address, site title, public base URL) -Environment variables can be referenced with `${VAR}` syntax in the YAML. +Environment variables can be referenced with `${VAR}` syntax in the TOML. ### Web UI @@ -91,7 +91,7 @@ With a 4-hour cadence Pete will post at most 6 stories/day, so set `posting.dail | Flag | Description | |---|---| -| `-config ` | Path to config file (default: `config.yaml`) | +| `-config ` | Path to config file (default: `config.toml`) | | `-seed` | Ingest all current feed items as seen without posting, then exit | | `-test` | Post one story to verify the full pipeline, then exit | diff --git a/config.example.toml b/config.example.toml new file mode 100644 index 0000000..60b3e32 --- /dev/null +++ b/config.example.toml @@ -0,0 +1,75 @@ +[matrix] +homeserver = "https://matrix.example.org" +user_id = "@pete:matrix.example.org" +password = "${PETE_PASSWORD}" +pickle_key = "${PETE_PICKLE_KEY}" +display_name = "Pete" +data_dir = "./data" +admin_room = "!adminroomid:matrix.example.org" + +[matrix.channels] +tech = "!techroomid:matrix.example.org" +politics = "!politicsroomid:matrix.example.org" +gaming = "!gamingroomid:matrix.example.org" + +[posting] +min_interval_seconds = 300 +burst_cap_count = 3 +burst_cap_window_seconds = 1800 +daily_cap_total = 5 # hard global cap across ALL channels (rolling 24h); 0 disables + +[posting.round_robin] +enabled = false # when true, replaces immediate posting with paced rotation +interval_hours = 4 # one story per N hours, cycling through channels in sorted order + +[storage] +db_path = "./data/pete.db" +recent_window_hours = 24 + +[web] +enabled = true +listen_addr = ":8080" +site_title = "Pete" +base_url = "https://news.parodia.dev" + +# Every enabled source MUST set direct_route to a key from [matrix.channels] above. +# There is no automatic classification — Pete posts each story to its configured channel. +[[sources]] +name = "The Guardian — World" +feed_url = "https://www.theguardian.com/world/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "politics" +enabled = true + +[[sources]] +name = "The Guardian — Politics" +feed_url = "https://www.theguardian.com/politics/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "politics" +enabled = true + +[[sources]] +name = "The Guardian — US News" +feed_url = "https://www.theguardian.com/us-news/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "politics" +enabled = true + +[[sources]] +name = "Ars Technica" +feed_url = "https://feeds.arstechnica.com/arstechnica/index" +tier = 1 +poll_interval_minutes = 20 +direct_route = "tech" +enabled = true + +[[sources]] +name = "Time Extension" +feed_url = "https://www.timeextension.com/feeds/news" +tier = 1 +poll_interval_minutes = 20 +direct_route = "gaming" +enabled = true diff --git a/config.example.yaml b/config.example.yaml deleted file mode 100644 index b707d0f..0000000 --- a/config.example.yaml +++ /dev/null @@ -1,69 +0,0 @@ -matrix: - homeserver: "https://matrix.example.org" - user_id: "@pete:matrix.example.org" - password: "${PETE_PASSWORD}" - pickle_key: "${PETE_PICKLE_KEY}" - display_name: "Pete" - data_dir: "./data" - admin_room: "!adminroomid:matrix.example.org" - channels: - tech: "!techroomid:matrix.example.org" - politics: "!politicsroomid:matrix.example.org" - gaming: "!gamingroomid:matrix.example.org" - -posting: - min_interval_seconds: 300 - burst_cap_count: 3 - burst_cap_window_seconds: 1800 - daily_cap_total: 5 # hard global cap across ALL channels (rolling 24h); 0 disables - round_robin: - enabled: false # when true, replaces immediate posting with paced rotation - interval_hours: 4 # one story per N hours, cycling through channels in sorted order - -storage: - db_path: "./data/pete.db" - recent_window_hours: 24 - -web: - enabled: true - listen_addr: ":8080" - site_title: "Pete" - base_url: "https://news.parodia.dev" - -# Every enabled source MUST set direct_route to a key from matrix.channels above. -# There is no automatic classification — Pete posts each story to its configured channel. -sources: - - name: "The Guardian — World" - feed_url: "https://www.theguardian.com/world/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "politics" - enabled: true - - - name: "The Guardian — Politics" - feed_url: "https://www.theguardian.com/politics/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "politics" - enabled: true - - - name: "The Guardian — US News" - feed_url: "https://www.theguardian.com/us-news/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "politics" - enabled: true - - - name: "Ars Technica" - feed_url: "https://feeds.arstechnica.com/arstechnica/index" - tier: 1 - poll_interval_minutes: 20 - direct_route: "tech" - enabled: true - - - name: "Time Extension" - feed_url: "https://www.timeextension.com/feeds/news" - tier: 1 - poll_interval_minutes: 20 - direct_route: "gaming" - enabled: true diff --git a/docker-compose.yml b/docker-compose.yml index a30d82d..08b4a84 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,4 +8,4 @@ services: - "${PETE_WEB_PORT:-8080}:8080" volumes: - ./data:/app/data - - ./config.yaml:/app/config.yaml:ro + - ./config.toml:/app/config.toml:ro diff --git a/go.mod b/go.mod index d73b160..3ce0076 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,9 @@ module pete go 1.25.0 require ( + github.com/BurntSushi/toml v1.6.0 github.com/PuerkitoBio/goquery v1.12.0 github.com/mmcdole/gofeed v1.3.0 - gopkg.in/yaml.v3 v3.0.1 maunium.net/go/mautrix v0.28.0 modernc.org/sqlite v1.50.1 ) diff --git a/go.sum b/go.sum index 7d0c7fb..c68a026 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= github.com/PuerkitoBio/goquery v1.12.0 h1:pAcL4g3WRXekcB9AU/y1mbKez2dbY2AajVhtkO8RIBo= @@ -143,8 +145,6 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= maunium.net/go/mautrix v0.28.0 h1:vBakLzf8MAdfED3NzAKiMeKQbc3AQ4EAS03NC+TVMXQ= diff --git a/internal/config/config.go b/internal/config/config.go index cc244b6..adc0933 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,70 +6,70 @@ import ( "os" "regexp" - "gopkg.in/yaml.v3" + "github.com/BurntSushi/toml" ) // envBracketRe matches only ${VAR} style env references, not bare $VAR. var envBracketRe = regexp.MustCompile(`\$\{([^}]+)\}`) type Config struct { - Matrix MatrixConfig `yaml:"matrix"` - Posting PostingConfig `yaml:"posting"` - Storage StorageConfig `yaml:"storage"` - Web WebConfig `yaml:"web"` - Sources []SourceConfig `yaml:"sources"` + Matrix MatrixConfig `toml:"matrix"` + Posting PostingConfig `toml:"posting"` + Storage StorageConfig `toml:"storage"` + Web WebConfig `toml:"web"` + Sources []SourceConfig `toml:"sources"` } // WebConfig controls the read-only HTTP interface (news.parodia.dev style). type WebConfig struct { - Enabled bool `yaml:"enabled"` - ListenAddr string `yaml:"listen_addr"` // e.g. ":8080" or "127.0.0.1:8080" - SiteTitle string `yaml:"site_title"` // display name in the header - BaseURL string `yaml:"base_url"` // public URL (used in metadata only) + Enabled bool `toml:"enabled"` + ListenAddr string `toml:"listen_addr"` // e.g. ":8080" or "127.0.0.1:8080" + SiteTitle string `toml:"site_title"` // display name in the header + BaseURL string `toml:"base_url"` // public URL (used in metadata only) } type MatrixConfig struct { - Homeserver string `yaml:"homeserver"` - UserID string `yaml:"user_id"` - Password string `yaml:"password"` - PickleKey string `yaml:"pickle_key"` - DisplayName string `yaml:"display_name"` - DataDir string `yaml:"data_dir"` - AdminRoom string `yaml:"admin_room"` - Channels map[string]string `yaml:"channels"` + Homeserver string `toml:"homeserver"` + UserID string `toml:"user_id"` + Password string `toml:"password"` + PickleKey string `toml:"pickle_key"` + DisplayName string `toml:"display_name"` + DataDir string `toml:"data_dir"` + AdminRoom string `toml:"admin_room"` + Channels map[string]string `toml:"channels"` } type PostingConfig struct { - MinIntervalSeconds int `yaml:"min_interval_seconds"` - BurstCapCount int `yaml:"burst_cap_count"` - BurstCapWindowSeconds int `yaml:"burst_cap_window_seconds"` - DedupCooldownHours int `yaml:"dedup_cooldown_hours"` + MinIntervalSeconds int `toml:"min_interval_seconds"` + BurstCapCount int `toml:"burst_cap_count"` + BurstCapWindowSeconds int `toml:"burst_cap_window_seconds"` + DedupCooldownHours int `toml:"dedup_cooldown_hours"` // DailyCapTotal is the hard global cap on posts across ALL channels in a // rolling 24-hour window. 0 disables the cap. - DailyCapTotal int `yaml:"daily_cap_total"` - RoundRobin RoundRobinConfig `yaml:"round_robin"` + DailyCapTotal int `toml:"daily_cap_total"` + RoundRobin RoundRobinConfig `toml:"round_robin"` } // RoundRobinConfig switches Pete from immediate-on-classify posting to a // paced rotation: one story per IntervalHours, picking the next channel in // rotation order that has a postable story (skip-and-advance, newest-first). type RoundRobinConfig struct { - Enabled bool `yaml:"enabled"` - IntervalHours int `yaml:"interval_hours"` + Enabled bool `toml:"enabled"` + IntervalHours int `toml:"interval_hours"` } type StorageConfig struct { - DBPath string `yaml:"db_path"` - RecentWindowHours int `yaml:"recent_window_hours"` + DBPath string `toml:"db_path"` + RecentWindowHours int `toml:"recent_window_hours"` } type SourceConfig struct { - Name string `yaml:"name"` - FeedURL string `yaml:"feed_url"` - Tier int `yaml:"tier"` - PollIntervalMinutes int `yaml:"poll_interval_minutes"` - DirectRoute string `yaml:"direct_route"` - Enabled bool `yaml:"enabled"` + Name string `toml:"name"` + FeedURL string `toml:"feed_url"` + Tier int `toml:"tier"` + PollIntervalMinutes int `toml:"poll_interval_minutes"` + DirectRoute string `toml:"direct_route"` + Enabled bool `toml:"enabled"` } func Load(path string) (*Config, error) { @@ -90,7 +90,7 @@ func Load(path string) (*Config, error) { }) var cfg Config - if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil { + if _, err := toml.Decode(expanded, &cfg); err != nil { return nil, fmt.Errorf("parse config: %w", err) } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 46114eb..e76c49a 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -8,28 +8,29 @@ import ( // validMatrix is a reusable matrix config block for tests. const validMatrix = ` -matrix: - homeserver: "https://matrix.example.org" - user_id: "@pete:example.org" - password: "testpass" - channels: - tech: "!tech:example.org" - politics: "!politics:example.org" +[matrix] +homeserver = "https://matrix.example.org" +user_id = "@pete:example.org" +password = "testpass" +[matrix.channels] +tech = "!tech:example.org" +politics = "!politics:example.org" ` func TestLoadValid(t *testing.T) { - yaml := validMatrix + ` -storage: - db_path: "/tmp/test.db" -sources: - - name: "Test Source" - feed_url: "https://example.com/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "tech" - enabled: true + toml := validMatrix + ` +[storage] +db_path = "/tmp/test.db" + +[[sources]] +name = "Test Source" +feed_url = "https://example.com/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "tech" +enabled = true ` - cfg := loadFromString(t, yaml) + cfg := loadFromString(t, toml) if cfg.Matrix.Homeserver != "https://matrix.example.org" { t.Errorf("homeserver = %q", cfg.Matrix.Homeserver) @@ -51,18 +52,18 @@ sources: func TestEnvVarExpansion(t *testing.T) { t.Setenv("TEST_PETE_PASS", "secret_pass_123") - yaml := ` -matrix: - homeserver: "https://matrix.example.org" - user_id: "@pete:example.org" - password: "${TEST_PETE_PASS}" - channels: - tech: "!tech:example.org" -storage: - db_path: "/tmp/test.db" -sources: [] + toml := ` +[matrix] +homeserver = "https://matrix.example.org" +user_id = "@pete:example.org" +password = "${TEST_PETE_PASS}" +[matrix.channels] +tech = "!tech:example.org" + +[storage] +db_path = "/tmp/test.db" ` - cfg := loadFromString(t, yaml) + cfg := loadFromString(t, toml) if cfg.Matrix.Password != "secret_pass_123" { t.Errorf("password = %q, want secret_pass_123", cfg.Matrix.Password) @@ -70,18 +71,19 @@ sources: [] } func TestDefaults(t *testing.T) { - yaml := validMatrix + ` -storage: - db_path: "/tmp/test.db" -sources: - - name: "S" - feed_url: "https://example.com/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "tech" - enabled: true + toml := validMatrix + ` +[storage] +db_path = "/tmp/test.db" + +[[sources]] +name = "S" +feed_url = "https://example.com/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "tech" +enabled = true ` - cfg := loadFromString(t, yaml) + cfg := loadFromString(t, toml) if cfg.Matrix.DataDir != "./data" { t.Errorf("data_dir default = %q, want ./data", cfg.Matrix.DataDir) @@ -106,109 +108,125 @@ sources: func TestValidationErrors(t *testing.T) { tests := []struct { name string - yaml string + toml string }{ {"missing homeserver", ` -matrix: - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} +[matrix] +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" `}, {"missing user_id", ` -matrix: - homeserver: "https://h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} +[matrix] +homeserver = "https://h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" `}, {"missing password", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} +[matrix] +homeserver = "https://h" +user_id = "@p:h" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" `}, {"no channels", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" -storage: {db_path: "/tmp/t.db"} +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[storage] +db_path = "/tmp/t.db" `}, {"missing db_path", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {} +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] `}, {"invalid tier", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} -sources: - - name: "S" - feed_url: "https://e.com/rss" - tier: 5 - poll_interval_minutes: 20 - direct_route: "tech" - enabled: true +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" +[[sources]] +name = "S" +feed_url = "https://e.com/rss" +tier = 5 +poll_interval_minutes = 20 +direct_route = "tech" +enabled = true `}, {"missing source name", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} -sources: - - feed_url: "https://e.com/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "tech" - enabled: true +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" +[[sources]] +feed_url = "https://e.com/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "tech" +enabled = true `}, {"enabled source missing direct_route", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} -sources: - - name: "S" - feed_url: "https://e.com/rss" - tier: 1 - poll_interval_minutes: 20 - enabled: true +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" +[[sources]] +name = "S" +feed_url = "https://e.com/rss" +tier = 1 +poll_interval_minutes = 20 +enabled = true `}, {"direct_route not in channels", ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} -sources: - - name: "S" - feed_url: "https://e.com/rss" - tier: 1 - poll_interval_minutes: 20 - direct_route: "gaming" - enabled: true +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" +[[sources]] +name = "S" +feed_url = "https://e.com/rss" +tier = 1 +poll_interval_minutes = 20 +direct_route = "gaming" +enabled = true `}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { dir := t.TempDir() - path := filepath.Join(dir, "config.yaml") - os.WriteFile(path, []byte(tt.yaml), 0o644) + path := filepath.Join(dir, "config.toml") + os.WriteFile(path, []byte(tt.toml), 0o644) _, err := Load(path) if err == nil { t.Error("expected validation error, got nil") @@ -219,31 +237,33 @@ sources: func TestDisabledSourceSkipsDirectRouteCheck(t *testing.T) { // A disabled source without direct_route must not fail validation. - yaml := ` -matrix: - homeserver: "https://h" - user_id: "@p:h" - password: "pw" - channels: {tech: "!t:e"} -storage: {db_path: "/tmp/t.db"} -sources: - - name: "Off" - feed_url: "https://e.com/rss" - tier: 1 - poll_interval_minutes: 20 - enabled: false + toml := ` +[matrix] +homeserver = "https://h" +user_id = "@p:h" +password = "pw" +[matrix.channels] +tech = "!t:e" +[storage] +db_path = "/tmp/t.db" +[[sources]] +name = "Off" +feed_url = "https://e.com/rss" +tier = 1 +poll_interval_minutes = 20 +enabled = false ` - cfg := loadFromString(t, yaml) + cfg := loadFromString(t, toml) if cfg.Sources[0].DirectRoute != "" { t.Errorf("expected empty direct_route, got %q", cfg.Sources[0].DirectRoute) } } -func loadFromString(t *testing.T, yamlContent string) *Config { +func loadFromString(t *testing.T, tomlContent string) *Config { t.Helper() dir := t.TempDir() - path := filepath.Join(dir, "config.yaml") - if err := os.WriteFile(path, []byte(yamlContent), 0o644); err != nil { + path := filepath.Join(dir, "config.toml") + if err := os.WriteFile(path, []byte(tomlContent), 0o644); err != nil { t.Fatal(err) } cfg, err := Load(path) diff --git a/main.go b/main.go index 35ae4e5..f564d37 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ import ( ) func main() { - configPath := flag.String("config", "config.yaml", "path to config file") + configPath := flag.String("config", "config.toml", "path to config file") seed := flag.Bool("seed", false, "ingest current feed items as seen without posting, then exit") test := flag.Bool("test", false, "post one story to verify the full pipeline, then exit") testSource := flag.String("test-source", "", "source name to use for -test (default: first enabled)")