solar/docs/BROWSER_API.md

7.7 KiB
Raw Permalink Blame History

Browser API (Kasm Chrome) degelas stack

The degelas stack runs a Kasm Chrome container so you can keep a browser session open (e.g. logged into X or Instagram) and control it via the backend API.

Architecture

  • chrome (kasmweb/chrome:1.18.0-rolling-daily): runs Chrome with noVNC (port 6901) and CDP (port 9222, internal).
  • backend: FastAPI exposes /browser/* endpoints; uses Playwright to connect to Chrome via CDP and drive the session.

No frontend or other backend code was changed; only the Chrome service and a new router were added.

Accessing the browser session (noVNC)

Subdomain (recommended): Open https://browser.degelas.be — requires DNS + SSL for browser.degelas.be (see "Subdomain setup" below).

Direct port: Open https://<your-server>:6901 in a browser (or use SSH port-forward if you dont expose 6901).

  • Log in: user kasm_user, password from env CHROME_VNC_PW (default degelas).
  • Use Chrome as usual: log into X, Instagram, etc. Leave this tab/window open.

Subdomain setup (browser.degelas.be)

  1. DNS — Add A or CNAME: browser.degelas.be → your server IP (same as degelas.be).
  2. SSL — Request a certificate for this subdomain only (uses the fullstack_degelas certbot stack):
    cd /root/fullstack_degelas && ./scripts/request-cert-browser-subdomain.sh
    
    Cert is stored under /etc/letsencrypt/live/browser.degelas.be/; the script reloads nginx.
  3. Chrome on proxy — The degelas compose already attaches chrome to the proxy network so nginx can reach degelas-chrome:6901.
  4. Nginx — Config is in fullstack_degelas/nginx/conf.d/browser.degelas.be.conf. Reload nginx after DNS and cert are set.

API endpoints (via degelas backend)

Base path: /api/browser (nginx forwards /api/ to the backend).

Method Path Description
GET /api/browser/status Check if Chrome CDP is reachable and return current page URL.
POST /api/browser/navigate Body: {"url": "https://..."} navigate the current tab.
POST /api/browser/type Body: {"selector": "css", "text": "..."} type into an element.
POST /api/browser/click Body: {"selector": "css"} click an element.
POST /api/browser/execute Body: {"expression": "document.title"} run JS in page, get result.
POST /api/browser/post Body: {"platform": "x"|"instagram", "text": "..."} best-effort post (see below).

Posting to X or Instagram

  1. In the noVNC session, open X or Instagram and log in. For /post you can either:

    • Leave the tab on the X/Instagram compose screen (e.g. home with compose box visible), then call POST /api/browser/post with platform: "x" or "instagram" and text: "Your post text".
    • Or drive the flow yourself: POST /api/browser/navigate to the compose URL, then /type and /click with selectors you maintain.
  2. The /post endpoint uses best-effort selectors (e.g. data-testid="tweetTextarea_0" for X). These can break when the sites change; for production, prefer /navigate, /type, and /click with your own selectors.

Deployment notes (live)

  • Chrome VNC password: set CHROME_VNC_PW in the environment (or in a .env next to docker-compose.yml) so the noVNC session is not the default password.
  • Port 6901: only published on the host; restrict access (firewall or nginx with auth) if the server is public.
  • CDP (9222): not published to the host; only the backend on the degelas network uses it.
  • Chrome CDP binding: Recent Chromium (M113+) may ignore --remote-debugging-address=0.0.0.0 and bind to 127.0.0.1. If GET /api/browser/status returns connected: false with a connection error, Chrome is not accepting external CDP connections. Then you need either a custom image that uses a socat forward (e.g. listen on 0.0.0.0:9222 → 127.0.0.1:9223 with Chrome on 9223), or run Chrome with an older build that still honors the flag. See e.g. Kasm: How to Open Chrome's Remote Debugging Port.

Optional: backend env

  • BROWSER_CDP_URL: default http://chrome:9222. Override if Chrome runs elsewhere or with a different port.

Scheduled posts

Posts are stored in the database; a job runs every minute and publishes any post whose scheduled_at time has passed. You can load many posts for given dates (bulk create) and list by date range and platform.

Method Path Description
POST /api/scheduled-posts Create one: {"platform": "x", "text": "...", "scheduled_at": "2025-03-12T14:00:00Z"}.
POST /api/scheduled-posts/bulk Load many: {"posts": [{"platform": "x", "text": "...", "scheduled_at": "..."}, ...]}.
GET /api/scheduled-posts List. Query: ?status=pending, ?platform=x, ?from_date=2025-03-12, ?to_date=2025-03-20, ?limit=100.
DELETE /api/scheduled-posts/{id} Cancel a pending post.
  • scheduled_at: ISO datetime (UTC or offset), or date-only YYYY-MM-DD for list filters. Single create requires a future time; bulk can be any.
  • from_date / to_date: Filter list by scheduled_at (inclusive). Use YYYY-MM-DD or full ISO datetime.
  • Status: pending, posted, failed, cancelled. Only pending can be cancelled.
  • Character limits and max posts per platform per day are enforced (see below). Use GET /api/scheduled-posts/limits to get current limits for the UI.

Examples:

# Load database with posts for a range of dates (one per day to X)
curl -X POST "https://degelas.be/api/scheduled-posts/bulk" \
  -H "Content-Type: application/json" \
  -d '{
    "posts": [
      {"platform": "x", "text": "Post for March 12.", "scheduled_at": "2025-03-12T09:00:00Z"},
      {"platform": "x", "text": "Post for March 13.", "scheduled_at": "2025-03-13T09:00:00Z"}
    ]
  }'

# List posts for given dates and platform
curl -s "https://degelas.be/api/scheduled-posts?from_date=2025-03-12&to_date=2025-03-20&platform=x"

# List pending only
curl -s "https://degelas.be/api/scheduled-posts?status=pending"

# Cancel one
curl -X DELETE "https://degelas.be/api/scheduled-posts/1"

Where limits and calendar are configured

What Where Env override
Character limit (X) app/config.py: X_MAX_POST_LENGTH (default 280) X_MAX_POST_LENGTH
Character limit (Instagram) app/config.py: INSTAGRAM_MAX_CAPTION_LENGTH (default 2200) INSTAGRAM_MAX_CAPTION_LENGTH
Max posts per platform per day app/config.py: MAX_POSTS_PER_PLATFORM_PER_DAY (default 5) MAX_POSTS_PER_PLATFORM_PER_DAY
Validation app/scheduled_posts.py: validate_post_text(), count_posts_on_date(); used in create_scheduled_post and create_scheduled_posts_bulk
Limits API GET /api/scheduled-posts/limits returns the above for UI

Campaign calendar

Campaigns group posts by a name and date range; you can use them as a "campaign calendar".

Method Path Description
POST /api/campaigns Create: {"name": "...", "start_date": "YYYY-MM-DD", "end_date": "YYYY-MM-DD", "max_posts_per_platform_per_day": null}
GET /api/campaigns List campaigns
GET /api/campaigns/{id}/calendar Calendar: posts for this campaign grouped by date

When creating scheduled posts (single or bulk), set campaign_id to link them to a campaign. List posts with ?campaign_id=... to see only that campaigns posts.

Quick test

# After stack is up (docker compose up -d)
curl -s https://degelas.be/api/browser/status
# With noVNC tab open and a page loaded, you should see "connected": true and "page_url": ...