Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Post articles and image-text content to WeChat Official Account via API or Chrome CDP, with markdown-to-WeChat HTML conversion.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/wechat-socks-http.test.ts
1import assert from "node:assert/strict";2import http from "node:http";3import net from "node:net";4import test, { type TestContext } from "node:test";56import { createSocksClient } from "./wechat-socks-http.ts";78interface EchoServer {9baseUrl: string;10port: number;11received: Array<{ method: string; url: string; headers: http.IncomingHttpHeaders; body: Buffer }>;12}1314async function startEchoServer(t: TestContext): Promise<EchoServer> {15const received: EchoServer["received"] = [];16const server = http.createServer((req, res) => {17const chunks: Buffer[] = [];18req.on("data", (chunk: Buffer) => chunks.push(chunk));19req.on("end", () => {20received.push({21method: req.method ?? "",22url: req.url ?? "",23headers: req.headers,24body: Buffer.concat(chunks),25});26res.statusCode = 200;27res.setHeader("Content-Type", "application/json");28res.end(JSON.stringify({ ok: true, url: req.url }));29});30});31await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));32const address = server.address();33if (!address || typeof address === "string") throw new Error("echo server bind failed");34const port = address.port;35t.after(async () => {36await new Promise<void>((resolve) => server.close(() => resolve()));37});38return { baseUrl: `http://127.0.0.1:${port}`, port, received };39}4041interface FakeSocks5 {42port: number;43connectionCount: () => number;44destinations: () => Array<{ host: string; port: number }>;45}4647async function startFakeSocks5(t: TestContext): Promise<FakeSocks5> {48let connectionCount = 0;49const destinations: Array<{ host: string; port: number }> = [];5051const server = net.createServer((client) => {52connectionCount++;53let phase: "greeting" | "request" | "tunnel" = "greeting";54let buf = Buffer.alloc(0);55let upstream: net.Socket | undefined;5657const tryParse = () => {58if (phase === "greeting") {59if (buf.length < 2) return;60const nMethods = buf[1]!;61if (buf.length < 2 + nMethods) return;62buf = buf.subarray(2 + nMethods);63client.write(Buffer.from([0x05, 0x00]));64phase = "request";65}66if (phase === "request") {67if (buf.length < 5) return;68if (buf[0] !== 0x05 || buf[1] !== 0x01) {69client.destroy();70return;71}72const atyp = buf[3];73let addrEnd: number;74let host: string;75if (atyp === 0x01) {76if (buf.length < 4 + 4 + 2) return;77host = `${buf[4]}.${buf[5]}.${buf[6]}.${buf[7]}`;78addrEnd = 4 + 4;79} else if (atyp === 0x03) {80const dlen = buf[4]!;81if (buf.length < 4 + 1 + dlen + 2) return;82host = buf.subarray(5, 5 + dlen).toString("ascii");83addrEnd = 4 + 1 + dlen;84} else {85client.destroy();86return;87}88const port = (buf[addrEnd]! << 8) | buf[addrEnd + 1]!;89destinations.push({ host, port });90const totalLen = addrEnd + 2;91const remaining = buf.subarray(totalLen);92buf = Buffer.alloc(0);9394upstream = net.connect({ host, port }, () => {95client.write(Buffer.from([0x05, 0x00, 0x00, 0x01, 0, 0, 0, 0, 0, 0]));96phase = "tunnel";97if (remaining.length > 0) {98upstream!.write(remaining);99}100});101upstream.on("data", (data: Buffer) => {102client.write(data);103});104upstream.on("end", () => {105try { client.end(); } catch { /* noop */ }106});107upstream.on("error", () => {108try { client.destroy(); } catch { /* noop */ }109});110}111};112113client.on("data", (chunk: Buffer) => {114if (phase === "tunnel") {115upstream?.write(chunk);116return;117}118buf = Buffer.concat([buf, chunk]);119tryParse();120});121client.on("end", () => {122try { upstream?.end(); } catch { /* noop */ }123});124client.on("error", () => {125try { upstream?.destroy(); } catch { /* noop */ }126});127});128129await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));130const address = server.address();131if (!address || typeof address === "string") throw new Error("socks server bind failed");132const port = address.port;133t.after(async () => {134await new Promise<void>((resolve) => server.close(() => resolve()));135});136return {137port,138connectionCount: () => connectionCount,139destinations: () => destinations,140};141}142143test("createSocksClient routes plain HTTP through the SOCKS5 proxy", async (t) => {144const echo = await startEchoServer(t);145const socks = await startFakeSocks5(t);146147const client = createSocksClient({ host: "127.0.0.1", port: socks.port });148149const res = await client(`${echo.baseUrl}/cgi-bin/token?appid=AID`);150151assert.equal(res.status, 200);152const data = await res.json<{ ok: boolean; url: string }>();153assert.equal(data.ok, true);154assert.equal(data.url, "/cgi-bin/token?appid=AID");155156assert.equal(157socks.connectionCount(),1581,159"SOCKS5 proxy must have received exactly one connection (proves bytes were routed through it)",160);161162const dests = socks.destinations();163assert.equal(dests.length, 1);164assert.equal(dests[0]!.host, "127.0.0.1");165assert.equal(dests[0]!.port, echo.port);166167assert.equal(echo.received.length, 1);168assert.equal(echo.received[0]!.method, "GET");169assert.equal(echo.received[0]!.url, "/cgi-bin/token?appid=AID");170});171172test("createSocksClient sends POST body through the SOCKS5 proxy", async (t) => {173const echo = await startEchoServer(t);174const socks = await startFakeSocks5(t);175176const client = createSocksClient({ host: "127.0.0.1", port: socks.port });177178const body = JSON.stringify({ articles: [{ title: "hi" }] });179const res = await client(`${echo.baseUrl}/cgi-bin/draft/add?access_token=T`, {180method: "POST",181headers: { "Content-Type": "application/json" },182body,183});184185assert.equal(res.status, 200);186assert.equal(socks.connectionCount(), 1);187assert.equal(echo.received.length, 1);188assert.equal(echo.received[0]!.method, "POST");189assert.equal(echo.received[0]!.headers["content-type"], "application/json");190assert.equal(echo.received[0]!.headers["content-length"], String(Buffer.byteLength(body)));191assert.equal(echo.received[0]!.body.toString("utf-8"), body);192});193194test("createSocksClient rejects invalid proxy ports", () => {195assert.throws(196() => createSocksClient({ host: "127.0.0.1", port: 0 }),197/Invalid SOCKS proxy port/,198);199assert.throws(200() => createSocksClient({ host: "127.0.0.1", port: 70_000 }),201/Invalid SOCKS proxy port/,202);203assert.throws(204() => createSocksClient({ host: "", port: 1080 }),205/SOCKS proxy host required/,206);207});208