Smartart-Proxy/index.js

190 lines
7.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const express = require("express");
const playwright = require("playwright");
const app = express();
const port = 3004;
// const proxyUrl = "https://smartart-dev.felo.ai";
const proxyUrl = "http://localhost:5173/";
// 用户提供的,用于初始化图形的 JS 对象
const graphicInitPayload = {
type: "graphic:init",
value: {
tokens: {
login: "cW_39372b3655c84d058161000372da8ce8__",
anonymous: "a83f75a1fb5f2c701759320b8ead2042",
visitor: null,
},
options: {
uid: "1701151465403707393",
canEdit: true,
mode: "thread",
thread_id: "2svFUkJFEWZnrKZpM6gU7h",
message_id: "4dbec04b-a3b9-4465-af04-8259bebfa3ea",
language: "zh-Hans",
inputText:
'\n Query:介绍一下可琳威尔斯\n Answer:# 可琳·威克斯介绍\n\n可琳·威克斯Corin Wickes是米哈游开发的游戏《绝区零》中的一位A级物理强攻角色于2024年7月4日在游戏1.0版本正式上线[1][4]。\n\n## 基本信息\n\n- **全名**可琳·威克斯Corin Wickes[2]\n- **性别**:女[1][2]\n- **生日**6月2日[1]\n- **星座**:双子座[1]\n- **身高**141厘米[1]\n- **血型**Rh阴性[1]\n- **所属组织**:维多利亚家政[1][2]\n- **稀有度**A级[1][2][5]\n- **属性**:物理[1][5]\n- **特性**:强攻[1][5]\n- **配音演员**:沐霏(汉语)、五十岚裕美(日语)[1][11]\n\n## 角色特点\n\n可琳是维多利亚家政的女仆之一性格外柔内刚、乖巧谦逊拥有无法抛弃的责任心[1]。她非常认真负责,不惧任何脏活与累活,但同时极度缺乏自信,总是害怕被其他人讨厌,经常表现得慌慌张张[13]。\n\n根据维多利亚家政收到的客户反馈表显示可琳的服务能力获得了四星评价服务质量获得了三星半服务态度获得了两星半[1]。客户在备注中提到,虽然最初对可琳畏畏缩缩的态度感到不放心,但实际上她完成任务的质量很好,只是不明白为什么她总是一副担惊受怕的样子[1]。\n\n## 战斗特点\n\n可琳在战斗中使用一把电锯造型的扫除工具[1][19],她的战斗技能包括:\n\n1. **普通攻击:扫除开始** - 向前方进行至多五段的斩击,造成物理伤害,在第三、第五段中连点或长按时,可以使用电锯持续斩击敌人[19]。\n\n2. **闪避:[离]** - 快速的冲刺闪避[19]。\n\n她的专属装备"安心家用轮锯"提供以下效果位于后场时装备者的能量自动回复提升0.45点/秒;"强化特殊技"命中敌人时装备者造成的物理伤害提升3%4.8%最多叠加15层持续1秒[1]。\n\n## 获取方式\n\n玩家可以通过常驻频段"热门卡司"以及商城"丽都迎新"礼盒获得可琳·威克斯[1]。\n\n作为《绝区零》中的一位受欢迎角色可琳·威克斯以其独特的女仆形象和电锯武器设计吸引了许多玩家的喜爱[12][15]。',
smart_art_id: "1934511766443233282",
},
},
};
// 全局浏览器实例
let browser;
/**
* 核心渲染函数
* @returns {Promise<object>} 从页面获取到的图形数据
*/
async function renderGraphic() {
const context = await browser.newContext();
// 将所有浏览器的 console 日志转发到 Node.js 的终端
const page = await context.newPage();
// page.on('console', msg => {
// // 过滤掉一些不重要的 vite 日志,让输出更清晰
// const text = msg.text();
// if (text.includes('[vite]')) return;
// console.log(`[Browser Console] ${msg.type().toUpperCase()}: ${text}`);
// });
// 设置一个Promise用于监听从页面回传的最终数据
// 我们将 exposeFunction 和 addInitScript 绑定到 context 而不是 page这更可靠
const finalDataPromise = new Promise(async (resolve, reject) => {
// 1. 在 Context 上暴露函数
await context
.exposeFunction("onGraphicData", (data) => {
console.log(
"Node.js: onGraphicData function was called from the browser."
);
resolve(data);
})
.catch(reject);
console.log(
"Node.js: exposeFunction and addInitScript have been set up on the context."
);
});
// await page.addInitScript(() => {
// console.log("INIT SCRIPT: This script is running from page.addInitScript!");
// });
// 2. 在 Context 上添加初始化脚本
await context.addInitScript(() => {
// 这个 console.log 现在应该可以被 Node.js 终端捕获到
console.log(
"INIT SCRIPT: This script is running from context.addInitScript!"
);
window.addEventListener("message", (event) => {
// 为了调试,我们打印收到的消息类型
if (event.data && event.data.type) {
console.log(
`INIT SCRIPT: Message received in browser - Type: ${event.data.type}`
);
}
// 根据用户提供的返回数据格式,判断是否是我们需要的数据
if (
event.data &&
event.data.value &&
(event.data.value.svg || event.data.value.png)
) {
// 如果是则调用从Node.js注入的函数将数据传递回来
// window.onGraphicData(event.data);
}
});
});
try {
// 3. 导航到目标网页
console.log("Node.js: Navigating to page...");
await page.goto(proxyUrl, {
waitUntil: "domcontentloaded",
});
console.log("Node.js: Page navigation complete.");
// 4. 注入登录态和图形信息
console.log("Node.js: Posting graphic:init message...");
await page.evaluate((payload) => {
window.postMessage(payload, "*");
}, graphicInitPayload);
// 5. 等待图形加载完成的标志——`.editor-controls` 元素出现
console.log("Node.js: Waiting for selector .editor-controls...");
await page.waitForSelector(".editor-controls", { timeout: 30000 }); // 设置30秒超时
console.log("Node.js: Selector .editor-controls found.");
// 6. 请求图片数据
console.log("Node.js: Posting graphic:handlessRequestData message...");
await page.evaluate(() => {
window.postMessage({ type: "graphic:handlessRequestData" }, "*");
});
// 7. 等待 `finalDataPromise` 完成,以获取最终的图形数据
console.log("Waiting for graphic data from the page...");
const graphicData = await finalDataPromise;
console.log("Graphic data received.");
return graphicData;
} finally {
// 8. 确保页面和上下文被关闭,释放资源
await page.close();
await context.close();
}
}
app.get("/render", async (req, res) => {
console.log("Received a request to /render");
try {
const data = await renderGraphic();
// 返回从页面获取到的 value 对象
res.setHeader("Content-Type", "application/json");
console.log(data);
res.json(data);
} catch (error) {
console.error("Failed to render graphic:", error);
res
.status(500)
.json({ error: "Failed to render graphic", details: error.message });
}
});
// 启动服务
const server = app.listen(port, async () => {
try {
// 启动 Playwright 浏览器实例
// 使用 headless: false 可以在本地看到浏览器操作,方便调试
browser = await playwright.chromium.launch({ headless: false });
console.log(
`Playwright-based graphic rendering service listening at http://localhost:${port}`
);
console.log(
`Please make sure your frontend dev server is running on ${proxyUrl}`
);
console.log(`To render a graphic, access: http://localhost:${port}/render`);
} catch (error) {
console.error("Failed to launch browser:", error);
process.exit(1);
}
});
// 优雅地关闭浏览器和服务器
const cleanup = async () => {
console.log("Closing server and browser...");
if (browser) {
await browser.close();
}
server.close(() => {
console.log("Server closed.");
process.exit(0);
});
};
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);