import { createServer } from "node:http";
const PORT = 8976;
let messageQueue: Array<{ from_user: string; content: string; msg_id: string }> = [];
let waitingPolls: Array<{ resolve: (value: string) => void; timer: NodeJS.Timeout }> = [];
const server = createServer((req, res) => {
const url = new URL(req.url!, `http://localhost:${PORT}`);
if (req.method === "GET" && url.pathname === "/getupdates") {
const timeout = parseInt(url.searchParams.get("timeout") ?? "10", 10) * 1000;
if (messageQueue.length > 0) {
const messages = [...messageQueue];
messageQueue = [];
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ updates: messages, next_offset: String(Date.now()) }));
return;
}
const poll = {
resolve: (body: string) => {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(body);
},
timer: setTimeout(() => {
waitingPolls = waitingPolls.filter((p) => p !== poll);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ updates: [], next_offset: String(Date.now()) }));
}, timeout),
};
waitingPolls.push(poll);
return;
}
if (req.method === "POST" && url.pathname === "/sendmessage") {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const data = JSON.parse(body);
console.log(`\n[iLink mock] 收到回复 -> to_user=${data.to_user}`);
console.log(`[iLink mock] 内容:\n${data.content}\n`);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ errcode: 0, msg_id: `reply_${Date.now()}` }));
});
return;
}
if (req.method === "POST" && url.pathname === "/inject") {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const data = JSON.parse(body);
const msg = {
from_user: data.from_user ?? "test_user",
content: data.content ?? "你好",
msg_id: `msg_${Date.now()}`,
chat_id: data.chat_id ?? data.from_user ?? "test_user",
msg_type: "text",
};
console.log(`[iLink mock] 注入消息: from=${msg.from_user} content="${msg.content}"`);
if (waitingPolls.length > 0) {
const poll = waitingPolls.shift()!;
clearTimeout(poll.timer);
poll.resolve(JSON.stringify({ updates: [msg], next_offset: String(Date.now()) }));
} else {
messageQueue.push(msg);
}
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true }));
});
return;
}
res.writeHead(404);
res.end("not found");
});
server.listen(PORT, () => {
console.log(`[iLink mock] Mock iLink server running on http://127.0.0.1:${PORT}`);
console.log(`[iLink mock] 接口:`);
console.log(` GET /getupdates - WeixinChannel long-poll 端点`);
console.log(` POST /sendmessage - WeixinChannel 发送回复端点`);
console.log(` POST /inject - 手动注入测试消息`);
console.log(`\n[iLink mock] 用法: curl -X POST http://127.0.0.1:${PORT}/inject -H 'Content-Type: application/json' -d '{"content":"你好"}'`);
});