← Commit history

Initial commit: app/adom-rss-gchat

Drew Owens ·294485a5fa ·3mo ago
2 files changed +228
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:++![First-run setup guide](docs/editor-setup.png)++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++![The feed editor with two feeds configured](docs/editor-feeds.png)++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]"+  }+}\ No newline at end of file