Fix archive missing on manual publish; refresh VK token instructions
- Mark scheduled post as 'published' instead of deleting it when user clicks "Опубликовать сейчас", so it appears in the archive (parity with cron path) - Surface VK warnings (e.g. photos posted as links when community token lacks photos right) in both the post-publish notification and the archive card - Replace dead vkhost.github.io / VK Admin instructions with a community-token flow via group settings, since VK now blocks the old app (error 8) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -776,6 +776,43 @@ try {
|
||||
}
|
||||
break;
|
||||
|
||||
case 'mark_post_published':
|
||||
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
|
||||
$postId = $_POST['id'] ?? '';
|
||||
$resultsJson = $_POST['results'] ?? '{}';
|
||||
$results = json_decode($resultsJson, true) ?: [];
|
||||
|
||||
if (!$postId) {
|
||||
echo json_encode(['error' => 'ID поста не указан']);
|
||||
exit;
|
||||
}
|
||||
|
||||
$posts = file_exists($scheduledFile) ? json_decode(file_get_contents($scheduledFile), true) ?: [] : [];
|
||||
$found = false;
|
||||
|
||||
foreach ($posts as &$post) {
|
||||
if (($post['id'] ?? null) === $postId && ($post['status'] ?? '') === 'pending') {
|
||||
$post['status'] = 'published';
|
||||
$post['published_at'] = date('Y-m-d H:i:s');
|
||||
$post['results'] = $results;
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
unset($post);
|
||||
|
||||
if (!$found) {
|
||||
echo json_encode(['error' => 'Пост не найден или уже опубликован']);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (file_put_contents($scheduledFile, json_encode($posts, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE))) {
|
||||
echo json_encode(['success' => true]);
|
||||
} else {
|
||||
echo json_encode(['error' => 'Не удалось сохранить']);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'get_published_posts':
|
||||
$scheduledFile = __DIR__ . '/data/scheduled_posts.json';
|
||||
if (file_exists($scheduledFile)) {
|
||||
|
||||
@@ -625,17 +625,18 @@ foreach ($channels as $ch) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<details class="vk-help" open>
|
||||
<summary>Как получить пользовательский токен (для загрузки фото)?</summary>
|
||||
<summary>Как получить токен сообщества (рекомендуется)?</summary>
|
||||
<ol style="margin: 10px 0; padding-left: 20px; font-size: 0.9em;">
|
||||
<li>Перейдите на <a href="https://vkhost.github.io/" target="_blank">vkhost.github.io</a></li>
|
||||
<li>Нажмите <strong>"VK Admin"</strong></li>
|
||||
<li>Разрешите доступ приложению</li>
|
||||
<li>Скопируйте <code>access_token</code> из адресной строки браузера</li>
|
||||
<li>Вставьте токен в поле выше и нажмите "Сохранить"</li>
|
||||
<li>Откройте свою группу в VK → <strong>Управление</strong></li>
|
||||
<li>В меню справа выберите <strong>«Работа с API»</strong> → вкладка <strong>«Ключи доступа»</strong> → <strong>«Создать ключ»</strong></li>
|
||||
<li>Включите права: <strong>Управление сообществом</strong>, <strong>Сообщения сообщества</strong>, <strong>Фотографии</strong>, <strong>Стена</strong> (Wall)</li>
|
||||
<li>Скопируйте созданный ключ и вставьте его в поле выше → «Сохранить»</li>
|
||||
</ol>
|
||||
<p style="font-size: 0.85em; color: var(--text-secondary); margin-top: 10px;">
|
||||
<strong>Важно:</strong> Пользовательский токен позволяет загружать фото напрямую в VK.
|
||||
Community-токен (ключ сообщества) может только постить текст и ссылки.
|
||||
<strong>Важно:</strong> ключ сообщества бессрочный и не блокируется VK. Он постит и грузит фото только в свою группу — для нескольких групп создайте отдельный ключ в каждой.
|
||||
</p>
|
||||
<p style="font-size: 0.8em; color: var(--text-secondary); margin-top: 8px;">
|
||||
Старый способ через <a href="https://vkhost.github.io/" target="_blank">vkhost.github.io</a> / VK Admin сейчас не работает — VK заблокировал это приложение (ошибка <code>[8] Application is blocked</code>).
|
||||
</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@@ -3679,14 +3679,24 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
const anySuccess = Object.values(results).some(r => r.success);
|
||||
|
||||
if (anySuccess) {
|
||||
// Delete the scheduled post
|
||||
const delForm = new FormData();
|
||||
delForm.append('action', 'delete_scheduled_post');
|
||||
delForm.append('id', postId);
|
||||
await fetch('api.php', { method: 'POST', body: delForm });
|
||||
// Mark scheduled post as published so it appears in archive
|
||||
const markForm = new FormData();
|
||||
markForm.append('action', 'mark_post_published');
|
||||
markForm.append('id', postId);
|
||||
markForm.append('results', JSON.stringify(results));
|
||||
await fetch('api.php', { method: 'POST', body: markForm });
|
||||
|
||||
// Surface VK warnings (e.g. photos posted as links instead of attachments)
|
||||
const warnings = Object.entries(results)
|
||||
.filter(([, r]) => r.success && r.warning)
|
||||
.map(([p, r]) => `${p.toUpperCase()}: ${r.warning}`);
|
||||
if (warnings.length > 0) {
|
||||
showNotification('Опубликовано с предупреждением — ' + warnings.join(' | '), 'warning');
|
||||
} else {
|
||||
showNotification(allSuccess ? 'Опубликовано!' : 'Частично опубликовано', allSuccess ? 'success' : 'warning');
|
||||
}
|
||||
loadScheduledPosts();
|
||||
loadPublishedPosts();
|
||||
} else {
|
||||
const errors = Object.entries(results).map(([p, r]) => `${p}: ${r.error}`).join(', ');
|
||||
showNotification('Ошибка: ' + errors, 'error');
|
||||
@@ -3734,10 +3744,16 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
// Check results for success/error
|
||||
const results = post.results || {};
|
||||
let statusIcons = '';
|
||||
let warningText = '';
|
||||
Object.entries(results).forEach(([platform, result]) => {
|
||||
const icon = result.success ? '✓' : '✗';
|
||||
const icon = result.success ? (result.warning ? '⚠' : '✓') : '✗';
|
||||
const platformName = platform === 'telegram' ? 'TG' : platform === 'vk' ? 'VK' : platform;
|
||||
statusIcons += `<span class="result-${result.success ? 'success' : 'error'}" title="${result.error || 'OK'}">${platformName} ${icon}</span> `;
|
||||
const tip = result.error || result.warning || 'OK';
|
||||
const cls = result.success ? (result.warning ? 'result-warning' : 'result-success') : 'result-error';
|
||||
statusIcons += `<span class="${cls}" title="${escapeHtml(tip)}">${platformName} ${icon}</span> `;
|
||||
if (result.warning) {
|
||||
warningText = `${platformName}: ${result.warning}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Collect all photo URLs for preview
|
||||
@@ -3766,6 +3782,7 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
</div>
|
||||
${photosPreviewHtml}
|
||||
${post.text ? `<p class="archive-text">${escapeHtml(post.text.substring(0, 100))}${post.text.length > 100 ? '...' : ''}</p>` : ''}
|
||||
${warningText ? `<p class="archive-warning">⚠ ${escapeHtml(warningText)}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
Reference in New Issue
Block a user