feat: 人人视频增加调用APP API方式请求弹幕,原先方式作为兜底 (#80)

* 人人视频增加调用APP API方式请求弹幕,原先方式作为兜底

* 个别抽出数据模型

* 修改小版本号
This commit is contained in:
huangxd- authored and GitHub committed 2025-10-29 23:57:02 +08:00
1 parent 68e35e4f97
commit b7afcf08dd
8 files changed
+536 -72

No files matched your search

+39 -37
View File
@@ -19,6 +19,7 @@ import MangoSource from "../sources/mango.js";
import BilibiliSource from "../sources/bilibili.js";
import YoukuSource from "../sources/youku.js";
import OtherSource from "../sources/other.js";
import {Anime, AnimeMatch, Episodes, Bangumi} from "../models/dandan-model.js";
// =====================
// 兼容弹弹play接口
@@ -82,7 +83,7 @@ export async function searchAnime(url) {
// 链接弹幕解析
const urlRegex = /^(https?:\/\/)?([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,6}(:\d+)?(\/[^\s]*)?$/;
if (urlRegex.test(queryTitle)) {
const tmpAnime = {
const tmpAnime = Anime.fromJson({
"animeId": 111,
"bangumiId": "string",
"animeTitle": queryTitle,
@@ -93,7 +94,7 @@ export async function searchAnime(url) {
"episodeCount": 1,
"rating": 0,
"isFavorited": true
};
});
let platform = "unknown";
if (queryTitle.includes(".qq.com")) {
@@ -116,7 +117,7 @@ export async function searchAnime(url) {
"title": `【${platform}】 ${pageTitle}`
}];
curAnimes.push(tmpAnime);
addAnime({...tmpAnime, links: links});
addAnime(Anime.fromJson({...tmpAnime, links: links}));
if (globals.animes.length > globals.MAX_ANIMES) removeEarliestAnime();
// 如果有新的anime获取到,则更新redis
@@ -160,7 +161,10 @@ export async function searchAnime(url) {
});
// 解构出返回的结果
const { vod: animesVodResults, 360: animes360, renren: animesRenren, hanjutv: animesHanjutv, bahamut: animesBahamut, tencent: animesTencent, youku: animesYouku, iqiyi: animesIqiyi, imgo: animesImgo, bilibili: animesBilibili } = resultData;
const {
vod: animesVodResults, 360: animes360, renren: animesRenren, hanjutv: animesHanjutv, bahamut: animesBahamut,
tencent: animesTencent, youku: animesYouku, iqiyi: animesIqiyi, imgo: animesImgo, bilibili: animesBilibili
} = resultData;
// 按顺序处理每个来源的结果
for (const key of globals.sourceOrderArr) {
@@ -482,7 +486,7 @@ export async function matchAnime(url, req) {
if (resEpisode) {
resData["isMatched"] = true;
resData["matches"] = [
{
AnimeMatch.fromJson({
"episodeId": resEpisode.episodeId,
"animeId": resAnime.animeId,
"animeTitle": resAnime.animeTitle,
@@ -491,7 +495,7 @@ export async function matchAnime(url, req) {
"typeDescription": resAnime.typeDescription,
"shift": 0,
"imageUrl": resAnime.imageUrl
}
})
]
}
@@ -574,7 +578,7 @@ export async function searchEpisodes(url) {
// 只有当过滤后还有集数时才添加到结果中
if (filteredEpisodes.length > 0) {
resultAnimes.push({
resultAnimes.push(Episodes.fromJson({
animeId: animeItem.animeId,
animeTitle: animeItem.animeTitle,
type: animeItem.type,
@@ -583,7 +587,7 @@ export async function searchEpisodes(url) {
episodeId: ep.episodeId,
episodeTitle: ep.episodeTitle
}))
});
}));
}
}
}
@@ -624,33 +628,6 @@ export async function getBangumi(path) {
}
log("info", `Fetched details for anime ID: ${idParam}`);
let resData = {
errorCode: 0,
success: true,
errorMessage: "",
bangumi: {
animeId: anime.animeId,
bangumiId: anime.bangumiId,
animeTitle: anime.animeTitle,
imageUrl: anime.imageUrl,
isOnAir: true,
airDay: 1,
isFavorited: anime.isFavorited,
rating: anime.rating,
type: anime.type,
typeDescription: anime.typeDescription,
seasons: [
{
id: `season-${anime.animeId}`,
airDate: anime.startDate,
name: "Season 1",
episodeCount: anime.episodeCount,
},
],
episodes: [],
},
};
// 构建 episodes 列表
let episodesList = [];
for (let i = 0; i < anime.links.length; i++) {
@@ -681,9 +658,34 @@ export async function getBangumi(path) {
}
}
resData["bangumi"]["episodes"] = episodesList;
const bangumi = Bangumi.fromJson({
animeId: anime.animeId,
bangumiId: anime.bangumiId,
animeTitle: anime.animeTitle,
imageUrl: anime.imageUrl,
isOnAir: true,
airDay: 1,
isFavorited: anime.isFavorited,
rating: anime.rating,
type: anime.type,
typeDescription: anime.typeDescription,
seasons: [
{
id: `season-${anime.animeId}`,
airDate: anime.startDate,
name: "Season 1",
episodeCount: anime.episodeCount,
},
],
episodes: episodesList,
});
return jsonResponse(resData);
return jsonResponse({
errorCode: 0,
success: true,
errorMessage: "",
bangumi: bangumi
});
}
// Extracted function for GET /api/v2/comment/:commentId
+1 -1
View File
@@ -11,7 +11,7 @@ export const Globals = {
accessedEnvVars: {},
// 静态常量
VERSION: '1.6.2',
VERSION: '1.6.3',
MAX_LOGS: 500, // 日志存储,最多保存 500 行
MAX_ANIMES: 100,
MAX_LAST_SELECT_MAP: 100,
+248
View File
@@ -0,0 +1,248 @@
import { validateType } from "../utils/common-util.js";
// =====================
// 数据模型:Anime
// =====================
export class Anime {
constructor({ animeId = 111, bangumiId = "", animeTitle = "", type = "",
typeDescription = "", imageUrl = "", startDate = "", episodeCount = 1,
rating = 0, isFavorited = true, links = [] } = {}) {
// ---- 类型检查 ----
validateType(animeId, "number");
validateType(bangumiId, "string");
validateType(animeTitle, "string");
validateType(type, "string", "type");
validateType(typeDescription, "string");
validateType(imageUrl, "string");
validateType(startDate, "string");
validateType(episodeCount, "number");
validateType(rating, "number");
validateType(isFavorited, "boolean");
validateType(links, "array");
// 直接解构并赋值给 this
Object.assign(this, { animeId, bangumiId, animeTitle, type, typeDescription, imageUrl, startDate,
episodeCount, rating, isFavorited, links });
}
// ---- 静态方法:从 JSON 创建 User 对象 ----
static fromJson(json) {
if (typeof json !== "object" || json === null) {
throw new TypeError("fromJson 参数必须是对象");
}
return new Anime(json);
}
// ---- 转换为纯 JSON ----
toJson() {
return { ...this };
}
}
// =====================
// 数据模型:AnimeMatch
// =====================
export class AnimeMatch {
constructor({ episodeId = 10001, animeId = 111, animeTitle = "", episodeTitle = "",
type = "", typeDescription = "", shift = 1, imageUrl = "" } = {}) {
// ---- 类型检查 ----
validateType(episodeId, "number");
validateType(animeId, "number");
validateType(animeTitle, "string");
validateType(episodeTitle, "string");
validateType(type, "string", "type");
validateType(typeDescription, "string");
validateType(shift, "number");
validateType(imageUrl, "string");
// 直接解构并赋值给 this
Object.assign(this, { episodeId, animeId, animeTitle, episodeTitle, type, typeDescription, shift, imageUrl });
}
// ---- 静态方法:从 JSON 创建 User 对象 ----
static fromJson(json) {
if (typeof json !== "object" || json === null) {
throw new TypeError("fromJson 参数必须是对象");
}
return new AnimeMatch(json);
}
// ---- 转换为纯 JSON ----
toJson() {
return { ...this };
}
}
// =====================
// 数据模型:Episode
// =====================
export class Episode {
constructor({ episodeId = "", episodeTitle = "" } = {}) {
this.episodeId = episodeId;
this.episodeTitle = episodeTitle;
}
}
// Episode 的 toJson 方法
Episode.prototype.toJson = function () {
return {
episodeId: this.episodeId,
episodeTitle: this.episodeTitle
};
};
// =====================
// 数据模型:Episodes
// =====================
export class Episodes {
constructor({ animeId = 111, animeTitle = "", type = "", typeDescription = "",
episodes = [] } = {}) {
// ---- 类型检查 ----
validateType(animeId, "number");
validateType(animeTitle, "string");
validateType(type, "string");
validateType(typeDescription, "string");
validateType(episodes, "array");
// 直接解构并赋值给 this
Object.assign(this, { animeId, animeTitle, type, typeDescription,
episodes: episodes.map(ep => new Episode(ep)) });
}
// ---- 静态方法:从 JSON 创建 Episodes 对象 ----
static fromJson(json) {
if (typeof json !== "object" || json === null) {
throw new TypeError("fromJson 参数必须是对象");
}
return new Episodes(json);
}
// ---- 转换为纯 JSON ----
toJson() {
return {
animeId: this.animeId,
animeTitle: this.animeTitle,
type: this.type,
typeDescription: this.typeDescription,
episodes: this.episodes.map(ep => ep.toJson())
};
}
}
// =====================
// 数据模型:Season
// =====================
export class Season {
constructor({ id = "", airDate = "", name = "", episodeCount = 0 } = {}) {
validateType(id, "string");
validateType(airDate, "string");
validateType(name, "string");
validateType(episodeCount, "number");
// 直接解构并赋值给 this
Object.assign(this, { id, airDate, name, episodeCount });
}
// ---- 静态方法:从 JSON 创建 Season 对象 ----
static fromJson(json) {
if (typeof json !== "object" || json === null) {
throw new TypeError("fromJson 参数必须是对象");
}
return new Season(json);
}
// ---- 转换为纯 JSON ----
toJson() {
return { ...this };
}
}
// =====================
// 数据模型:BangumiEpisode
// =====================
export class BangumiEpisode {
constructor({ seasonId = "", episodeId = "", episodeTitle = "", episodeNumber = "",
airDate = "" } = {}) {
validateType(seasonId, "string");
validateType(episodeId, "number");
validateType(episodeTitle, "string");
validateType(episodeNumber, "string");
validateType(airDate, "string");
// 直接解构并赋值给 this
Object.assign(this, { seasonId, episodeId, episodeTitle, episodeNumber, airDate });
}
// ---- 静态方法:从 JSON 创建 BangumiEpisode 对象 ----
static fromJson(json) {
if (typeof json !== "object" || json === null) {
throw new TypeError("fromJson 参数必须是对象");
}
return new BangumiEpisode(json);
}
// ---- 转换为纯 JSON ----
toJson() {
return { ...this };
}
}
// =====================
// 数据模型:Bangumi
// =====================
export class Bangumi {
constructor({ animeId = 111, bangumiId = "", animeTitle = "", imageUrl = "",
isOnAir = true, airDay = 1, isFavorited = true, rating = 0,
type = "", typeDescription = "", seasons = [], episodes = [] } = {}) {
validateType(animeId, "number");
validateType(bangumiId, "string");
validateType(animeTitle, "string");
validateType(imageUrl, "string");
validateType(isOnAir, "boolean");
validateType(airDay, "number");
validateType(isFavorited, "boolean");
validateType(rating, "number");
validateType(type, "string");
validateType(typeDescription, "string");
validateType(seasons, "array");
validateType(episodes, "array");
// 将 seasons 转换为 Season 实例数组
const seasonInstances = seasons.map(seasonData => Season.fromJson(seasonData));
// 直接解构并赋值给 this
Object.assign(this, { animeId, bangumiId, animeTitle, imageUrl, isOnAir, airDay, isFavorited, rating,
type, typeDescription, seasons: seasonInstances, episodes });
}
// ---- 静态方法:从 JSON 创建 Bangumi 对象 ----
static fromJson(json) {
if (typeof json !== "object" || json === null) {
throw new TypeError("fromJson 参数必须是对象");
}
// 将 episodes 转换为 BangumiEpisode 实例数组
const episodes = json.episodes.map(ep => BangumiEpisode.fromJson(ep));
// 创建 Bangumi 实例
return new Bangumi({ ...json, episodes });
}
// ---- 转换为纯 JSON ----
toJson() {
return {
animeId: this.animeId,
bangumiId: this.bangumiId,
animeTitle: this.animeTitle,
imageUrl: this.imageUrl,
isOnAir: this.isOnAir,
airDay: this.airDay,
isFavorited: this.isFavorited,
rating: this.rating,
type: this.type,
typeDescription: this.typeDescription,
seasons: this.seasons.map(season => season.toJson()), // 转换每个 season 为 JSON
episodes: this.episodes.map(ep => ep.toJson()) // 转换每个 episode 为 JSON
};
}
}
+194 -28
View File
@@ -2,7 +2,7 @@ import BaseSource from './base.js';
import { globals } from '../configs/globals.js';
import { log } from "../utils/log-util.js";
import { getPathname, httpGet, sortedQueryString, updateQueryString } from "../utils/http-util.js";
import { autoDecode, createHmacSha256 } from "../utils/codec-util.js";
import { autoDecode, createHmacSha256, generateRandomSid, generateSign, generateXCaSign } from "../utils/codec-util.js";
import { generateValidStartDate } from "../utils/time-util.js";
import { addAnime, removeEarliestAnime } from "../utils/cache-util.js";
import { titleMatches } from "../utils/common-util.js";
@@ -11,6 +11,157 @@ import { titleMatches } from "../utils/common-util.js";
// 获取人人视频弹幕
// =====================
export default class RenrenSource extends BaseSource {
API_CONFIG = {
SECRET_KEY: "cf65GPholnICgyw1xbrpA79XVkizOdMq",
SEARCH_HOST: "api.qwdjapp.com",
DRAMA_HOST: "api.zhimeisj.top",
DANMU_HOST: "static-dm.qwdjapp.com",
USER_AGENT: 'Mozilla/5.0 (Linux; Android 15; PJC110 Build/AP3A.240617.008; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/140.0.7339.207 Mobile Safari/537.36 App/RRSPApp platform/android AppVersion/10.27.4'
};
generateAppCommonHeaders(timestamp, sign, xCaSign = null) {
const headers = {
'User-Agent': this.API_CONFIG.USER_AGENT,
'deviceId': 'fG1vO5jzBm22vJ5mfcCYGp2NrBii5SPysgiy%2FaUb63EOTrtXyXdxHm1cUajUR1zbszl62ApHyWc1GKZtH%2FbmF0UMZWgEetdDy9QVXd9WvPU%3D',
'aliId': 'aPuaf9shK3QDAL6WwVdhc7cC',
'umId': '380998657e22ed51b5a21f2b519aa5beod',
'clientType': 'android_rrsp_xb_RRSP',
't': timestamp.toString(),
'sign': sign,
'isAgree': '1',
'cv': '10.27.4'
};
if (xCaSign) {
headers['x-ca-sign'] = xCaSign;
headers['x-ca-method'] = '1';
}
return headers;
}
async searchAppContent(keyword, size = 15) {
try {
const timestamp = Date.now();
const path = "/search/content";
const queryParams = {
keywords: keyword,
size: size,
search_after: "",
order: "match",
isAgeLimit: false
};
const sign = generateSign(path, timestamp, queryParams, this.API_CONFIG.SECRET_KEY);
const queryString = Object.entries(queryParams)
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join('&');
const xCaSign = generateXCaSign(path, timestamp, queryString, this.API_CONFIG.SECRET_KEY);
const headers = this.generateAppCommonHeaders(timestamp, sign, xCaSign);
headers['Host'] = this.API_CONFIG.SEARCH_HOST;
const resp = await httpGet(`https://${this.API_CONFIG.SEARCH_HOST}${path}?${queryString}`, {
headers: headers,
});
if (!resp.data) return [];
const list = resp?.data?.data?.searchDramaList || [];
return list.map((item, idx) => ({
provider: "renren",
mediaId: String(item.id),
title: String(item.title || "").replace(/<[^>]+>/g, "").replace(/:/g, ":"),
type: "tv_series",
season: null,
year: item.year,
imageUrl: item.cover,
episodeCount: item.episodeTotal,
currentEpisodeIndex: null,
}));
} catch (error) {
log("error", "getRenrenAppAnimes error:", {
message: error.message,
name: error.name,
stack: error.stack,
});
return [];
}
}
async getAppDramaDetail(dramaId, episodeSid = "") {
try {
if (!episodeSid) episodeSid = generateRandomSid();
const timestamp = Date.now();
const path = "/app/drama/page";
const queryParams = {
isAgeLimit: false,
dramaId: dramaId,
episodeSid: episodeSid,
quality: "SD",
subtitle: 3,
hsdrOpen: 1,
hevcOpen: 1,
tria4k: 1
};
const sign = generateSign(path, timestamp, queryParams, this.API_CONFIG.SECRET_KEY);
const queryString = Object.entries(queryParams)
.map(([k, v]) => `${k}=${encodeURIComponent(v)}`)
.join('&');
const headers = this.generateAppCommonHeaders(timestamp, sign);
headers['Host'] = this.API_CONFIG.DRAMA_HOST;
headers['ignore'] = 'false';
const resp = await httpGet(`https://${this.API_CONFIG.DRAMA_HOST}${path}?${queryString}`, {
headers: headers
});
if (!resp.data) return null;
return resp.data;
} catch (error) {
log("error", "getRenrenAppDramaDetail error:", {
message: error.message,
name: error.name,
stack: error.stack,
});
return null;
}
}
// ========== 弹幕API ==========
async getAppDanmu(episodeSid) {
try {
const timestamp = Date.now();
const path = `/v1/produce/danmu/emo/EPISODE/${episodeSid}`;
const sign = generateSign(path, timestamp, {}, this.API_CONFIG.SECRET_KEY);
const xCaSign = generateXCaSign(path, timestamp, "", this.API_CONFIG.SECRET_KEY);
const headers = this.generateAppCommonHeaders(timestamp, sign, xCaSign);
headers['Host'] = this.API_CONFIG.DANMU_HOST;
const resp = await httpGet(`https://${this.API_CONFIG.DANMU_HOST}${path}`, {
headers: headers
});
if (!resp.data) return null;
return resp.data;
} catch (error) {
log("error", "getRenrenDramaDetail error:", {
message: error.message,
name: error.name,
stack: error.stack,
});
return null;
}
}
parseRRSPPFields(pField) {
const parts = String(pField).split(",");
const num = (i, cast, dft) => { try { return cast(parts[i]); } catch { return dft; } };
@@ -142,9 +293,14 @@ export default class RenrenSource extends BaseSource {
const searchTitle = parsedKeyword.title;
const searchSeason = parsedKeyword.season;
const lock = { value: false };
const lastRequestTime = { value: 0 };
let allResults = await this.performNetworkSearch(searchTitle, { lockRef: lock, lastRequestTimeRef: lastRequestTime, minInterval: 400 });
let allResults = [];
// 先使用APP API进行搜索
allResults = await this.searchAppContent(searchTitle);
if (allResults.length === 0) {
const lock = { value: false };
const lastRequestTime = { value: 0 };
allResults = await this.performNetworkSearch(searchTitle, { lockRef: lock, lastRequestTimeRef: lastRequestTime, minInterval: 400 });
}
if (searchSeason == null) return allResults;
@@ -153,12 +309,17 @@ export default class RenrenSource extends BaseSource {
}
async getDetail(id) {
const url = `https://api.rrmj.plus/m-station/drama/page`;
const params = { hsdrOpen:0,isAgeLimit:0,dramaId:String(id),hevcOpen:1 };
const resp = await this.renrenRequest("GET", url, params);
if (!resp.data) return null;
const decoded = autoDecode(resp.data);
return decoded?.data || null;
const resp = await this.getAppDramaDetail(String(id));
if (!resp) {
const url = `https://api.rrmj.plus/m-station/drama/page`;
const params = { hsdrOpen:0,isAgeLimit:0,dramaId:String(id),hevcOpen:1 };
const resp = await this.renrenRequest("GET", url, params);
if (!resp.data) return null;
const decoded = autoDecode(resp.data);
return decoded?.data || null;
} else {
return resp.data;
}
}
async getEpisodes(id) {
@@ -228,24 +389,29 @@ export default class RenrenSource extends BaseSource {
}
async getEpisodeDanmu(id) {
const ClientProfile = {
user_agent: "Mozilla/5.0",
origin: "https://rrsp.com.cn",
referer: "https://rrsp.com.cn/",
};
const url = `https://static-dm.rrmj.plus/v1/produce/danmu/EPISODE/${id}`;
const headers = {
"Accept": "application/json",
"User-Agent": ClientProfile.user_agent,
"Origin": ClientProfile.origin,
"Referer": ClientProfile.referer,
};
const resp = await this.renrenHttpGet(url, { headers });
if (!resp.data) return null;
const data = autoDecode(resp.data);
if (Array.isArray(data)) return data;
if (data?.data && Array.isArray(data.data)) return data.data;
return null;
const resp = await this.getAppDanmu(id);
if (!resp) {
const ClientProfile = {
user_agent: "Mozilla/5.0",
origin: "https://rrsp.com.cn",
referer: "https://rrsp.com.cn/",
};
const url = `https://static-dm.rrmj.plus/v1/produce/danmu/EPISODE/${id}`;
const headers = {
"Accept": "application/json",
"User-Agent": ClientProfile.user_agent,
"Origin": ClientProfile.origin,
"Referer": ClientProfile.referer,
};
const resp = await this.renrenHttpGet(url, { headers });
if (!resp.data) return null;
const data = autoDecode(resp.data);
if (Array.isArray(data)) return data;
if (data?.data && Array.isArray(data.data)) return data.data;
return null;
} else {
return resp;
}
}
formatComments(comments) {
+3 -1
View File
@@ -1,5 +1,6 @@
import { globals } from '../configs/globals.js';
import { log } from './log-util.js'
import { Anime } from "../models/dandan-model.js";
// =====================
// cache数据结构处理函数
@@ -140,6 +141,7 @@ export function findTitleById(id) {
// 添加 anime 对象到 animes,并将其 links 添加到 episodeIds
export function addAnime(anime) {
anime = Anime.fromJson(anime);
try {
// 确保 anime 有 links 属性且是数组
if (!anime.links || !Array.isArray(anime.links)) {
@@ -161,7 +163,7 @@ export function addAnime(anime) {
});
// 创建新的 anime 副本
const animeCopy = { ...anime, links: newLinks };
const animeCopy = Anime.fromJson({ ...anime, links: newLinks });
// 检查是否已存在相同 animeId 的 anime
const existingAnimeIndex = globals.animes.findIndex(a => a.animeId === anime.animeId);
+30 -1
View File
@@ -693,4 +693,33 @@ export function createHmacSha256(key, message) {
const hmacBytes = sha256(String.fromCharCode(...oKeyPad) + String.fromCharCode(...innerHash));
return bytesToBase64(hmacBytes);
}
}
// ========== 生成随机SID ==========
export function generateRandomSid() {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
return Array.from({length: 12}, () => chars[Math.floor(Math.random() * chars.length)]).join('');
}
// ========== 签名生成函数 ==========
export function generateSign(path, timestamp, params, secretKey) {
let signStr = path + "t" + timestamp;
if (params) {
const sortedKeys = Object.keys(params).sort();
sortedKeys.forEach(key => {
signStr += key + params[key];
});
}
signStr += secretKey;
return md5(signStr);
}
// ========== X-CA-Sign 生成函数 ==========
export function generateXCaSign(path, timestamp, queryString, secretKey) {
let signStr = `GET\n*/*\ngzip\n\nx-ca-method:1\n${path}`;
if (queryString) signStr += `?${queryString}`;
return createHmacSha256(secretKey, signStr);
}
+17
View File
@@ -225,4 +225,21 @@ export function titleMatches(title, query) {
// 宽松模糊匹配
return String(title).includes(String(query));
}
}
/**
* 数据类型校验
* @param {string} value - 值
* @param {string} expectedType - 期望类型
* @param {string} fieldName - 参数名称
*/
export function validateType(value, expectedType) {
const fieldName = value?.constructor?.name; // 获取字段名
if (expectedType === "array") {
if (!Array.isArray(value)) {
throw new TypeError(`${fieldName} 必须是一个数组`);
}
} else if (typeof value !== expectedType) {
throw new TypeError(`${fieldName} 必须是 ${expectedType}`);
}
}
+4 -4
View File
@@ -65,7 +65,7 @@ export async function httpGet(url, options = {}) {
try {
decodedData = await decompressedStream.text();
} catch (e) {
log("error", "[iOS模拟] 解压缩失败", e);
log("error", "[请求模拟] 解压缩失败", e);
throw e;
}
@@ -110,14 +110,14 @@ export async function httpGet(url, options = {}) {
// 检查是否是超时错误
if (error.name === 'AbortError') {
log("error", `[iOS模拟] 请求超时:`, error.message);
log("error", `[请求模拟] 请求超时:`, error.message);
log("error", '详细诊断:');
log("error", '- URL:', url);
log("error", '- 超时时间:', `${timeout}ms`);
throw new Error(`Request timeout after ${timeout}ms`);
}
log("error", `[iOS模拟] 请求失败:`, error.message);
log("error", `[请求模拟] 请求失败:`, error.message);
log("error", '详细诊断:');
log("error", '- URL:', url);
log("error", '- 错误类型:', error.name);
@@ -170,7 +170,7 @@ export async function httpPost(url, body, options = {}) {
};
} catch (error) {
log("error", `[iOS模拟] 请求失败:`, error.message);
log("error", `[请求模拟] 请求失败:`, error.message);
log("error", '详细诊断:');
log("error", '- URL:', url);
log("error", '- 错误类型:', error.name);