app
RSS to Google Chat
Public Made by Adomby adom
Post new RSS feed items straight into your Google Chat spaces. A small always-on poller with a pinnable web editor: click the link in your space, add or edit feeds, Save — live within one poll cycle. Per-feed dedup handles both news feeds (post once) and status pages (re-post on updates).
← Commit history
README.mdadded+180@@ -0,0 +1,180 @@+# RSS to Google Chat++Post new RSS feed items straight into your Google Chat spaces — and manage+which feeds post from a web editor pinned right next to them.++A small always-on service on your own Adom container polls your feeds (default+every 30 minutes) and posts each newly published item to the Chat space you+choose. Nothing is ever posted twice, adding a feed never floods the space with+its backlog, and edits to the feed list go live without touching the server.++```+[Vendor News] *Product 2.0 released*+Tue, 21 Jul 2026 14:00:00 +0000+https://vendor.example.com/blog/product-2-0+```++- **Per-feed routing** — each feed carries its own Chat webhook, so one+ instance can post different feeds to different spaces.+- **Two dedup modes** — `guid` posts once per item (news, blogs, release+ notes); `guid+date` re-posts when an item updates (status pages: you see+ Investigating, then Resolved).+- **Pinnable editor** — a token-gated web page to add/edit/remove feeds.+ Pin its link in the space; changes are live within one poll cycle.+- **Self-contained** — runs on your container, supervised by a watchdog.+ No central service, and your webhooks never leave your box.++## Setup++You need: an Adom account with `adom-cli` (any dev container has it), and a+Google Chat space you can add webhooks to. About five minutes.++### 1. Create a small service container++From your dev container:++```bash+IMAGE_ID=$(adom-cli carbon containers images | jq -r '.[] | select(.name=="default-light") | .id')+adom-cli carbon user repo-create rss-gchat --private --description "RSS to Google Chat service"+REPO_ID=$(adom-cli carbon user repos | jq -r '.[] | select(.name=="rss-gchat") | .id')+adom-cli carbon containers create --image-id "$IMAGE_ID" --class small --repo-id "$REPO_ID"+```++Note the `slug` and the SSH username in the response, then give it ~20 seconds+to boot.++### 2. Install (one command, on the service container)++```bash+ssh -o StrictHostKeyChecking=accept-new <ssh-username>@adom.cloud \+ 'curl -fsSL https://wiki.adom.inc/download/adom/adom-rss-gchat/1.0.0/install.sh | bash'+```++No Rust, git, or other tooling needed on the container — the installer+downloads a prebuilt binary, sets up the watchdog, and prints your **editor+token** plus the exact next steps.++### 3. Expose the feed editor++Back on your dev container:++```bash+adom-cli carbon containers port-add <slug> --port 8842 --prefix rss-editor+```++Your editor URL is `https://<returned-hostname>/?token=<token-from-step-2>`.++### 4. Add your first feed++Open the editor URL. On first run it opens straight into the setup guide:++++It walks you through the Google Chat side:++1. In Google Chat, open the target space, click the **space name**, then+ **Apps & integrations → Webhooks → Add webhook**. Name it (e.g. "RSS") and+ copy the URL.+2. In the editor: **Add feed**, paste the feed URL and the webhook URL, pick a+ label and dedup mode, **Save**.+3. **Pin the editor URL to the space** (paste it in a message, hover, Pin) so+ feed edits are always one click away.++That's it. New items post from here on; the feed's existing backlog is marked+as read silently, so your space is never flooded.++## Managing feeds++++Everything happens in the editor — it validates input, backs up the previous+list before every save, and the service hot-reloads the file each poll cycle:++| You want to | Do |+|-------------|----|+| Add a feed | Add feed → URL + webhook + label → Save |+| Route a feed to another space | Give it that space's webhook URL |+| Status-page style re-posts | Set dedup to `guid+date` |+| Rename a feed's label | Edit label → Save (read-history is kept) |+| Stop one feed | Remove it → Save |+| Pause everything | Remove all feeds → Save (nothing is forgotten) |++A save is live within one poll cycle (default ≤30 min). Feeds are identified+by URL, so relabeling never re-posts old items.++## Security model++The editor URL is a **capability link**: the long random token in it is the+whole credential. Anyone with the link can view and edit your feed list — that+is the point of pinning it in the space, so treat the link like a password for+that space's membership. Two properties limit what a leaked link is worth:++- **Webhooks are write-only.** The editor never returns stored webhook URLs —+ it shows a masked form with the key/token stripped. A link holder can't+ harvest posting credentials. (Leave the field blank to keep a saved webhook;+ paste to replace. Changing a feed's URL requires re-entering its webhook.)+- **Changes are loud.** Every save posts a short "feed configuration changed"+ notice to all affected spaces — including a space a feed was just removed+ from. Silently rerouting a feed to another webhook is not possible.+ (`EDITOR_CHANGE_NOTICE=0` in `~/rss-gchat/env` disables this.)++Remaining trust: a link holder can still see **feed URLs** and add/remove+feeds. If a feed URL itself embeds a private credential (a personal wiki feed,+a secret calendar URL), anyone with the editor link can read that URL — so pin+the link only in spaces whose members you'd trust with those feeds, or keep+private-credential feeds in a solo space with its own deployment. Stronger+per-user identity ("Sign in with Adom") is on the roadmap for deployments where+the space audience and the feed audience differ.++- **Rotate a leaked link:** on the service container run+ `rm ~/rss-gchat/editor.token`, re-run the installer one-liner, and pin the+ freshly printed URL.+- Webhook URLs live only in `~/rss-gchat/feeds.json` (0700-permission dir,+ file readable only by your user) on your container.++## Operations++| Task | How |+|------|-----|+| Upgrade | Re-run the install one-liner — feeds, state, and token are preserved |+| Logs | `~/rss-gchat/bridge.log` and `~/rss-gchat/editor.log` |+| Health | `pidof adom-rss-gchat` (bridge) and `curl localhost:8842/healthz` (editor) |+| After a full container restart | `sudo service cron start` once; the watchdog revives both processes within 2 min |++The watchdog (cron, every 2 minutes) relaunches the bridge or editor if either+dies. State writes are atomic and recorded per successful post, so even a+crash mid-batch never causes a duplicate post.++## Config reference++Settings live in `~/rss-gchat/env` on the service container (all optional):++| Variable | Default | Meaning |+|----------|---------|---------|+| `GCHAT_WEBHOOK_URL` | unset | Deployment-wide default webhook; per-feed webhooks override it. Usually unset — feeds carry their own. |+| `POLL_INTERVAL_MINUTES` | `30` | Poll frequency (min 1) |+| `RSS_EDITOR_PORT` | `8842` | Editor port |+| `SEED_ON_FIRST_RUN` | `true` | Silently mark a new feed's backlog as read |+| `RUST_LOG` | `info` | Log verbosity |++`feeds.json` format (what the editor writes):++```json+{+ "feeds": [+ { "url": "https://vendor.example.com/rss", "label": "Vendor News",+ "webhook": "https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=..." },+ { "url": "https://status.example.com/history.rss", "label": "Vendor Status",+ "dedup": "guid+date",+ "webhook": "https://chat.googleapis.com/v1/spaces/.../messages?key=...&token=..." }+ ]+}+```++## Running it elsewhere++The binary is a plain Rust program with no Adom dependencies — it reads env+vars and a feeds file, and POSTs to webhooks. `cargo build --release`, then+run it under systemd, cron (`RUN_ONCE=1`), or Docker (see `Dockerfile` and+`deploy/` in the source repo). `DRY_RUN=1` previews what would post without+sending anything.
page.jsonadded+48@@ -0,0 +1,48 @@+{+ "slug": "adom-rss-gchat",+ "title": "RSS to Google Chat",+ "type": "app",+ "version": "1.0.0",+ "description": "Post new RSS feed items straight into your Google Chat spaces. A small always-on poller with a pinnable web editor: click the link in your space, add or edit feeds, Save — live within one poll cycle. Per-feed dedup handles both news feeds (post once) and status pages (re-post on updates).",+ "tags": [+ "rss",+ "google-chat",+ "notifications",+ "service"+ ],+ "dependencies": {},+ "discovery_triggers": [+ "post an rss feed to google chat",+ "rss to chat",+ "feed notifications in a chat space",+ "watch a blog and post new articles to chat",+ "status page updates in google chat",+ "set up adom-rss-gchat"+ ],+ "discovery_pitch": "When a user wants new RSS/Atom feed items (news, blogs, status pages) posted automatically into a Google Chat space, with a web editor to manage the feed list.",+ "sample_prompts": [+ {+ "label": "Vendor news into Chat",+ "prompt": "Post new articles from our vendor's news RSS feed into our team's Google Chat space"+ },+ {+ "label": "Status page alerts",+ "prompt": "I want incident updates from a status page's RSS feed to show up in Google Chat"+ }+ ],+ "scripts": {+ "install": "./install.sh",+ "uninstall": "./uninstall.sh"+ },+ "hero": {+ "type": "billboard",+ "headline": "RSS to Google Chat",+ "subhead": "New feed items, posted to your space. Edit feeds from a link pinned right next to them.",+ "screenshot": "docs/hero.png"+ },+ "org": "adom",+ "author": {+ "name": "Drew Owens",+ "email": "[email protected]"+ }+}