// FileUpload.js - Модуль для загрузки файлов в чат Qwen.ai import { getBrowserContext } from '../browser/browser.js'; import { logInfo, logError } from '../logger/index.js'; import { getAuthToken, extractAuthToken, pagePool } from './chat.js'; import { getAvailableToken } from './tokenManager.js'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const UPLOAD_DIR = path.join(__dirname, '../../uploads'); const STS_TOKEN_API_URL = 'https://chat.qwen.ai/api/v1/files/getstsToken'; const OSS_SDK_URL = 'https://gosspublic.alicdn.com/aliyun-oss-sdk-6.20.0.min.js'; const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.bmp']; const DOCUMENT_EXTENSIONS = ['.pdf', '.doc', '.docx', '.txt']; const DEFAULT_FILE_TYPE = 'file'; const IMAGE_FILE_TYPE = 'image'; const DOCUMENT_FILE_TYPE = 'document'; // Убедимся, что директория для загрузок существует if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR, { recursive: true }); } /** * Получает и валидирует browser context * @returns {Object} - Browser context * @throws {Error} - Если браузер не инициализирован */ function validateBrowserContext() { const browserContext = getBrowserContext(); if (!browserContext) { throw new Error('Браузер не инициализирован'); } return browserContext; } /** * Получает токен авторизации, извлекая из браузера при необходимости * @param {Object} browserContext - Browser context * @returns {Promise} - Токен авторизации * @throws {Error} - Если не удалось получить токен */ async function validateAuthToken(browserContext) { let tokenObj = await getAvailableToken(); let token = null; if (tokenObj && tokenObj.token) { token = tokenObj.token; logInfo(`Используется токен из tokenManager: ${tokenObj.id}`); } if (!token) { token = getAuthToken(); } if (!token) { logInfo('Токен авторизации не найден в памяти, пытаемся извлечь из браузера'); token = await extractAuthToken(browserContext); if (!token) { throw new Error('Не удалось получить токен авторизации'); } } return token; } /** * Получает STS токен доступа для загрузки файлов * @param {Object} fileInfo - Информация о файле (имя, размер, тип) * @returns {Promise} - Объект с данными токена доступа */ export async function getStsToken(fileInfo) { const browserContext = validateBrowserContext(); const token = await validateAuthToken(browserContext); logInfo(`Запрос STS токена для файла: ${fileInfo.filename}`); let page = null; try { page = await pagePool.getPage(browserContext); const result = await page.evaluate(async (data) => { try { const response = await fetch(data.apiUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${data.token}`, 'Accept': 'application/json' }, body: JSON.stringify(data.fileInfo) }); if (response.ok) { return { success: true, data: await response.json()}; } else { return { success: false, status: response.status, statusText: response.statusText, errorBody: await response.text() }; } } catch (error) { return { success: false, error: error.toString() }; } }, { apiUrl: STS_TOKEN_API_URL, token, fileInfo }); if (result.success) { logInfo(`STS токен успешно получен для файла: ${fileInfo.filename}`); return result.data; } else { logError(`Ошибка при получении STS токена: status=${result.status}, error=${result.errorBody || result.error}`); throw new Error(`Ошибка получения STS токена: ${result.statusText || result.error}`); } } catch (error) { logError(`Ошибка при получении STS токена: ${error.message}`, error); throw error; } finally { if (page) { try { pagePool.releasePage(page); } catch (e) { logError('Ошибка при возврате страницы в пул:', e); } } } } /** * Загружает файл на URL, полученный с STS токеном * @param {string} filePath - Путь к файлу для загрузки * @param {Object} stsData - Данные STS токена * @returns {Promise} - Результат загрузки файла */ export async function uploadFile(filePath, stsData) { const browserContext = validateBrowserContext(); logInfo(`Начало загрузки файла: ${filePath}`); if (!stsData?.file_path || !stsData?.access_key_id || !stsData?.access_key_secret || !stsData?.security_token || !stsData?.region || !stsData?.bucketname) { throw new Error('Некорректные или неполные данные STS токена'); } logInfo(`[OSS] Загрузка через браузер`); logInfo(`[OSS] Регион: ${stsData.region}, Бакет: ${stsData.bucketname}`); if (stsData.endpoint) { logInfo(`[OSS] Endpoint: ${stsData.endpoint}`); } const fileBuffer = fs.readFileSync(filePath); const fileBase64 = fileBuffer.toString('base64'); logInfo(`[OSS] Размер файла: ${fileBuffer.length} байт`); let page = null; try { page = await pagePool.getPage(browserContext); const result = await page.evaluate(async (data) => { try { if (typeof window.OSS === 'undefined') { await new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = data.ossSdkUrl; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); }); } const blob = new Blob([Uint8Array.from(atob(data.fileBase64), c => c.charCodeAt(0))]) const client = new window.OSS({ region: data.stsData.region, accessKeyId: data.stsData.access_key_id, accessKeySecret: data.stsData.access_key_secret, stsToken: data.stsData.security_token, bucket: data.stsData.bucketname, secure: true }); await client.put(data.stsData.file_path, blob); return { success: true }; } catch (error) { return { success: false, error: error.toString() }; } }, { fileBase64, ossSdkUrl: OSS_SDK_URL, stsData: { region: stsData.region, bucketname: stsData.bucketname, file_path: stsData.file_path, access_key_id: stsData.access_key_id, access_key_secret: stsData.access_key_secret, security_token: stsData.security_token } }); if (result.success) { return { success: true, fileName: path.basename(filePath), url: stsData.file_url, fileId: stsData.file_id, filePath: stsData.file_path }; } else { logError(`[OSS] Ошибка загрузки: ${result.error}`); throw new Error(`Ошибка загрузки в OSS: ${result.error}`); } } catch (error) { logError(`Ошибка при загрузке файла в OSS: ${error.message}`, error); throw error; } finally { if (page) { try { pagePool.releasePage(page); } catch (e) { logError('Ошибка при возврате страницы в пул:', e); } } } } /** * Полный процесс загрузки файла: получение токена и загрузка * @param {string} filePath - Путь к файлу для загрузки * @returns {Promise} - Результат загрузки файла */ export async function uploadFileToQwen(filePath) { try { // Проверяем существование файла if (!fs.existsSync(filePath)) { throw new Error(`Файл не найден: ${filePath}`); } const fileName = path.basename(filePath); const fileSize = fs.statSync(filePath).size; const fileExt = path.extname(fileName).toLowerCase(); // Определяем тип файла let fileType = DEFAULT_FILE_TYPE; if (IMAGE_EXTENSIONS.includes(fileExt)) { fileType = IMAGE_FILE_TYPE; } else if (DOCUMENT_EXTENSIONS.includes(fileExt)) { fileType = DOCUMENT_FILE_TYPE; } // Запрашиваем STS токен const fileInfo = { filename: fileName, filesize: fileSize, filetype: fileType }; const stsData = await getStsToken(fileInfo); const uploadResult = await uploadFile(filePath, stsData); return { ...uploadResult, fileInfo, stsData }; } catch (error) { logError(`Ошибка в процессе загрузки файла: ${error.message}`, error); return { success: false, error: error.message }; } } export default { getStsToken, uploadFile, uploadFileToQwen };