mock-ilink.ts 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. import { createServer } from "node:http";
  2. const PORT = 8976;
  3. let messageQueue: Array<{ from_user: string; content: string; msg_id: string }> = [];
  4. let waitingPolls: Array<{ resolve: (value: string) => void; timer: NodeJS.Timeout }> = [];
  5. const server = createServer((req, res) => {
  6. const url = new URL(req.url!, `http://localhost:${PORT}`);
  7. if (req.method === "GET" && url.pathname === "/getupdates") {
  8. const timeout = parseInt(url.searchParams.get("timeout") ?? "10", 10) * 1000;
  9. if (messageQueue.length > 0) {
  10. const messages = [...messageQueue];
  11. messageQueue = [];
  12. res.writeHead(200, { "Content-Type": "application/json" });
  13. res.end(JSON.stringify({ updates: messages, next_offset: String(Date.now()) }));
  14. return;
  15. }
  16. const poll = {
  17. resolve: (body: string) => {
  18. res.writeHead(200, { "Content-Type": "application/json" });
  19. res.end(body);
  20. },
  21. timer: setTimeout(() => {
  22. waitingPolls = waitingPolls.filter((p) => p !== poll);
  23. res.writeHead(200, { "Content-Type": "application/json" });
  24. res.end(JSON.stringify({ updates: [], next_offset: String(Date.now()) }));
  25. }, timeout),
  26. };
  27. waitingPolls.push(poll);
  28. return;
  29. }
  30. if (req.method === "POST" && url.pathname === "/sendmessage") {
  31. let body = "";
  32. req.on("data", (chunk) => (body += chunk));
  33. req.on("end", () => {
  34. const data = JSON.parse(body);
  35. console.log(`\n[iLink mock] 收到回复 -> to_user=${data.to_user}`);
  36. console.log(`[iLink mock] 内容:\n${data.content}\n`);
  37. res.writeHead(200, { "Content-Type": "application/json" });
  38. res.end(JSON.stringify({ errcode: 0, msg_id: `reply_${Date.now()}` }));
  39. });
  40. return;
  41. }
  42. if (req.method === "POST" && url.pathname === "/inject") {
  43. let body = "";
  44. req.on("data", (chunk) => (body += chunk));
  45. req.on("end", () => {
  46. const data = JSON.parse(body);
  47. const msg = {
  48. from_user: data.from_user ?? "test_user",
  49. content: data.content ?? "你好",
  50. msg_id: `msg_${Date.now()}`,
  51. chat_id: data.chat_id ?? data.from_user ?? "test_user",
  52. msg_type: "text",
  53. };
  54. console.log(`[iLink mock] 注入消息: from=${msg.from_user} content="${msg.content}"`);
  55. if (waitingPolls.length > 0) {
  56. const poll = waitingPolls.shift()!;
  57. clearTimeout(poll.timer);
  58. poll.resolve(JSON.stringify({ updates: [msg], next_offset: String(Date.now()) }));
  59. } else {
  60. messageQueue.push(msg);
  61. }
  62. res.writeHead(200, { "Content-Type": "application/json" });
  63. res.end(JSON.stringify({ ok: true }));
  64. });
  65. return;
  66. }
  67. res.writeHead(404);
  68. res.end("not found");
  69. });
  70. server.listen(PORT, () => {
  71. console.log(`[iLink mock] Mock iLink server running on http://127.0.0.1:${PORT}`);
  72. console.log(`[iLink mock] 接口:`);
  73. console.log(` GET /getupdates - WeixinChannel long-poll 端点`);
  74. console.log(` POST /sendmessage - WeixinChannel 发送回复端点`);
  75. console.log(` POST /inject - 手动注入测试消息`);
  76. console.log(`\n[iLink mock] 用法: curl -X POST http://127.0.0.1:${PORT}/inject -H 'Content-Type: application/json' -d '{"content":"你好"}'`);
  77. });