v0.3: dashboard local mode, openbridge tab reuse, tab activation fix
- dashboard.html: local dashboard.json mode (10s poll) alongside artifact db mode; clickable cards behind an http(s)-only safeUrl filter; meta charset; mode-aware freshness line - openbridge.py: localhost shim exposing open_link so dashboard clicks reuse the platform's warm tab instead of spawning one per click - cdp.py: Tab.activate(); get_or_open_tab activates reused tabs — a backgrounded tab never finishes rendering, which made YouTube's login check read a signed-in account as signed out - README/MANIFEST: corrected CDP port (9336, was documented as 9222), removed the false claim that the server writes dashboard.json, documented the reboot/VPS requirements - .gitignore: __pycache__/, *.pyc, state.json, Archived/; removed stale tracked pycache and state.json from index
This commit is contained in:
+103
-34
@@ -1,39 +1,50 @@
|
||||
# socialmag — Social Media MCP Server
|
||||
|
||||
**One tool, all platforms.** Post to Threads, Facebook, Instagram, TikTok (and YouTube when eligible) through a single MCP call. Built on CDP — no API keys, no rate limits, no approval process. Just a browser and a logged-in account.
|
||||
**One tool, all platforms.** Post to Threads, Facebook, Instagram, TikTok, Twitter/X (and YouTube when eligible) through a single MCP call. Built on CDP — no API keys, no rate limits, no approval process. Just a browser and a logged-in account.
|
||||
|
||||
## What It Does
|
||||
|
||||
**Write**
|
||||
- **`create_post`** — Post text/media to one or more platforms in one call
|
||||
- **`reply_tweet`** — Reply to a tweet found by search
|
||||
|
||||
**Read**
|
||||
- **`check_login`** — Verify which platforms are logged in
|
||||
- **`get_status`** — Last post results + open browser tabs
|
||||
- **`get_feed`** — Twitter timeline or search, with X operators (`from:`, `min_faves:`, …)
|
||||
- **`get_notifications`** — Platform activity (Threads)
|
||||
|
||||
**Navigate**
|
||||
- **`open_link`** — Send a platform's existing tab to a URL, instead of spawning a new one
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
Claude/MCP Client
|
||||
↓ stdio (JSON-RPC)
|
||||
socialmag server.py
|
||||
Claude/MCP Client dashboard.html (browser)
|
||||
↓ stdio (JSON-RPC) ↓ fetch (localhost)
|
||||
socialmag server.py ←────────────── openbridge.py :8322
|
||||
↓ CDP WebSocket
|
||||
Brave Browser (dedicated instance, port 9336)
|
||||
↓ real browser sessions
|
||||
Threads · Facebook · Instagram · TikTok · YouTube
|
||||
Threads · Facebook · Instagram · TikTok · YouTube · Twitter/X
|
||||
```
|
||||
|
||||
- **Tab reuse** — attaches to existing tabs, no cold loads
|
||||
- **Tab activation** — a reused tab is brought to the front, because a backgrounded tab is not guaranteed to have rendered (see *Journey*)
|
||||
- **Per-platform modules** — each platform has its own flow + selectors
|
||||
- **Sequential queue** — one platform at a time, no race conditions
|
||||
- **Error screenshots** — failed flows capture the page state to `/tmp/socialmag/`
|
||||
|
||||
## Platform Status
|
||||
|
||||
| Platform | Text | Media | Tab Reuse | Status |
|
||||
|----------|:----:|:-----:|:---------:|--------|
|
||||
| Threads | ✅ | 🔲 | ✅ | **Live** — inline composer |
|
||||
| Facebook | ✅ | 🔲 | ✅ | **Live** — dialog flow |
|
||||
| Instagram | — | ✅ | ✅ | **Live** — media required |
|
||||
| TikTok | — | ✅ | ✅ | **Live** — video/photo upload |
|
||||
| YouTube | 🔲 | 🔲 | ✅ | **Parked** — needs 500 subs |
|
||||
| Platform | Text | Media | Read | Status |
|
||||
|----------|:----:|:-----:|:----:|--------|
|
||||
| Threads | ✅ | 🔲 | ✅ | **Live** — inline composer + activity scraping |
|
||||
| Facebook | ✅ | 🔲 | — | **Live** — dialog flow |
|
||||
| Instagram | — | ✅ | — | **Live** — media required |
|
||||
| TikTok | — | ✅ | — | **Live** — video/photo upload |
|
||||
| Twitter/X | ✅ | 🔲 | ✅ | **Live** — post, reply, timeline + search |
|
||||
| YouTube | 🔲 | 🔲 | — | **Parked** — needs 500 subs |
|
||||
|
||||
✅ = tested live · 🔲 = coded but untested · — = not applicable
|
||||
|
||||
@@ -56,33 +67,86 @@ brave --remote-debugging-port=9336 \
|
||||
|
||||
Log into each platform manually once. socialmag reuses those sessions.
|
||||
|
||||
## Future Roadmap
|
||||
---
|
||||
|
||||
### v0.2 — Read Side
|
||||
- [ ] `get_notifications(platform)` — pull notifications via CDP scraping
|
||||
- [ ] `get_comments(platform, post_id?)` — read comments on recent posts
|
||||
- [ ] `get_engagement(platform)` — likes, shares, reach per post
|
||||
## Journey So Far
|
||||
|
||||
### v0.3 — Monitoring
|
||||
- [ ] Cron/loop polling — periodic checks, dump to JSON
|
||||
### v0.1 — 2026-09-10 · it posts
|
||||
Five platforms coded, four live. Threads, Facebook, Instagram, TikTok posting
|
||||
through a real logged-in browser. YouTube parked behind its 500-subscriber gate.
|
||||
First automated post from the house went out that night.
|
||||
|
||||
### v0.2 — 2026-09-11 (early hours) · it reads
|
||||
Twitter/X joined as a full module — post, reply, timeline, search.
|
||||
`get_notifications` landed for Threads. `open_link` built for warm-tab reuse.
|
||||
Pushed to a private GitHub repo.
|
||||
|
||||
**What the read side cost that the plan didn't predict:**
|
||||
- The Threads scraper was believed to already return a post `link`. It did not —
|
||||
it returned `{text}` only. Row detection had to be built from the live DOM. The
|
||||
rule that worked: *a row is an element that links somewhere, carries a
|
||||
relative-time marker, and contains no smaller element that does the same.*
|
||||
- A tweet's permalink is **not** "the first `/status/` href" — an article carries
|
||||
three (canonical, `/analytics`, `/photo/N`). It's the anchor wrapping `<time>`.
|
||||
|
||||
### v0.3 — 2026-09-11 (morning) · it shows, and it stops lying
|
||||
|
||||
**Dashboard, two modes.** Artifact mode reads four live `db` collections over
|
||||
`onSnapshot`. Local mode falls back to `dashboard.json` beside the page, polling
|
||||
every 10s, served off any plain HTTP server.
|
||||
|
||||
**Clicks stopped exploding.** `openbridge.py` exposes `open_link` over localhost so
|
||||
a dashboard card navigates the platform's existing tab. Verified: 8 tabs before two
|
||||
clicks, 8 after. It imports `tool_open_link` rather than reimplementing it.
|
||||
|
||||
**The YouTube false-negative.** `check_login` reported YouTube signed out while the
|
||||
account was signed in. Not a race, not an account problem — a **backgrounded tab
|
||||
never finishes rendering**. YouTube leaves its masthead as
|
||||
`#masthead-skeleton-icons` with an empty `#buttons`, so `#avatar-btn` — the element
|
||||
the check looks for — never mounts. Measured: still absent after 23 seconds, while
|
||||
`ytcfg.get('LOGGED_IN')` was `true` the whole time. Fixed by activating a reused tab
|
||||
(`Target.activateTarget`): `yt` now returns `True` 3/3, against `False` 3/3 before.
|
||||
|
||||
The general lesson, written down because it will bite again: **a DOM-render signal
|
||||
needs the foreground; a JS-state signal does not.**
|
||||
|
||||
**What was lost.** The v2 dashboard files were written to `/tmp/socialmag/` and did
|
||||
not survive a `/tmp` clear. Git held only the pre-v2 version. They were rebuilt from
|
||||
the field handback. Everything since is written into the repo.
|
||||
|
||||
---
|
||||
|
||||
## Roadmap
|
||||
|
||||
### Done
|
||||
- [x] `get_notifications(platform)` — Threads activity via CDP scraping *(v0.2)*
|
||||
- [x] `get_feed` — Twitter timeline + search *(v0.2)*
|
||||
- [x] `reply_tweet` *(v0.2)*
|
||||
- [x] Web dashboard — one view for all platform activity *(v0.3)*
|
||||
- [x] `open_link` + openbridge — tab reuse from the dashboard *(v0.3)*
|
||||
|
||||
### Next — monitoring
|
||||
- [ ] **A writer for `dashboard.json`** — nothing writes it today; it is hand-maintained
|
||||
- [ ] Periodic refresh (systemd timer over cron: journal + ordering + `Persistent=true`)
|
||||
- [ ] Units so `:8321` and `:8322` survive reboot
|
||||
- [ ] Unified notification feed across all platforms
|
||||
- [ ] Alert on new comments/DMs
|
||||
- [ ] `get_comments`, `get_engagement`
|
||||
|
||||
### v0.4 — Dashboard
|
||||
- [ ] Web dashboard — one view for all platform activity
|
||||
- [ ] Post scheduler — queue posts for specific times
|
||||
- [ ] Analytics — engagement trends, best posting times
|
||||
|
||||
### v0.5 — Media Pipeline
|
||||
- [ ] Threads media posting (image + video)
|
||||
- [ ] Facebook media posting
|
||||
- [ ] YouTube video upload (separate from community posts)
|
||||
- [ ] Auto-resize/format media per platform requirements
|
||||
⚠️ **The blocker isn't the code.** Interval refresh now steals browser focus by
|
||||
design, since tab activation is what makes login checks truthful. And Brave is not a
|
||||
service — after boot there is no CDP until it's opened by hand, so a refresher must
|
||||
treat "no browser" as a waiting state, not a failure. See README → *Surviving
|
||||
reboot*.
|
||||
|
||||
### Later
|
||||
- [ ] Post scheduler + content calendar
|
||||
- [ ] Analytics — engagement trends, best posting times
|
||||
- [ ] Media pipeline — Threads/Facebook media, YouTube video upload, per-platform resize
|
||||
- [ ] Multi-account support
|
||||
- [ ] Platform API integration (where available + worthwhile)
|
||||
- [ ] Content calendar
|
||||
- [ ] VPS deployment — **a different product, not a port.** Needs a headless browser
|
||||
with all six accounts logged in *there*, a display (unrendered pages give wrong
|
||||
answers — see the YouTube finding), and it puts real accounts at risk of being
|
||||
challenged from a datacentre IP
|
||||
- [ ] AI-assisted caption generation
|
||||
|
||||
## File Structure
|
||||
@@ -91,17 +155,22 @@ Log into each platform manually once. socialmag reuses those sessions.
|
||||
socialmag/
|
||||
├── __init__.py # package marker
|
||||
├── __main__.py # python -m socialmag entry
|
||||
├── server.py # MCP stdio server + tool handlers
|
||||
├── cdp.py # CDP WebSocket client
|
||||
├── server.py # MCP stdio server + 7 tool handlers
|
||||
├── cdp.py # CDP WebSocket client (CDPClient + Tab, incl. activate)
|
||||
├── selectors.py # all platform selectors in one file
|
||||
├── queue.py # sequential post queue
|
||||
├── openbridge.py # localhost HTTP shim — open_link for the dashboard
|
||||
├── dashboard.html # status/timeline viewer (artifact db or local JSON)
|
||||
├── dashboard.json # local-mode data — hand-maintained, nothing writes it
|
||||
├── platforms/
|
||||
│ ├── __init__.py # shared helpers (guard, require_media, etc.)
|
||||
│ ├── threads.py
|
||||
│ ├── facebook.py
|
||||
│ ├── instagram.py
|
||||
│ ├── tiktok.py
|
||||
│ ├── twitter.py
|
||||
│ └── youtube.py
|
||||
├── README.md # how to run it, and what it costs to keep running
|
||||
└── MANIFEST.md # this file
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user