Spaces:
Sleeping
Sleeping
| import { PassThrough } from "stream"; | |
| import path from "path"; | |
| import _ from "lodash"; | |
| import mime from "mime"; | |
| import axios, { AxiosRequestConfig, AxiosResponse } from "axios"; | |
| import APIException from "@/lib/exceptions/APIException.ts"; | |
| import EX from "@/api/consts/exceptions.ts"; | |
| import { createParser } from "eventsource-parser"; | |
| import logger from "@/lib/logger.ts"; | |
| import util from "@/lib/util.ts"; | |
| import provider from "@/lib/upstream-provider.ts"; | |
| import { findDreaminaCookieHeaderBySessionid, getDreaminaApiKey, getDreaminaSessionPool } from "@/lib/intl-account-pool.ts"; | |
| // 模型名称 | |
| const MODEL_NAME = provider.modelName; | |
| // 默认的AgentID | |
| export const DEFAULT_ASSISTANT_ID = 513695; | |
| // 版本号 | |
| const VERSION_CODE = "8.4.0"; | |
| // 平台代码 | |
| const PLATFORM_CODE = "7"; | |
| // 设备ID | |
| const DEVICE_ID = Math.random() * 999999999999999999 + 7000000000000000000; | |
| // WebID | |
| export const WEB_ID = Math.random() * 999999999999999999 + 7000000000000000000; | |
| // 用户ID | |
| export const USER_ID = util.uuid(false); | |
| // 最大重试次数 | |
| const MAX_RETRY_COUNT = 3; | |
| // 重试延迟 | |
| const RETRY_DELAY = 5000; | |
| // 伪装headers | |
| const FAKE_HEADERS = { | |
| Accept: "application/json, text/plain, */*", | |
| "Accept-Encoding": "gzip, deflate, br, zstd", | |
| "Accept-language": provider.acceptLanguage, | |
| "App-Sdk-Version": "48.0.0", | |
| "Cache-control": "no-cache", | |
| Appid: provider.assistantId, | |
| Appvr: VERSION_CODE, | |
| Lan: provider.lan, | |
| Loc: provider.loc, | |
| Origin: provider.origin, | |
| Pragma: "no-cache", | |
| Priority: "u=1, i", | |
| Referer: provider.referer, | |
| Pf: PLATFORM_CODE, | |
| "Sec-Ch-Ua": | |
| '"Google Chrome";v="132", "Chromium";v="132", "Not_A Brand";v="8"', | |
| "Sec-Ch-Ua-Mobile": "?0", | |
| "Sec-Ch-Ua-Platform": '"Windows"', | |
| "Sec-Fetch-Dest": "empty", | |
| "Sec-Fetch-Mode": "cors", | |
| "Sec-Fetch-Site": "same-origin", | |
| "User-Agent": | |
| "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36", | |
| }; | |
| // 文件最大大小 | |
| const FILE_MAX_SIZE = 100 * 1024 * 1024; | |
| function resolveBaseUrlByPath(uri: string) { | |
| if (uri.startsWith("/commerce/")) return provider.commerceApiBaseUrl; | |
| if (uri.startsWith("/mweb/")) return provider.mwebApiBaseUrl; | |
| if (uri.startsWith("/lv/") || uri.startsWith("/user/")) return provider.webApiBaseUrl; | |
| if (uri.startsWith("/passport/")) return provider.pageBaseUrl; | |
| return provider.baseUrl; | |
| } | |
| function shouldUseGlobalQueryParams(uri: string) { | |
| if (uri.startsWith("/passport/")) return false; | |
| if (uri.startsWith("/commerce/")) return false; | |
| return true; | |
| } | |
| function isPassportRequest(uri: string) { | |
| return uri.startsWith("/passport/") || uri.startsWith("/user/"); | |
| } | |
| function isDreaminaPageApiRequest(uri: string) { | |
| return provider.name === "dreamina-intl" && uri.startsWith("/mweb/"); | |
| } | |
| function cookieNameFromKey(name: string) { | |
| const mapped: Record<string, string> = { | |
| store_idc: "store-idc", | |
| cc_target_idc: "cc-target-idc", | |
| tt_target_idc_sign: "tt-target-idc-sign", | |
| store_country_sign: "store-country-sign", | |
| }; | |
| return mapped[name] || name; | |
| } | |
| function resolveAccountCookies(refreshToken: string) { | |
| const cookieHeader = provider.name === "dreamina-intl" ? findDreaminaCookieHeaderBySessionid(refreshToken)?.header : null; | |
| if (!cookieHeader) return null; | |
| const result: Record<string, string> = {}; | |
| for (const chunk of cookieHeader.split(";")) { | |
| const trimmed = chunk.trim(); | |
| if (!trimmed) continue; | |
| const idx = trimmed.indexOf("="); | |
| if (idx === -1) continue; | |
| const key = trimmed.slice(0, idx).trim(); | |
| const value = trimmed.slice(idx + 1).trim(); | |
| result[key.replace(/-/g, "_")] = value; | |
| } | |
| return result; | |
| } | |
| /** | |
| * 获取缓存中的access_token | |
| * | |
| * 目前jimeng的access_token是固定的,暂无刷新功能 | |
| * | |
| * @param refreshToken 用于刷新access_token的refresh_token | |
| */ | |
| export async function acquireToken(refreshToken: string): Promise<string> { | |
| return refreshToken; | |
| } | |
| /** | |
| * 生成cookie | |
| */ | |
| export function generateCookie(refreshToken: string) { | |
| const account = resolveAccountCookies(refreshToken); | |
| if (provider.name === "dreamina-intl" && account) { | |
| return Object.entries(account) | |
| .map(([name, value]) => `${cookieNameFromKey(name)}=${value}`) | |
| .join("; "); | |
| } | |
| const extraCookies = account || provider.extraCookies || {}; | |
| const teaWebId = extraCookies?.tea_web_id || String(WEB_ID); | |
| const uidTt = extraCookies?.uid_tt || USER_ID; | |
| const uidTtSs = extraCookies?.uid_tt_ss || USER_ID; | |
| const sidGuard = extraCookies?.sid_guard || `${refreshToken}%7C${util.unixTimestamp()}%7C5184000%7CMon%2C+03-Feb-2025+08%3A17%3A09+GMT`; | |
| const cookies = [ | |
| `_tea_web_id=${teaWebId}`, | |
| `is_staff_user=false`, | |
| `${provider.storeRegionKey}=${provider.storeRegionValue}`, | |
| `${provider.storeRegionSrcKey}=${provider.storeRegionSrcValue}`, | |
| `sid_guard=${sidGuard}`, | |
| `uid_tt=${uidTt}`, | |
| `uid_tt_ss=${uidTtSs}`, | |
| `sid_tt=${refreshToken}`, | |
| `sessionid=${refreshToken}`, | |
| `sessionid_ss=${refreshToken}`, | |
| `sid_tt=${refreshToken}` | |
| ]; | |
| for (const [name, value] of Object.entries(extraCookies || {})) { | |
| if (value) cookies.push(`${cookieNameFromKey(name)}=${value}`); | |
| } | |
| return cookies.join("; "); | |
| } | |
| /** | |
| * 获取浏览器格式的cookie数组(用于Playwright context.addCookies) | |
| */ | |
| export function getCookiesForBrowser(refreshToken: string) { | |
| const domain = provider.cookieDomain; | |
| const account = resolveAccountCookies(refreshToken); | |
| if (provider.name === "dreamina-intl" && account) { | |
| return Object.entries(account).map(([name, value]) => ({ | |
| name: cookieNameFromKey(name), | |
| value, | |
| domain, | |
| path: "/", | |
| })); | |
| } | |
| const extraCookies = account || provider.extraCookies || {}; | |
| const teaWebId = extraCookies?.tea_web_id || String(WEB_ID); | |
| const uidTt = extraCookies?.uid_tt || USER_ID; | |
| const uidTtSs = extraCookies?.uid_tt_ss || USER_ID; | |
| const sidGuard = extraCookies?.sid_guard || `${refreshToken}%7C${util.unixTimestamp()}%7C5184000%7CMon%2C+03-Feb-2025+08%3A17%3A09+GMT`; | |
| const cookies = [ | |
| { name: "_tea_web_id", value: teaWebId, domain, path: "/" }, | |
| { name: "is_staff_user", value: "false", domain, path: "/" }, | |
| { name: provider.storeRegionKey, value: provider.storeRegionValue, domain, path: "/" }, | |
| { name: provider.storeRegionSrcKey, value: provider.storeRegionSrcValue, domain, path: "/" }, | |
| { name: "sid_guard", value: sidGuard, domain, path: "/" }, | |
| { name: "uid_tt", value: uidTt, domain, path: "/" }, | |
| { name: "uid_tt_ss", value: uidTtSs, domain, path: "/" }, | |
| { name: "sid_tt", value: refreshToken, domain, path: "/" }, | |
| { name: "sessionid", value: refreshToken, domain, path: "/" }, | |
| { name: "sessionid_ss", value: refreshToken, domain, path: "/" }, | |
| ]; | |
| for (const [name, value] of Object.entries(extraCookies || {})) { | |
| if (value) cookies.push({ name: cookieNameFromKey(name), value, domain, path: "/" }); | |
| } | |
| return cookies; | |
| } | |
| /** | |
| * 获取积分信息 | |
| * | |
| * @param refreshToken 用于刷新access_token的refresh_token | |
| */ | |
| export async function getCredit(refreshToken: string) { | |
| const candidateRequests = [ | |
| { path: provider.creditPath || "/commerce/v1/benefits/user_credit", method: provider.creditMethod || "POST", params: {} }, | |
| ...(provider.creditFallbackPaths || []), | |
| ]; | |
| let lastResult: any = null; | |
| for (const candidate of candidateRequests) { | |
| try { | |
| lastResult = await request(candidate.method, candidate.path, refreshToken, { | |
| params: candidate.params || {}, | |
| data: candidate.method === "POST" ? {} : undefined, | |
| headers: { | |
| Referer: provider.pageBaseUrl + "/", | |
| } | |
| }); | |
| if (typeof lastResult === "string" && /<html|<!doctype html/i.test(lastResult)) { | |
| throw new Error("积分接口返回 HTML 页面"); | |
| } | |
| const benefitMap = lastResult?.benefit_map || lastResult?.benefits || lastResult?.data?.benefits || null; | |
| const strategy = lastResult?.strategy || lastResult?.data?.strategy || null; | |
| const responseData = typeof lastResult?.response === "string" | |
| ? JSON.parse(lastResult.response) | |
| : lastResult?.response; | |
| logger.info(`国际版积分返回: ${JSON.stringify(lastResult).slice(0, 800)}`); | |
| const credit = lastResult?.data?.credit || responseData?.credit || lastResult?.credit || lastResult?.points || strategy || benefitMap || lastResult; | |
| const gift_credit = credit?.gift_credit ?? credit?.giftCredit ?? credit?.gift ?? credit?.gift_balance ?? credit?.free_credit ?? 0; | |
| const purchase_credit = credit?.purchase_credit ?? credit?.purchaseCredit ?? credit?.purchase ?? credit?.purchase_balance ?? credit?.paid_credit ?? 0; | |
| const vip_credit = credit?.vip_credit ?? credit?.vipCredit ?? credit?.vip ?? credit?.vip_balance ?? 0; | |
| if (gift_credit === 0 && purchase_credit === 0 && vip_credit === 0 && !benefitMap && !strategy && !credit) { | |
| throw new Error(`未识别的积分响应结构: ${JSON.stringify(lastResult).slice(0, 300)}`); | |
| } | |
| logger.info(`\n积分信息: \n赠送积分: ${gift_credit}, 购买积分: ${purchase_credit}, VIP积分: ${vip_credit}`); | |
| return { | |
| giftCredit: gift_credit, | |
| purchaseCredit: purchase_credit, | |
| vipCredit: vip_credit, | |
| totalCredit: gift_credit + purchase_credit + vip_credit, | |
| }; | |
| } catch (err) { | |
| logger.warn(`积分接口尝试失败: ${candidate.method} ${candidate.path} -> ${(err as Error).message}`); | |
| } | |
| } | |
| throw new APIException(EX.API_REQUEST_FAILED, `获取积分失败: ${JSON.stringify(lastResult)}`); | |
| } | |
| /** | |
| * 接收今日积分 | |
| * | |
| * @param refreshToken 用于刷新access_token的refresh_token | |
| */ | |
| export async function receiveCredit(refreshToken: string) { | |
| logger.info("正在收取今日积分...") | |
| const { cur_total_credits, receive_quota } = await request("POST", "/commerce/v1/benefits/credit_receive", refreshToken, { | |
| data: { | |
| time_zone: "Asia/Shanghai" | |
| }, | |
| headers: { | |
| Referer: provider.imageReferer | |
| } | |
| }); | |
| logger.info(`\n今日${receive_quota}积分收取成功\n剩余积分: ${cur_total_credits}`); | |
| return cur_total_credits; | |
| } | |
| /** | |
| * 请求jimeng | |
| * | |
| * @param method 请求方法 | |
| * @param uri 请求路径 | |
| * @param params 请求参数 | |
| * @param headers 请求头 | |
| */ | |
| export async function request( | |
| method: string, | |
| uri: string, | |
| refreshToken: string, | |
| options: AxiosRequestConfig = {} | |
| ) { | |
| const token = await acquireToken(refreshToken); | |
| const deviceTime = util.unixTimestamp(); | |
| const sign = util.md5( | |
| `9e2c|${uri.slice(-7)}|${PLATFORM_CODE}|${VERSION_CODE}|${deviceTime}||11ac` | |
| ); | |
| const fullUrl = `${resolveBaseUrlByPath(uri)}${uri}`; | |
| const requestParams = shouldUseGlobalQueryParams(uri) | |
| ? { | |
| aid: provider.assistantId, | |
| device_platform: "web", | |
| region: provider.region, | |
| webId: provider.extraCookies?.tea_web_id || WEB_ID, | |
| da_version: "3.3.12", | |
| web_component_open_flag: 1, | |
| web_version: "7.5.0", | |
| aigc_features: "app_lip_sync", | |
| os: "windows", | |
| commerce_with_input_video: 1, | |
| msToken: provider.extraCookies?.msToken, | |
| ...(options.params || {}), | |
| } | |
| : { | |
| ...(options.params || {}), | |
| }; | |
| const headers = isPassportRequest(uri) | |
| ? { | |
| Accept: "application/json, text/plain, */*", | |
| "Accept-Encoding": "gzip, deflate, br, zstd", | |
| "Accept-language": provider.acceptLanguage, | |
| Origin: provider.origin, | |
| Referer: provider.generateImageReferer, | |
| "User-Agent": FAKE_HEADERS["User-Agent"], | |
| Cookie: generateCookie(token), | |
| "x-tt-passport-csrf-token": provider.extraCookies?.passport_csrf_token || "", | |
| ...(options.headers || {}), | |
| } | |
| : isDreaminaPageApiRequest(uri) | |
| ? { | |
| Accept: "application/json, text/plain, */*", | |
| "Accept-Encoding": "gzip, deflate, br, zstd", | |
| "Accept-language": provider.acceptLanguage, | |
| Origin: provider.origin, | |
| Referer: provider.generateImageReferer, | |
| "User-Agent": FAKE_HEADERS["User-Agent"], | |
| Cookie: generateCookie(token), | |
| "Sec-Fetch-Dest": "empty", | |
| "Sec-Fetch-Mode": "cors", | |
| "Sec-Fetch-Site": "same-origin", | |
| ...(options.headers || {}), | |
| } | |
| : { | |
| ...FAKE_HEADERS, | |
| Cookie: generateCookie(token), | |
| "Device-Time": deviceTime, | |
| Sign: sign, | |
| "Sign-Ver": "1", | |
| ...(options.headers || {}), | |
| }; | |
| logger.info(`发送请求: ${method.toUpperCase()} ${fullUrl}`); | |
| logger.info(`请求参数: ${JSON.stringify(requestParams)}`); | |
| logger.info(`请求数据: ${JSON.stringify(options.data || {})}`); | |
| // 添加重试逻辑 | |
| let retries = 0; | |
| const maxRetries = 3; // 最大重试次数 | |
| let lastError = null; | |
| while (retries <= maxRetries) { | |
| try { | |
| if (retries > 0) { | |
| logger.info(`第 ${retries} 次重试请求: ${method.toUpperCase()} ${fullUrl}`); | |
| // 重试前等待一段时间 | |
| await new Promise(resolve => setTimeout(resolve, 1000 * retries)); | |
| } | |
| const response = await axios.request({ | |
| method, | |
| url: fullUrl, | |
| params: requestParams, | |
| headers: headers, | |
| timeout: 45000, // 增加超时时间到45秒 | |
| validateStatus: () => true, // 允许任何状态码 | |
| ..._.omit(options, "params", "headers"), | |
| }); | |
| // 记录响应状态和头信息 | |
| logger.info(`响应状态: ${response.status} ${response.statusText}`); | |
| // 流式响应直接返回response | |
| if (options.responseType == "stream") return response; | |
| // 记录响应数据摘要 | |
| const responseDataSummary = JSON.stringify(response.data).substring(0, 500) + | |
| (JSON.stringify(response.data).length > 500 ? "..." : ""); | |
| logger.info(`响应数据摘要: ${responseDataSummary}`); | |
| // 检查HTTP状态码 | |
| if (response.status >= 400) { | |
| logger.warn(`HTTP错误: ${response.status} ${response.statusText}`); | |
| if (retries < maxRetries) { | |
| retries++; | |
| continue; | |
| } | |
| } | |
| return checkResult(response); | |
| } | |
| catch (error) { | |
| lastError = error; | |
| logger.error(`请求失败 (尝试 ${retries + 1}/${maxRetries + 1}): ${error.message}`); | |
| // 如果是网络错误或超时,尝试重试 | |
| if ((error.code === 'ECONNABORTED' || error.code === 'ETIMEDOUT' || | |
| error.message.includes('timeout') || error.message.includes('network')) && | |
| retries < maxRetries) { | |
| retries++; | |
| continue; | |
| } | |
| // 其他错误直接抛出 | |
| break; | |
| } | |
| } | |
| // 所有重试都失败了,抛出最后一个错误 | |
| logger.error(`请求失败,已重试 ${retries} 次: ${lastError.message}`); | |
| if (lastError.response) { | |
| logger.error(`响应状态: ${lastError.response.status}`); | |
| logger.error(`响应数据: ${JSON.stringify(lastError.response.data)}`); | |
| } | |
| throw lastError; | |
| } | |
| /** | |
| * 预检查文件URL有效性 | |
| * | |
| * @param fileUrl 文件URL | |
| */ | |
| export async function checkFileUrl(fileUrl: string) { | |
| if (util.isBASE64Data(fileUrl)) return; | |
| const result = await axios.head(fileUrl, { | |
| timeout: 15000, | |
| validateStatus: () => true, | |
| }); | |
| if (result.status >= 400) | |
| throw new APIException( | |
| EX.API_FILE_URL_INVALID, | |
| `File ${fileUrl} is not valid: [${result.status}] ${result.statusText}` | |
| ); | |
| // 检查文件大小 | |
| if (result.headers && result.headers["content-length"]) { | |
| const fileSize = parseInt(result.headers["content-length"], 10); | |
| if (fileSize > FILE_MAX_SIZE) | |
| throw new APIException( | |
| EX.API_FILE_EXECEEDS_SIZE, | |
| `File ${fileUrl} is not valid` | |
| ); | |
| } | |
| } | |
| /** | |
| * 上传文件 | |
| * | |
| * @param refreshToken 用于刷新access_token的refresh_token | |
| * @param fileUrl 文件URL或BASE64数据 | |
| * @param isVideoImage 是否是用于视频图像 | |
| * @returns 上传结果,包含image_uri | |
| */ | |
| export async function uploadFile( | |
| refreshToken: string, | |
| fileUrl: string, | |
| isVideoImage: boolean = false | |
| ) { | |
| try { | |
| logger.info(`开始上传文件: ${fileUrl}, 视频图像模式: ${isVideoImage}`); | |
| // 预检查远程文件URL可用性 | |
| await checkFileUrl(fileUrl); | |
| let filename, fileData, mimeType; | |
| // 如果是BASE64数据则直接转换为Buffer | |
| if (util.isBASE64Data(fileUrl)) { | |
| mimeType = util.extractBASE64DataFormat(fileUrl); | |
| const ext = mime.getExtension(mimeType); | |
| filename = `${util.uuid()}.${ext}`; | |
| fileData = Buffer.from(util.removeBASE64DataHeader(fileUrl), "base64"); | |
| logger.info(`处理BASE64数据,文件名: ${filename}, 类型: ${mimeType}, 大小: ${fileData.length}字节`); | |
| } | |
| // 下载文件到内存,如果您的服务器内存很小,建议考虑改造为流直传到下一个接口上,避免停留占用内存 | |
| else { | |
| filename = path.basename(fileUrl); | |
| logger.info(`开始下载远程文件: ${fileUrl}`); | |
| ({ data: fileData } = await axios.get(fileUrl, { | |
| responseType: "arraybuffer", | |
| // 100M限制 | |
| maxContentLength: FILE_MAX_SIZE, | |
| // 60秒超时 | |
| timeout: 60000, | |
| })); | |
| logger.info(`文件下载完成,文件名: ${filename}, 大小: ${fileData.length}字节`); | |
| } | |
| // 获取文件的MIME类型 | |
| mimeType = mimeType || mime.getType(filename); | |
| logger.info(`文件MIME类型: ${mimeType}`); | |
| // 构建FormData | |
| const formData = new FormData(); | |
| const blob = new Blob([fileData], { type: mimeType }); | |
| formData.append('file', blob, filename); | |
| // 获取上传凭证 | |
| logger.info(`请求上传凭证,场景: ${isVideoImage ? 'video_cover' : 'aigc_image'}`); | |
| const uploadProofUrl = 'https://imagex.bytedanceapi.com/'; | |
| const proofResult = await request( | |
| 'POST', | |
| '/mweb/v1/get_upload_image_proof', | |
| refreshToken, | |
| { | |
| data: { | |
| scene: isVideoImage ? 'video_cover' : 'aigc_image', | |
| file_name: filename, | |
| file_size: fileData.length, | |
| } | |
| } | |
| ); | |
| if (!proofResult || !proofResult.proof_info) { | |
| logger.error(`获取上传凭证失败: ${JSON.stringify(proofResult)}`); | |
| throw new APIException(EX.API_REQUEST_FAILED, '获取上传凭证失败'); | |
| } | |
| logger.info(`获取上传凭证成功`); | |
| // 上传文件 | |
| const { proof_info } = proofResult; | |
| logger.info(`开始上传文件到: ${uploadProofUrl}`); | |
| const uploadResult = await axios.post( | |
| uploadProofUrl, | |
| formData, | |
| { | |
| headers: { | |
| ...proof_info.headers, | |
| 'Content-Type': 'multipart/form-data', | |
| }, | |
| params: proof_info.query_params, | |
| timeout: 60000, | |
| validateStatus: () => true, // 允许任何状态码以便详细处理 | |
| } | |
| ); | |
| logger.info(`上传响应状态: ${uploadResult.status}`); | |
| if (!uploadResult || uploadResult.status !== 200) { | |
| logger.error(`上传文件失败: 状态码 ${uploadResult?.status}, 响应: ${JSON.stringify(uploadResult?.data)}`); | |
| throw new APIException(EX.API_REQUEST_FAILED, `上传文件失败: 状态码 ${uploadResult?.status}`); | |
| } | |
| // 验证 proof_info.image_uri 是否存在 | |
| if (!proof_info.image_uri) { | |
| logger.error(`上传凭证中缺少 image_uri: ${JSON.stringify(proof_info)}`); | |
| throw new APIException(EX.API_REQUEST_FAILED, '上传凭证中缺少 image_uri'); | |
| } | |
| logger.info(`文件上传成功: ${proof_info.image_uri}`); | |
| // 返回上传结果 | |
| return { | |
| image_uri: proof_info.image_uri, | |
| uri: proof_info.image_uri, | |
| } | |
| } catch (error) { | |
| logger.error(`文件上传过程中发生错误: ${error.message}`); | |
| throw error; | |
| } | |
| } | |
| /** | |
| * 检查请求结果 | |
| * | |
| * @param result 结果 | |
| */ | |
| export function checkResult(result: AxiosResponse) { | |
| const { ret, errmsg, data } = result.data; | |
| if (!_.isFinite(Number(ret))) return result.data; | |
| if (ret === '0') return data; | |
| if (ret === '5000') | |
| throw new APIException(EX.API_IMAGE_GENERATION_INSUFFICIENT_POINTS, `[无法生成图像]: 即梦积分可能不足,${errmsg}`); | |
| throw new APIException(EX.API_REQUEST_FAILED, `[请求jimeng失败]: ${errmsg}`); | |
| } | |
| /** | |
| * Token切分 | |
| * | |
| * @param authorization 认证字符串 | |
| */ | |
| export function tokenSplit(authorization: string) { | |
| const token = authorization.replace("Bearer ", "").trim(); | |
| const apiKey = getDreaminaApiKey(); | |
| if (provider.name === "dreamina-intl" && apiKey && token === apiKey) { | |
| const pool = getDreaminaSessionPool(); | |
| if (pool.length === 0) | |
| throw new APIException(EX.API_REQUEST_FAILED, "未配置国际版账号池 DREAMINA_COOKIE_POOL 或 DREAMINA_SESSIONIDS"); | |
| return pool; | |
| } | |
| return token.split(","); | |
| } | |
| /** | |
| * 获取Token存活状态 | |
| */ | |
| export async function getTokenLiveStatus(refreshToken: string) { | |
| const candidateRequests = [ | |
| { path: provider.userInfoPath, method: provider.userInfoMethod, params: provider.userInfoParams || {} }, | |
| ...(provider.userInfoFallbackPaths || []), | |
| ]; | |
| for (const candidate of candidateRequests) { | |
| try { | |
| const result = await request(candidate.method, candidate.path, refreshToken, { | |
| params: provider.name === "dreamina-intl" | |
| ? { | |
| aid: provider.assistantId, | |
| account_sdk_source: "web", | |
| sdk_version: "2.1.10-tiktok", | |
| language: provider.lan, | |
| verifyFp: provider.extraCookies?.s_v_web_id || undefined, | |
| ...(candidate.params || {}), | |
| } | |
| : (candidate.params || {}), | |
| }); | |
| const user = result?.data || result?.userInfo || result?.user_info || result; | |
| if (user?.user_id || user?.uid || user?.id || user?.user_id_str || user?.sec_user_id) { | |
| return true; | |
| } | |
| } catch (err) { | |
| logger.warn(`用户信息接口尝试失败: ${candidate.method} ${candidate.path} -> ${(err as Error).message}`); | |
| } | |
| } | |
| return false; | |
| } | |