Skip to content

Bug: uploadFromBuffer падает при загрузке видео — ответ сервера XML, а не JSON #226

@Sergei-Popov

Description

@Sergei-Popov

Пакет: @maxhub/max-bot-api v0.2.1

Описание

Загрузка видео через api.upload.video({ source: buffer }) падает с ошибкой:

SyntaxError: Unexpected token '<', "<retval>1</retval>" is not valid JSON

Проблема в методе Upload.uploadFromBuffer (dist/core/helpers/upload.js). После загрузки файла на upload URL (шаг 2) он вызывает res.json() на ответе сервера. Однако MAX API для видео возвращает XML <retval>1</retval>, а не JSON.

Корневая причина

В потоке загрузки два бага:

Баг 1: В типе GetUploadUrlResponse отсутствует поле token

В dist/core/network/api/modules/uploads/types.d.ts:

export type GetUploadUrlResponse = {
    url: string;  // ← token отсутствует!
};

Реальный ответ MAX API на POST /uploads?type=video:

{ "url": "https://...", "token": "f9LHodD0cOLK4f2L47RB..." }

token уже возвращается на шаге 1, но SDK его игнорирует, потому что в типе его нет.

Баг 2: uploadFromBuffer парсит ответ шага 2 как JSON

В dist/core/helpers/upload.js:

this.uploadFromBuffer = async ({ file, uploadUrl, abortController }) => {
    const formData = new FormData();
    formData.append('data', new Blob([file.buffer]), file.fileName);
    const res = await fetch(uploadUrl, {
        method: 'POST',
        body: formData,
        signal: abortController?.signal,
    });
    return await res.json();  // ← БАГ: ответ — XML, не JSON
};

Upload-эндпоинт (шаг 2) возвращает <retval>1</retval> (XML-подтверждение), а не JSON с { id, token }. Токен нужно брать из ответа шага 1.

Аналогичная проблема есть в uploadFromStream, который тоже вызывает uploadData = await uploadRes.json() на промежуточных ответах загрузки.

Ожидаемое поведение

Метод upload должен:

  1. Извлекать token из ответа POST /uploads (шаг 1) — он там уже есть
  2. Не пытаться парсить ответ шага 2 как JSON (это XML)
  3. Возвращать { token } из шага 1

Предлагаемое исправление

this.upload = async (type, file, options) => {
    // Шаг 1: getUploadUrl возвращает { url, token }
    const { url: uploadUrl, token } = await this.api.raw.uploads.getUploadUrl({ type });
    
    // ... загрузка файла на uploadUrl (шаг 2) ...
    
    // Возвращаем token из шага 1, а не из ответа шага 2
    return { token };
};

И обновить тип:

export type GetUploadUrlResponse = {
    url: string;
    token: string;
};

Воспроизведение

import { Bot } from '@maxhub/max-bot-api'
import fs from 'fs'

const bot = new Bot('BOT_TOKEN')

const videoBuffer = fs.readFileSync('video.mp4')
const result = await bot.api.upload.video({ source: videoBuffer })
// Падает: SyntaxError: Unexpected token '<', "<retval>1</retval>" is not valid JSON

Обходное решение

Вызывать MAX API напрямую, минуя хелпер загрузки SDK:

// Шаг 1: Получаем upload URL и token
const res1 = await fetch(`https://botapi.max.ru/uploads?access_token=${botToken}&type=video`, { method: 'POST' })
const { url, token } = await res1.json()

// Шаг 2: Загружаем файл (ответ — XML, не парсим)
const formData = new FormData()
formData.append('data', new Blob([buffer], { type: 'video/mp4' }), 'video.mp4')
await fetch(url, { method: 'POST', body: formData })

// Шаг 3: Используем token из шага 1
const attachment = new VideoAttachment({ token })

Окружение

  • @maxhub/max-bot-api: 0.2.1
  • Node.js: 22.x
  • ОС: Linux (Docker)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions