- View cards now show "2026-06-04 14:30 (in 2h)" next to the scheduled time
- New "Dispatch now" button runs dispatch-social.php immediately and shows
output inline (useful for diagnosing why cron isn't sending posts)
- dispatch action added to /api/social
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
datetime-local sends ISO format with T separator; normalize to space on save
and use SQLite datetime() in dispatcher query to handle both formats.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- When LinkedIn is connected, replace the Connect button with a pencil
(test post to first personal profile) and a reconnect arrow icon
- Add Process Now button to the Schedule tab that shells out to the cron
script on demand and shows its output inline
- Add flock() to the cron script to prevent overlapping runs
- Fix cron install path comment to match deployed location
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Scheduled posts: new Schedule tab with multi-platform post scheduling (Discord,
Mastodon, LinkedIn); SQLite queue with per-target delivery tracking and retry;
cron script in bin/process-scheduled-posts.php processes due targets inline
- Discord channels: new config section for webhook-based channels used as scheduled
post targets; webhook URLs stored in .env as DISCORD_WEBHOOK_<KEY>
- LinkedIn: fix botcompose_post to use member_id (urn:li:person) for personal
profiles instead of hardcoded org author; filter org pages out of compose UI
and RSS modal picker since org posting is unsupported; label org pages clearly
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- OAuth callback fetches /v2/userinfo to store member_id + name,
auto-creates a 'profile' personal page on first connect
- Page modal adds type selector (personal/org), hides org ID for personal
- renderLinkedinPages shows 'Personal' badge instead of org ID
- botconfig saves type field; personal pages omit organization_id
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
w_organization_social requires Marketing Developer Platform approval.
Org page scope kept as a comment for when/if that access is granted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides a gitignored override file for credentials like LinkedIn OAuth
keys, avoiding reliance on Apache SetEnv / PHP-FPM env config.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Apache SetEnv doesn't reach PHP-FPM processes via getenv(), but the
values are still available in \$_SERVER.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
channel_id and role_id are 64-bit integers that exceed Number.MAX_SAFE_INTEGER,
causing silent rounding in the browser. Cast to string in the GET response;
PHP stores them back as integers on save (64-bit PHP handles them correctly).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches the pattern in botconfig.php — returns bool and reports
failure to the caller so the UI shows the real error instead of
false success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- botcompose.php: new API endpoint for rss_repost, mastodon_post, linkedin_post
- _tab_botconfig.php: shared compose modal (textarea + post button)
- app.js: repost button on RSS cards (clears last_id, bot reposts on next poll),
compose button on Mastodon/LinkedIn cards (direct post via compose modal),
audit feed labels/icons for new compose actions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- linkedin_auth API: generates auth URL with CSRF state stored in session
- linkedin_callback: exchanges code for tokens, writes to project's config.json
- index.php: route /linkedin/callback before auth check
- Connect button wired up in JS; reads LINKEDIN_CLIENT_ID/SECRET from server env
Server setup required:
SetEnv LINKEDIN_CLIENT_ID ...
SetEnv LINKEDIN_CLIENT_SECRET ...
Redirect URI to register in LinkedIn app: https://<host>/linkedin/callback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
is-active and show work without sudo as www-data; only
start/stop/restart need elevated privileges.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>