Commit graph

31 commits

Author SHA1 Message Date
30242ae470 Fix timezone mismatch: store/compare scheduled_at in UTC
JS was sending browser-local time; dispatcher compared against server UTC,
so posts scheduled in UTC+3 appeared 3h in the future to the dispatcher.

- JS helpers: utcToDate, utcToLocalInput, localInputToUTC
- All scheduled_at values sent to server are now converted to UTC
- Stored values are displayed converted back to browser local time
- relTime and fmtDT parse stored UTC timestamps correctly
- dispatch-social.php: gmdate() for $now (UTC) + sent_at

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 21:07:42 +03:00
9af6326be2 Add startup diagnostics to dispatch-social for debugging
Logs now=, due_targets=, pending_posts= and per-post details (scheduled,
status, target count, DUE/future) so Dispatch now button shows useful output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:55:11 +03:00
dd65c09ad5 Move Schedule/Save/Cancel buttons to bottom-right of edit card
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:50:48 +03:00
629f2515e9 Add relative time to post cards; add Dispatch now button for manual testing
- 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>
2026-06-04 20:49:56 +03:00
e52f90445b Redesign social scheduler as inline-edit card grid
- 3-col grid (col-xl-4 col-md-6) replacing left/right split layout
- First card is always the "new post" composer in edit state
- Clicking a post card (or its pencil icon) opens inline editing on that card
- Multiple cards can be in edit state simultaneously
- Autosave for existing posts (debounced 2s) with saving/saved/error indicator
- Post cards show: datetime + status (top left), action icons (top right),
  text body, image (centered, max-height = card width), platform badges
- Image max-height constrained to card width via JS after render
- Emoji picker moved to shared fixed floating panel
- Per-card error display; no global form state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:39:53 +03:00
1c43c6c432 Color-code image icon on social pills: red = no image, green = image on
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:15:17 +03:00
5b801514b2 Replace platform checkboxes with pill toggles in social scheduler
Target selection is now a row of compact pills (per platform+target)
instead of grouped checkbox lists. Clicking a pill toggles it active;
the image icon inside only activates when the pill is selected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 20:09:57 +03:00
24c9deef94 Auto-check platform image toggles when image is uploaded
include_image was always 0 because users had to manually tick the
per-platform image checkbox after uploading — not obvious. Now uploading
an image auto-checks all targets; users can still uncheck individually.

Also removes debug logging from dispatch-social.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 19:43:29 +03:00
b792535dd0 Add image debug logging to dispatch-social
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 19:40:56 +03:00
4767fe0109 Fix image posting for Discord and Mastodon
Discord: payload_json was missing the attachments array, so uploaded
files were orphaned and not shown in the message (Discord API v10 requirement).

Mastodon: media_ids[] sent as URL-encoded form was unreliable — switch to
JSON body. Also poll /api/v1/media/:id when upload returns 202 (async) to
wait until the image is processed before attaching it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 19:35:03 +03:00
1a366bef63 Overhaul social scheduler UI
- Emoji picker with 6 categories (~100 emojis), inserts at cursor
- Character counter (0/500) with amber/red thresholds
- Default schedule time is now +5 min in browser local time (fixes UTC offset bug)
- Relative time display uses local time (removes incorrect +Z suffix)
- Post cards redesigned: color-coded left border by status, platform badges
  in Discord/Mastodon/LinkedIn brand colors, status icons
- Delete is two-click confirm (no confirm() dialog)
- Duplicate errors use inline error area (no alert())
- Nicer empty state with icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 19:26:55 +03:00
8ab9b7bbe2 Fix scheduled_at datetime format mismatch
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>
2026-06-03 17:55:11 +03:00
30d2b53075 Fix dispatcher timezone: use local time to match datetime-local form input
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 17:45:28 +03:00
ac5c262bfb Add Social Scheduler — global social media post scheduling
- New /social page with left/right layout (form + post list)
- Social topbar link
- Tables: social_posts, social_targets (project-agnostic posts, per-target project refs)
- CRUD: create, edit, duplicate, delete, retry
- Image upload (JPEG/PNG/GIF/WebP, 10MB max) stored in data/social-uploads/
- Per-platform image toggle (Discord multipart, Mastodon /api/v2/media, LinkedIn Assets API)
- bin/dispatch-social.php cron dispatcher (every minute via /etc/cron.d)
- Settings: eligible projects section to enable bot projects as social account sources
- Platforms: Discord (webhook), Mastodon, LinkedIn personal profile

