forked from admin/zeropost-engine
fix: VK photo upload — 2-step getWallUploadServer + saveWallPhoto
До: wall.post без attachments → картинка игнорировалась
После:
1. photos.getWallUploadServer → upload_url
2. POST upload_url с файлом (local path или download) → server/photo/hash
3. photos.saveWallPhoto → owner_id + photo_id
4. wall.post с attachments=photo{owner_id}_{id}
При ошибке загрузки фото — публикуем без картинки (graceful degradation)
Поддерживает как локальные /uploads/ файлы так и внешние URL
This commit is contained in:
@@ -127,7 +127,13 @@ async function publishToVK({ channel, text, photoUrl, article }) {
|
|||||||
if (!channel.vk_group_id || !channel.vk_access_token) {
|
if (!channel.vk_group_id || !channel.vk_access_token) {
|
||||||
throw new Error('VK не настроен');
|
throw new Error('VK не настроен');
|
||||||
}
|
}
|
||||||
// VK не поддерживает кнопки в постах — добавляем ссылку в конец текста, если её там ещё нет
|
|
||||||
|
const groupId = String(channel.vk_group_id).replace(/^-/, '');
|
||||||
|
const ownerId = '-' + groupId;
|
||||||
|
const token = channel.vk_access_token;
|
||||||
|
const v = '5.199';
|
||||||
|
|
||||||
|
// Добавляем ссылку на статью в конец текста
|
||||||
let finalText = text;
|
let finalText = text;
|
||||||
if (article) {
|
if (article) {
|
||||||
const url = articleUrl(article);
|
const url = articleUrl(article);
|
||||||
@@ -136,15 +142,69 @@ async function publishToVK({ channel, text, photoUrl, article }) {
|
|||||||
finalText = `${finalText}\n\n${buttonText}\n${url}`;
|
finalText = `${finalText}\n\n${buttonText}\n${url}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2-step upload картинки
|
||||||
|
let attachments = '';
|
||||||
|
if (photoUrl) {
|
||||||
|
try {
|
||||||
|
// Шаг 1: получаем upload URL
|
||||||
|
const uploadServerRes = await axios.get('https://api.vk.com/method/photos.getWallUploadServer', {
|
||||||
|
params: { group_id: groupId, access_token: token, v },
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
if (uploadServerRes.data?.error) throw new Error(`VK getWallUploadServer: ${uploadServerRes.data.error.error_msg}`);
|
||||||
|
const uploadUrl = uploadServerRes.data.response.upload_url;
|
||||||
|
|
||||||
|
// Шаг 2: загружаем файл
|
||||||
|
const localPath = resolveLocalPhoto(photoUrl);
|
||||||
|
let fileBuffer, fileName;
|
||||||
|
if (localPath) {
|
||||||
|
fileBuffer = fs.readFileSync(localPath);
|
||||||
|
fileName = path.basename(localPath);
|
||||||
|
} else {
|
||||||
|
// Внешний URL — скачиваем
|
||||||
|
const dl = await axios.get(photoUrl, { responseType: 'arraybuffer', timeout: 30_000 });
|
||||||
|
fileBuffer = Buffer.from(dl.data);
|
||||||
|
fileName = 'photo.jpg';
|
||||||
|
}
|
||||||
|
|
||||||
|
const form = new FormData();
|
||||||
|
form.append('photo', fileBuffer, { filename: fileName, contentType: 'image/jpeg' });
|
||||||
|
const uploadRes = await axios.post(uploadUrl, form, {
|
||||||
|
headers: form.getHeaders(),
|
||||||
|
timeout: 60_000,
|
||||||
|
});
|
||||||
|
const { server, photo: photoData, hash } = uploadRes.data;
|
||||||
|
if (!photoData) throw new Error('VK upload: пустой ответ');
|
||||||
|
|
||||||
|
// Шаг 3: сохраняем фото
|
||||||
|
const saveRes = await axios.get('https://api.vk.com/method/photos.saveWallPhoto', {
|
||||||
|
params: { group_id: groupId, server, photo: photoData, hash, access_token: token, v },
|
||||||
|
timeout: 10_000,
|
||||||
|
});
|
||||||
|
if (saveRes.data?.error) throw new Error(`VK saveWallPhoto: ${saveRes.data.error.error_msg}`);
|
||||||
|
const saved = saveRes.data.response?.[0];
|
||||||
|
if (!saved) throw new Error('VK saveWallPhoto: нет данных');
|
||||||
|
|
||||||
|
attachments = `photo${saved.owner_id}_${saved.id}`;
|
||||||
|
} catch (photoErr) {
|
||||||
|
console.warn(`[VK] photo upload failed, posting without photo: ${photoErr.message}`);
|
||||||
|
// Публикуем без картинки если загрузка упала
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Шаг 4: публикуем пост
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
owner_id: '-' + String(channel.vk_group_id).replace(/^-/, ''),
|
owner_id: ownerId,
|
||||||
from_group: '1',
|
from_group: '1',
|
||||||
message: finalText,
|
message: finalText,
|
||||||
access_token: channel.vk_access_token,
|
access_token: token,
|
||||||
v: '5.199',
|
v,
|
||||||
});
|
});
|
||||||
const res = await axios.post('https://api.vk.com/method/wall.post', params, { timeout: 15000 });
|
if (attachments) params.set('attachments', attachments);
|
||||||
if (res.data?.error) throw new Error(`VK: ${res.data.error.error_msg}`);
|
|
||||||
|
const res = await axios.post('https://api.vk.com/method/wall.post', params, { timeout: 15_000 });
|
||||||
|
if (res.data?.error) throw new Error(`VK wall.post: ${res.data.error.error_msg}`);
|
||||||
return res.data?.response?.post_id;
|
return res.data?.response?.post_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user