import http from "node:http"; import { execFile } from "node:child_process"; import { readFile } from "node:fs/promises"; import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const pagePaths = new Map([ ["/", join(__dirname, "baishun-game-test.html")], ["/baishun-game-test.html", join(__dirname, "baishun-game-test.html")], ["/lucky-gift-test.html", join(__dirname, "lucky-gift-test.html")], ]); const port = Number(process.env.PORT || 8787); const upstreamBase = (process.env.BAISHUN_API_BASE || "https://api-test.global-interaction.com").replace(/\/+$/, ""); function readBody(req) { return new Promise((resolve, reject) => { const chunks = []; req.on("data", (chunk) => chunks.push(chunk)); req.on("end", () => resolve(Buffer.concat(chunks))); req.on("error", reject); }); } function copyProxyHeaders(req) { const headers = new Headers(); for (const [key, value] of Object.entries(req.headers)) { if (!value) continue; const lower = key.toLowerCase(); if (["host", "origin", "referer", "connection", "content-length", "accept-encoding"].includes(lower)) continue; headers.set(key, Array.isArray(value) ? value.join(", ") : value); } headers.set("Accept-Encoding", "identity"); return headers; } async function proxyApi(req, res) { const targetUrl = `${upstreamBase}${req.url}`; const body = req.method === "GET" || req.method === "HEAD" ? undefined : await readBody(req); const upstream = await fetch(targetUrl, { method: req.method, headers: copyProxyHeaders(req), body, redirect: "manual", }); const responseBody = Buffer.from(await upstream.arrayBuffer()); res.writeHead(upstream.status, { "Content-Type": upstream.headers.get("content-type") || "application/octet-stream", "Cache-Control": "no-store", }); res.end(responseBody); } function sqlString(value) { return `'${String(value ?? "").replaceAll("\\", "\\\\").replaceAll("'", "''")}'`; } function runMysqlJSON(sql) { return new Promise((resolve, reject) => { execFile( "docker", ["exec", "hyapp-mysql", "mysql", "-uroot", "-proot", "--batch", "--raw", "--skip-column-names", "-e", sql], { maxBuffer: 1024 * 1024 }, (err, stdout, stderr) => { if (err) { reject(new Error((stderr || err.message).trim())); return; } const raw = stdout.trim(); if (!raw) { resolve({}); return; } try { resolve(JSON.parse(raw.split(/\r?\n/).at(-1))); } catch { reject(new Error(`mysql json parse failed: ${raw}`)); } }, ); }); } async function luckyGiftStats(req, res, url) { const appCode = url.searchParams.get("app_code") || "lalu"; const userID = url.searchParams.get("user_id") || ""; const poolID = url.searchParams.get("pool_id") || ""; if (!/^\d+$/.test(userID)) { res.writeHead(400, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); res.end(JSON.stringify({ code: "INVALID_ARGUMENT", message: "user_id is required" })); return; } const appSQL = sqlString(appCode); const userSQL = sqlString(userID); const poolSQL = sqlString(poolID); const poolFilter = poolID ? `AND pool_id = ${poolSQL}` : ""; const statePoolFilter = poolID ? `AND gift_id = ${poolSQL}` : ""; const sql = ` SELECT JSON_OBJECT( 'app_code', ${appSQL}, 'user_id', CAST(${userSQL} AS UNSIGNED), 'pool_id', ${poolSQL}, 'draw_count', COALESCE((SELECT COUNT(*) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0), 'win_count', COALESCE((SELECT COUNT(*) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter} AND effective_reward_coins > 0), 0), 'granted_count', COALESCE((SELECT COUNT(*) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter} AND reward_status = 'granted'), 0), 'coin_spent', COALESCE((SELECT SUM(coin_spent) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0), 'base_reward_coins', COALESCE((SELECT SUM(base_reward_coins) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0), 'effective_reward_coins', COALESCE((SELECT SUM(effective_reward_coins) FROM hyapp_activity.lucky_draw_records WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${poolFilter}), 0), 'state_paid_draws', COALESCE((SELECT SUM(paid_draws) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0), 'state_cumulative_wager_coins', COALESCE((SELECT SUM(cumulative_wager_coins) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0), 'state_equivalent_draws', COALESCE((SELECT MAX(equivalent_draws) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0), 'loss_streak', COALESCE((SELECT MAX(loss_streak) FROM hyapp_activity.lucky_user_states WHERE app_code = ${appSQL} AND user_id = CAST(${userSQL} AS UNSIGNED) ${statePoolFilter}), 0) ) `; const data = await runMysqlJSON(sql); res.writeHead(200, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); res.end(JSON.stringify({ code: "OK", data })); } const server = http.createServer(async (req, res) => { try { if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; } const url = new URL(req.url || "/", `http://${req.headers.host || "127.0.0.1"}`); const pagePath = pagePaths.get(url.pathname); if (pagePath) { const html = await readFile(pagePath); res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store", }); res.end(html); return; } if (req.url?.startsWith("/api/")) { await proxyApi(req, res); return; } if (url.pathname === "/__local/lucky-gift/stats") { await luckyGiftStats(req, res, url); return; } res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" }); res.end("not found"); } catch (err) { res.writeHead(502, { "Content-Type": "text/plain; charset=utf-8" }); res.end(err instanceof Error ? err.message : String(err)); } }); server.listen(port, "127.0.0.1", () => { console.log(`Baishun game test page: http://127.0.0.1:${port}/baishun-game-test.html`); console.log(`Lucky gift test page: http://127.0.0.1:${port}/lucky-gift-test.html`); console.log(`Proxy upstream: ${upstreamBase}`); });