Cron setup:
  * * * * * www-data /usr/bin/php /opt/hackmancms/bin/dispatch-social.php >> /var/log/hackman_social.log 2>&1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 17:01:03 +03:00
45b9e15152 Improve LinkedIn UI and scheduling robustness
- 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>
2026-06-03 15:17:26 +03:00
30b21b6214 Add scheduled social posts, Discord channels config, and LinkedIn personal posting fix
- 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>
2026-05-29 15:14:03 +03:00
09e3406847 Support personal profile LinkedIn posting
- 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>
2026-05-28 13:25:19 +03:00
9b5bf89d11 Switch LinkedIn scope to w_member_social (personal profile)
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>
2026-05-28 12:56:49 +03:00
ac5b82c381 Load config.local.php from repo root for server-side secrets
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>
2026-05-28 12:40:14 +03:00
e1f7fb14fc Simplify deploy.sh to run in place on the server
Removes local/prod cases — script now just does git pull, writes BUILD,
and runs migrate wherever it's executed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 12:32:28 +03:00
e8e4476a58 Fall back to \$_SERVER for LinkedIn env vars to support PHP-FPM
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>
2026-05-28 12:22:16 +03:00
f6e768c246 Expand bot/automated-tool filter in site log importer
Add empty UA early return and a comprehensive list of bot signals covering
HTTP libraries, SEO platforms, uptime monitors, security scanners, AI
crawlers, and generic scanner vocabulary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 19:29:29 +03:00
dfca6df629 Send Discord snowflake IDs as strings to prevent JS precision loss
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>
2026-05-27 19:29:23 +03:00
df11e32e70 Fix write_config silently swallowing errors in botcompose
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>
2026-05-27 11:21:43 +03:00
f86859695a Add compose/repost UI for RSS feeds, Mastodon accounts, and LinkedIn pages
- 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>
2026-05-26 19:07:45 +03:00
158a5b0775 Add LinkedIn OAuth flow
- 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>
2026-05-26 17:25:38 +03:00
3d7b620b6c Point botconfig and botlogs to data/ subdirectory
Matches bashyBot's new data/ layout for safe www-data write access.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 17:19:17 +03:00
796bc15302 Remove sudo from read-only systemctl calls in botcontrol
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>
2026-05-26 16:13:21 +03:00
bf3ae2351d Add Discord Bot project type with process control, config editor, and log viewer
- DiscordBotProject: new project type (discord-bot), auto-detects from main.py + cogs/
- botcontrol API: start/stop/restart/status via sudo systemctl
- botconfig API: atomic read/write of config.json; CRUD for streamers, RSS feeds,
  Mastodon accounts, and LinkedIn pages; tokens masked in GET responses
- botlogs API: tail log file (path via project_settings.bot_log_file)
- Tab partials: _tab_bot (process control), _tab_botconfig (structured editor),
  _tab_logs (log viewer with auto-refresh)
- view.php: register bot/botconfig/logs tabs with labels, icons, and groups
- app.js: bot control panel, config editor with account/page dropdowns in RSS modal,
  Mastodon accounts CRUD, LinkedIn pages CRUD; audit labels/icons for all new actions
- CLAUDE.md: document Discord Bot integration, sudoers requirement, API actions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 14:39:17 +03:00
a2a1f356ba Update of draft system 2026-05-03 23:20:46 +03:00
60ca58f5ba Init 2026-05-03 20:56:15 +03:00