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>
This commit is contained in:
Bashy 2026-05-29 15:14:03 +03:00
parent 09e3406847
commit 30b21b6214
9 changed files with 848 additions and 14 deletions

View file

@ -31,8 +31,21 @@
<hr>
<!-- LinkedIn Pages -->
<h6 class="mb-3 mt-3"><i class="bi bi-linkedin me-1 text-primary"></i>LinkedIn Pages</h6>
<!-- Discord Channels (for scheduled posts) -->
<h6 class="mb-1 mt-3"><i class="bi bi-discord me-1 text-primary"></i>Discord Channels</h6>
<p class="text-muted small mb-3">
Add the webhook URL to <code>.env</code> as <code>DISCORD_WEBHOOK_&lt;KEY&gt;</code> (uppercase).
These channels are available as targets for scheduled posts.
</p>
<div id="discordChannelList" class="mb-2"><div class="text-muted small">Loading…</div></div>
<button class="btn btn-sm btn-outline-primary mb-4" id="addDiscordChannelBtn">
<i class="bi bi-plus-lg me-1"></i>Add Channel
</button>
<hr>
<!-- LinkedIn -->
<h6 class="mb-3 mt-3"><i class="bi bi-linkedin me-1 text-primary"></i>LinkedIn</h6>
<div class="card mb-3">
<div class="card-body py-2 d-flex align-items-center gap-3">
@ -202,6 +215,36 @@
</div>
</div>
<!-- Discord channel modal -->
<div class="modal fade" id="discordChannelModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header py-2">
<h6 class="modal-title" id="discordChannelModalTitle">Add Discord Channel</h6>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="discordChannelModalMode">
<div class="mb-2">
<label class="form-label small">Channel key</label>
<input type="text" id="discordChannelName" class="form-control form-control-sm font-monospace"
placeholder="general" autocomplete="off">
<div class="form-text small">Used as <code>DISCORD_WEBHOOK_&lt;KEY&gt;</code> in .env (uppercase).</div>
</div>
<div class="mb-2">
<label class="form-label small">Label</label>
<input type="text" id="discordChannelLabel" class="form-control form-control-sm" placeholder="General">
</div>
</div>
<div class="modal-footer py-2">
<div class="text-danger small d-none flex-grow-1" id="discordChannelModalError"></div>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-primary btn-sm" id="discordChannelModalSaveBtn">Save</button>
</div>
</div>
</div>
</div>
<!-- LinkedIn page modal -->
<div class="modal fade" id="linkedinPageModal" tabindex="-1">
<div class="modal-dialog modal-sm">

View file

@ -0,0 +1,80 @@
<div id="botSchedulePanel" data-project-id="<?= $pid ?>">
<!-- Create form -->
<div class="card mb-4">
<div class="card-header py-2 small fw-semibold"><i class="bi bi-clock me-1"></i>Schedule a Post</div>
<div class="card-body">
<div class="mb-2">
<label class="form-label small">Content</label>
<textarea id="schedContent" rows="4" class="form-control form-control-sm font-monospace"
placeholder="What's on your mind?"></textarea>
</div>
<div class="mb-3">
<label class="form-label small">URL <span class="text-muted">(optional appended to content)</span></label>
<input type="url" id="schedUrl" class="form-control form-control-sm font-monospace"
placeholder="https://example.com/post">
</div>
<div class="mb-3">
<label class="form-label small">Send at</label>
<input type="datetime-local" id="schedAt" class="form-control form-control-sm" style="max-width:240px">
</div>
<label class="form-label small">Platforms</label>
<div class="mb-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="schedUseDiscord">
<label class="form-check-label small fw-semibold" for="schedUseDiscord">
<i class="bi bi-discord me-1 text-primary"></i>Discord
</label>
</div>
<div id="schedDiscordPicker" class="d-none ms-3 mt-1">
<div id="schedDiscordChannels" class="d-flex flex-wrap gap-2"></div>
<div class="text-muted small d-none" id="schedDiscordEmpty">
No Discord channels configured add them in the Config tab.
</div>
</div>
</div>
<div class="mb-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="schedUseMastodon">
<label class="form-check-label small fw-semibold" for="schedUseMastodon">
<i class="bi bi-mastodon me-1 text-primary"></i>Mastodon
</label>
</div>
<div id="schedMastodonPicker" class="d-none ms-3 mt-1">
<div id="schedMastodonAccounts" class="d-flex flex-wrap gap-2"></div>
<div class="text-muted small d-none" id="schedMastodonEmpty">
No Mastodon accounts configured add them in the Config tab.
</div>
</div>
</div>
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="schedUseLinkedin">
<label class="form-check-label small fw-semibold" for="schedUseLinkedin">
<i class="bi bi-linkedin me-1 text-primary"></i>LinkedIn
</label>
</div>
<div id="schedLinkedinPicker" class="d-none ms-3 mt-1">
<div id="schedLinkedinProfiles" class="d-flex flex-wrap gap-2"></div>
<div class="text-muted small d-none" id="schedLinkedinEmpty">
No LinkedIn profiles connected connect your account in the Config tab.
</div>
</div>
</div>
<div class="text-danger small d-none mb-2" id="schedError"></div>
<button class="btn btn-primary btn-sm" id="schedSubmitBtn">
<i class="bi bi-calendar-check me-1"></i>Schedule Post
</button>
</div>
</div>
<!-- Post list -->
<h6 class="mb-3">Scheduled &amp; Recent Posts</h6>
<div id="schedPostsList"><div class="text-muted small">Loading…</div></div>
</div>

View file

@ -40,7 +40,7 @@ $tabLabels = [
'plugins' => 'Plugins', 'git' => 'Git',
'notes' => 'Notes', 'settings' => 'Settings',
'bot' => 'Bot', 'botconfig' => 'Config',
'logs' => 'Logs',
'botschedule' => 'Schedule', 'logs' => 'Logs',
];
$tabIcons = [
'dashboard' => 'bi-grid-1x2', 'analytics' => 'bi-graph-up',
@ -50,11 +50,11 @@ $tabIcons = [
'plugins' => 'bi-puzzle', 'git' => 'bi-git',
'notes' => 'bi-sticky', 'settings' => 'bi-gear',
'bot' => 'bi-robot', 'botconfig' => 'bi-sliders2',
'logs' => 'bi-journal-text',
'botschedule' => 'bi-calendar-event', 'logs' => 'bi-journal-text',
];
$tabGroups = [
['dashboard', 'analytics'],
['bot', 'botconfig', 'logs'],
['bot', 'botconfig', 'botschedule', 'logs'],
['posts', 'config', 'files', 'media'],
['run', 'themes', 'plugins', 'git'],
['notes', 'settings'],
@ -267,6 +267,9 @@ $tabGroups = [
<?php elseif ($tab === 'botconfig'): ?>
<?php include ROOT . '/views/project/_tab_botconfig.php'; ?>
<?php elseif ($tab === 'botschedule'): ?>
<?php include ROOT . '/views/project/_tab_botschedule.php'; ?>
<?php elseif ($tab === 'logs'): ?>
<?php include ROOT . '/views/project/_tab_logs.php'; ?>