diff --git a/api.php b/api.php index e50cd0c..6020685 100644 --- a/api.php +++ b/api.php @@ -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)) { diff --git a/index.php b/index.php index d4ca10f..f1b8803 100644 --- a/index.php +++ b/index.php @@ -625,17 +625,18 @@ foreach ($channels as $ch) {
- Как получить пользовательский токен (для загрузки фото)? + Как получить токен сообщества (рекомендуется)?
    -
  1. Перейдите на vkhost.github.io
  2. -
  3. Нажмите "VK Admin"
  4. -
  5. Разрешите доступ приложению
  6. -
  7. Скопируйте access_token из адресной строки браузера
  8. -
  9. Вставьте токен в поле выше и нажмите "Сохранить"
  10. +
  11. Откройте свою группу в VK → Управление
  12. +
  13. В меню справа выберите «Работа с API» → вкладка «Ключи доступа»«Создать ключ»
  14. +
  15. Включите права: Управление сообществом, Сообщения сообщества, Фотографии, Стена (Wall)
  16. +
  17. Скопируйте созданный ключ и вставьте его в поле выше → «Сохранить»

- Важно: Пользовательский токен позволяет загружать фото напрямую в VK. - Community-токен (ключ сообщества) может только постить текст и ссылки. + Важно: ключ сообщества бессрочный и не блокируется VK. Он постит и грузит фото только в свою группу — для нескольких групп создайте отдельный ключ в каждой. +

+

+ Старый способ через vkhost.github.io / VK Admin сейчас не работает — VK заблокировал это приложение (ошибка [8] Application is blocked).

diff --git a/js/app.js b/js/app.js index 2a0a4b8..2603232 100644 --- a/js/app.js +++ b/js/app.js @@ -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 }); - showNotification(allSuccess ? 'Опубликовано!' : 'Частично опубликовано', allSuccess ? 'success' : 'warning'); + // 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 += `${platformName} ${icon} `; + const tip = result.error || result.warning || 'OK'; + const cls = result.success ? (result.warning ? 'result-warning' : 'result-success') : 'result-error'; + statusIcons += `${platformName} ${icon} `; + if (result.warning) { + warningText = `${platformName}: ${result.warning}`; + } }); // Collect all photo URLs for preview @@ -3766,6 +3782,7 @@ document.addEventListener('DOMContentLoaded', function() { ${photosPreviewHtml} ${post.text ? `

${escapeHtml(post.text.substring(0, 100))}${post.text.length > 100 ? '...' : ''}

` : ''} + ${warningText ? `

⚠ ${escapeHtml(warningText)}

` : ''} `; }).join('');