Files
Pete/config.example.toml
prosolis dbcb459908 Add server-side Piper read-aloud with voice picker
Reader read-aloud now streams neural WAV audio from a new POST /api/tts
endpoint that shells out to Piper, instead of the browser's Web Speech
voice. Each paragraph is synthesized on demand with the next one
prefetched during playback, keeping the existing highlight/scroll sync.

Voices are configured under [web.tts] (piper binary + voices_dir + a
labelled voice list) and exposed to the client as window.PETE_TTS; the
reader gets a Voice selector in the Aa menu, persisted per-device. Still
a signed-in-only perk and gated on auth.
2026-07-07 23:00:27 -07:00

275 lines
8.2 KiB
TOML

[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 user IDs allowed to run in-room commands like !post. Empty disables
# commands entirely (channel rooms may be public, so empty != "anyone").
admins = ["@you:matrix.example.org"]
[matrix.channels]
tech = "!techroomid:matrix.example.org"
politics = "!politicsroomid:matrix.example.org"
gaming = "!gamingroomid:matrix.example.org"
lego = "!legoroomid:matrix.example.org"
[posting]
enabled = true # master switch for auto-posting news to Matrix; false = web-only (commands still work)
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"
# OIDC subjects allowed to view the owner-facing source-health dashboard at
# /status (per-feed poll status, failures, content stats). Requires web.auth
# below. Empty = /status returns 404 for everyone. Find a user's subject in the
# server logs ("auth: user signed in" sub=...) after they sign in once.
admin_subs = []
# Optional OIDC sign-in (Authentik). When enabled, signed-in users get their
# preferences (hidden feeds, weather location, toggles) stored server-side keyed
# by their OIDC subject and synced across devices. Anonymous visitors keep using
# browser localStorage — the site stays public. If the provider is unreachable
# at startup, Pete logs a warning and serves anonymously rather than refusing to
# boot. Create an OAuth2/OIDC provider + application in Authentik, set the
# redirect URI to <base_url>/auth/callback, then fill these in.
[web.auth]
enabled = false
issuer = "https://authentik.parodia.dev/application/o/pete/"
client_id = "${PETE_OIDC_CLIENT_ID}"
client_secret = "${PETE_OIDC_CLIENT_SECRET}"
redirect_url = "https://news.parodia.dev/auth/callback"
# HMAC key that signs the session cookie. Generate with: openssl rand -hex 32
session_secret = "${PETE_SESSION_SECRET}"
# Optional Web Push digests. When enabled, signed-in users can opt in (from the
# feed-settings panel) to a periodic "N new stories" notification, delivered via
# a service worker so it also works when the site is installed as a PWA. Push is
# signed-in only, so it needs web.auth above; it does nothing otherwise.
# Generate the VAPID keypair once with: pete -genvapid
# then paste the two keys here (the private key is a secret — treat it like a
# password). subject identifies you to the push service (a mailto: or https: URL).
[web.push]
enabled = false
vapid_public_key = "${PETE_VAPID_PUBLIC_KEY}"
vapid_private_key = "${PETE_VAPID_PRIVATE_KEY}"
subject = "mailto:admin@parodia.dev"
# How often the sender wakes to look for new stories per subscriber (minutes).
interval_minutes = 360
# Smallest number of new (non-hidden) stories that triggers a digest, so a lone
# item doesn't ping everyone.
min_stories = 3
# Server-side neural read-aloud (Piper, https://github.com/rhasspy/piper).
# When enabled, the reader's "Listen" button streams real Piper voices instead
# of the browser's robotic Web Speech voice. Signed-in only, so it needs
# [web.auth] on too. Install the piper binary and one or more voice models
# (<id>.onnx + <id>.onnx.json) into voices_dir first.
[web.tts]
enabled = false
piper_bin = "/opt/piper/piper" # path to the piper executable
voices_dir = "/opt/piper/voices" # dir holding <id>.onnx (+ .onnx.json) models
default = "en_US-amy-medium" # voice id selected until the reader picks another
# List the voices to offer, in menu order. Omit the whole [[web.tts.voices]]
# list to auto-discover every *.onnx in voices_dir (labelled by filename).
[[web.tts.voices]]
id = "en_US-amy-medium"
label = "Amy (US, female)"
[[web.tts.voices]]
id = "en_US-ryan-high"
label = "Ryan (US, male, HQ)"
# 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.
# Optional: language = "en" drops feed items whose per-item <language> tag is
# set and doesn't match (prefix). Useful for multilingual feeds like Politico
# Europe that publish the same story in en / fr / de side-by-side. Items with
# no language tag pass through unchanged.
[[sources]]
name = "The Guardian — World"
feed_url = "https://www.theguardian.com/world/rss"
tier = 1
poll_interval_minutes = 20
direct_route = "politics"
enabled = true
# Optional: override the User-Agent for this feed only. Leave unset to use
# Pete's honest bot UA; set a browser string for feeds whose WAF blocks bots.
# user_agent = "Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0"
[[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
[[sources]]
name = "Pitchfork"
feed_url = "https://pitchfork.com/feed/feed-news/rss"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "The Quietus"
feed_url = "https://thequietus.com/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "Consequence"
feed_url = "https://consequence.net/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "Brooklyn Vegan"
feed_url = "https://www.brooklynvegan.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "NME — Music"
feed_url = "https://www.nme.com/news/music/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "BBC Newsround"
feed_url = "http://feeds.bbci.co.uk/newsround/rss.xml"
tier = 1
poll_interval_minutes = 30
direct_route = "kids"
enabled = true
[[sources]]
name = "DOGO News"
feed_url = "https://www.dogonews.com/articles.rss"
tier = 1
poll_interval_minutes = 60
direct_route = "kids"
enabled = true
[[sources]]
name = "Science News Explores"
feed_url = "https://www.snexplores.org/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "kids"
enabled = true
[[sources]]
name = "NASA for Students"
feed_url = "https://www.nasa.gov/rss/dyn/educationnews.rss"
tier = 1
poll_interval_minutes = 60
direct_route = "kids"
enabled = true
[[sources]]
name = "Post-Punk.com"
feed_url = "https://post-punk.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "music"
enabled = true
[[sources]]
name = "Naked Capitalism"
feed_url = "https://www.nakedcapitalism.com/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "Wolf Street"
feed_url = "https://wolfstreet.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "MarketBeat"
feed_url = "https://www.marketbeat.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "Seeking Alpha"
feed_url = "https://seekingalpha.com/feed.xml"
tier = 2
poll_interval_minutes = 60
direct_route = "finance"
enabled = true
[[sources]]
name = "Brickset"
feed_url = "https://brickset.com/feed"
tier = 1
poll_interval_minutes = 60
direct_route = "lego"
enabled = true
[[sources]]
name = "The Brick Fan"
feed_url = "https://www.thebrickfan.com/feed/"
tier = 1
poll_interval_minutes = 60
direct_route = "lego"
enabled = true