pi-harness-runtime 1.1.375 → 1.1.376
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +68 -39
- package/package.json +2 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/agent-policy/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/codex-watcher/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/connection-runtime/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/contracts/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/durable-review/package.json +1 -1
- package/packages/e2e-tester/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-sink/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/paperclip-integration/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/permission-mode/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/package.json +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/relay/dist/ws-server.d.ts +48 -0
- package/packages/relay/dist/ws-server.js +130 -0
- package/packages/relay/package.json +1 -1
- package/packages/relay/src/ws-server.ts +165 -0
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/telegram/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/voice-api/package.json +1 -1
- package/packages/webmcp-tester/package.json +1 -1
- package/packages/wiki/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/scripts/relay-client.ts +175 -0
- package/scripts/sync-callback-queue.ts +57 -0
- package/scripts/telegram-callback-processor.ts +16 -0
- package/scripts/unified-webhook-server.ts +142 -129
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
/**
|
|
3
|
-
* Unified Webhook Server — Handles
|
|
3
|
+
* Unified Webhook Server — Handles Telegram, LINE, and WebSocket relay
|
|
4
4
|
*
|
|
5
5
|
* Routes:
|
|
6
6
|
* /telegram → Telegram webhook handler
|
|
7
7
|
* /line → LINE webhook handler
|
|
8
|
+
* /ws → WebSocket relay (port 8444)
|
|
8
9
|
*
|
|
9
10
|
* Usage:
|
|
10
11
|
* bun run scripts/unified-webhook-server.ts
|
|
11
|
-
*
|
|
12
|
-
* Environment variables (or keys/*.txt files):
|
|
13
|
-
* TELEGRAM_BOT_TOKEN, TELEGRAM_WEBHOOK_SECRET, TELEGRAM_ALLOWED_USERS
|
|
14
|
-
* LINE_CHANNEL_SECRET, LINE_ACCESS_TOKEN
|
|
15
|
-
*
|
|
16
|
-
* Webhook URLs to configure:
|
|
17
|
-
* Telegram: https://webhook.bunchee.online/telegram
|
|
18
|
-
* LINE: https://webhook.bunchee.online/line
|
|
19
12
|
*/
|
|
20
13
|
|
|
21
14
|
import { existsSync, readFileSync, appendFileSync } from "node:fs";
|
|
22
15
|
import { join } from "node:path";
|
|
16
|
+
import { homedir } from "os";
|
|
23
17
|
import { createServer, IncomingMessage, ServerResponse } from "node:http";
|
|
18
|
+
import { WebSocketServer, WebSocket } from "ws";
|
|
19
|
+
import { randomUUID } from "node:crypto";
|
|
24
20
|
|
|
25
21
|
// --- Configuration ---
|
|
26
22
|
interface Config {
|
|
@@ -30,68 +26,58 @@ interface Config {
|
|
|
30
26
|
allowedUsers: Set<number>;
|
|
31
27
|
};
|
|
32
28
|
line?: {
|
|
33
|
-
channelSecret: string;
|
|
34
29
|
accessToken: string;
|
|
30
|
+
channelSecret: string;
|
|
35
31
|
};
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
34
|
+
const PORT = parseInt(process.env.PORT || "8443", 10);
|
|
35
|
+
const WS_PORT = parseInt(process.env.WS_PORT || "8444", 10);
|
|
36
|
+
const RELAY_AUTH_TOKEN = process.env.RELAY_AUTH_TOKEN || "b61940936a2625036d6faf7fe32c52b3"; // Same as webhook secret
|
|
37
|
+
|
|
38
|
+
// --- Keys Loader ---
|
|
39
|
+
function loadConfig(): Config {
|
|
40
|
+
const keysDir = join(homedir(), ".pi-harness-runtime", "keys");
|
|
41
41
|
const config: Config = {};
|
|
42
42
|
|
|
43
|
-
// Telegram
|
|
43
|
+
// Telegram
|
|
44
44
|
const telegramTokenPath = join(keysDir, "telegram-bot-token.txt");
|
|
45
45
|
const telegramSecretPath = join(keysDir, "telegram-webhook-secret.txt");
|
|
46
46
|
const telegramUsersPath = join(keysDir, "telegram-allowed-users.txt");
|
|
47
47
|
|
|
48
48
|
if (existsSync(telegramTokenPath)) {
|
|
49
|
-
const token =
|
|
50
|
-
const secret =
|
|
49
|
+
const token = readFileSync(telegramTokenPath, "utf8").trim();
|
|
50
|
+
const secret = existsSync(telegramSecretPath)
|
|
51
|
+
? readFileSync(telegramSecretPath, "utf8").trim()
|
|
52
|
+
: process.env.TELEGRAM_WEBHOOK_SECRET || "";
|
|
51
53
|
|
|
52
54
|
const allowedUsers = new Set<number>();
|
|
53
|
-
const envUsers = process.env.TELEGRAM_ALLOWED_USERS;
|
|
54
|
-
if (envUsers) {
|
|
55
|
-
for (const part of envUsers.split(",")) {
|
|
56
|
-
const num = parseInt(part.trim(), 10);
|
|
57
|
-
if (!isNaN(num) && num > 0) allowedUsers.add(num);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
55
|
if (existsSync(telegramUsersPath)) {
|
|
61
|
-
const
|
|
62
|
-
for (const
|
|
63
|
-
|
|
64
|
-
if (!isNaN(num) && num > 0) allowedUsers.add(num);
|
|
56
|
+
const users = readFileSync(telegramUsersPath, "utf8").trim();
|
|
57
|
+
for (const id of users.split(",").map((s) => parseInt(s.trim(), 10))) {
|
|
58
|
+
if (!isNaN(id) && id > 0) allowedUsers.add(id);
|
|
65
59
|
}
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
config.telegram = { botToken: token, secret, allowedUsers };
|
|
69
63
|
}
|
|
70
64
|
|
|
71
|
-
// LINE
|
|
72
|
-
const lineSecretPath = join(keysDir, "line-channel-secret.txt");
|
|
65
|
+
// LINE
|
|
73
66
|
const lineTokenPath = join(keysDir, "line-access-token.txt");
|
|
67
|
+
const lineSecretPath = join(keysDir, "line-channel-secret.txt");
|
|
74
68
|
|
|
75
|
-
if (existsSync(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
if (existsSync(lineTokenPath) && existsSync(lineSecretPath)) {
|
|
70
|
+
config.line = {
|
|
71
|
+
accessToken: readFileSync(lineTokenPath, "utf8").trim(),
|
|
72
|
+
channelSecret: readFileSync(lineSecretPath, "utf8").trim(),
|
|
73
|
+
};
|
|
79
74
|
}
|
|
80
75
|
|
|
81
76
|
return config;
|
|
82
77
|
}
|
|
83
78
|
|
|
84
|
-
// ---
|
|
85
|
-
|
|
86
|
-
function log(prefix: string, ...args: unknown[]) {
|
|
87
|
-
const msg = args.map(a => typeof a === "object" ? JSON.stringify(a) : String(a)).join(" ");
|
|
88
|
-
const line = `[${new Date().toISOString()}] [${prefix}] ${msg}\n`;
|
|
89
|
-
appendFileSync(LOG_FILE, line);
|
|
90
|
-
console.log(line.trim());
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// --- Crypto utilities ---
|
|
94
|
-
async function verifyTelegram(data: string, secret: string, hash: string): Promise<boolean> {
|
|
79
|
+
// --- Telegram HMAC Verification (for future use with secret hash) ---
|
|
80
|
+
async function _verifyTelegramHmac(data: string, secret: string, hash: string): Promise<boolean> {
|
|
95
81
|
const encoder = new TextEncoder();
|
|
96
82
|
const key = await crypto.subtle.importKey(
|
|
97
83
|
"raw", encoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]
|
|
@@ -101,6 +87,7 @@ async function verifyTelegram(data: string, secret: string, hash: string): Promi
|
|
|
101
87
|
return expected === hash;
|
|
102
88
|
}
|
|
103
89
|
|
|
90
|
+
// --- LINE Verification ---
|
|
104
91
|
async function verifyLINE(body: string, channelSecret: string, xLineSignature: string): Promise<boolean> {
|
|
105
92
|
const encoder = new TextEncoder();
|
|
106
93
|
const key = await crypto.subtle.importKey(
|
|
@@ -111,13 +98,57 @@ async function verifyLINE(body: string, channelSecret: string, xLineSignature: s
|
|
|
111
98
|
return expected === xLineSignature;
|
|
112
99
|
}
|
|
113
100
|
|
|
114
|
-
// --- Callback
|
|
101
|
+
// --- Callback Queue (file-based fallback) ---
|
|
115
102
|
const QUEUE_FILE = join(process.env.HOME || "/tmp", ".pi-harness-runtime", "callback-queue.jsonl");
|
|
116
103
|
function queueCallback(callback: Record<string, unknown>) {
|
|
117
104
|
appendFileSync(QUEUE_FILE, JSON.stringify({ ...callback, timestamp: new Date().toISOString() }) + "\n");
|
|
118
105
|
}
|
|
119
106
|
|
|
120
|
-
// ---
|
|
107
|
+
// --- WebSocket Relay ---
|
|
108
|
+
const wsClients = new Map<string, WebSocket>();
|
|
109
|
+
|
|
110
|
+
function broadcastCallback(callback: Record<string, unknown>) {
|
|
111
|
+
const msg = JSON.stringify({ type: "callback", payload: callback });
|
|
112
|
+
let count = 0;
|
|
113
|
+
for (const ws of wsClients.values()) {
|
|
114
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
115
|
+
ws.send(msg);
|
|
116
|
+
count++;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return count;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function startWebSocketServer() {
|
|
123
|
+
const wss = new WebSocketServer({ port: WS_PORT });
|
|
124
|
+
|
|
125
|
+
wss.on("connection", (ws: WebSocket) => {
|
|
126
|
+
const clientId = randomUUID();
|
|
127
|
+
wsClients.set(clientId, ws);
|
|
128
|
+
console.log(`[WS] Client connected: ${clientId}`);
|
|
129
|
+
|
|
130
|
+
ws.on("message", (data) => {
|
|
131
|
+
try {
|
|
132
|
+
const msg = JSON.parse(data.toString());
|
|
133
|
+
if (msg.type === "auth" && msg.payload === RELAY_AUTH_TOKEN) {
|
|
134
|
+
ws.send(JSON.stringify({ type: "auth", payload: { success: true } }));
|
|
135
|
+
console.log(`[WS] Client authenticated: ${clientId}`);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
console.error(`[WS] Invalid message from ${clientId}:`, err);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
ws.on("close", () => {
|
|
143
|
+
wsClients.delete(clientId);
|
|
144
|
+
console.log(`[WS] Client disconnected: ${clientId}`);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
console.log(`[WS] WebSocket server started on port ${WS_PORT}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// --- Telegram Handler ---
|
|
121
152
|
async function handleTelegram(req: IncomingMessage, res: ServerResponse, config: Config) {
|
|
122
153
|
if (!config.telegram) {
|
|
123
154
|
res.writeHead(404);
|
|
@@ -128,7 +159,6 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
|
|
|
128
159
|
// Verify secret token
|
|
129
160
|
const secretToken = req.headers["x-telegram-bot-api-secret-token"];
|
|
130
161
|
if (secretToken !== config.telegram.secret) {
|
|
131
|
-
log("Telegram", "Unauthorized - secret mismatch");
|
|
132
162
|
res.writeHead(403);
|
|
133
163
|
res.end("Forbidden");
|
|
134
164
|
return;
|
|
@@ -149,7 +179,6 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
|
|
|
149
179
|
|
|
150
180
|
// Check authorization
|
|
151
181
|
if (config.telegram.allowedUsers.size > 0 && !config.telegram.allowedUsers.has(from.id)) {
|
|
152
|
-
log("Telegram", `Unauthorized user: ${from.id}`);
|
|
153
182
|
res.writeHead(403);
|
|
154
183
|
res.end("Forbidden");
|
|
155
184
|
return;
|
|
@@ -171,15 +200,22 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
|
|
|
171
200
|
targetId = "";
|
|
172
201
|
}
|
|
173
202
|
|
|
174
|
-
|
|
175
|
-
queueCallback({
|
|
203
|
+
const callback = {
|
|
176
204
|
type: "telegram_callback",
|
|
177
205
|
action,
|
|
178
206
|
targetId,
|
|
179
207
|
userId: from.id,
|
|
180
208
|
username: from.username,
|
|
181
209
|
messageId: query.message?.message_id,
|
|
182
|
-
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
// Broadcast via WebSocket
|
|
213
|
+
const wsCount = broadcastCallback(callback);
|
|
214
|
+
if (wsCount === 0) {
|
|
215
|
+
// Fallback to file queue
|
|
216
|
+
queueCallback(callback);
|
|
217
|
+
}
|
|
218
|
+
console.log(`[Telegram] Callback: ${action} ${targetId} from @${from.username || from.id} (WS clients: ${wsCount})`);
|
|
183
219
|
|
|
184
220
|
// Acknowledge immediately
|
|
185
221
|
await fetch(`https://api.telegram.org/bot${config.telegram.botToken}/answerCallbackQuery`, {
|
|
@@ -187,20 +223,18 @@ async function handleTelegram(req: IncomingMessage, res: ServerResponse, config:
|
|
|
187
223
|
headers: { "Content-Type": "application/json" },
|
|
188
224
|
body: JSON.stringify({ callback_query_id: query.id }),
|
|
189
225
|
});
|
|
190
|
-
|
|
191
|
-
log("Telegram", `Callback: ${action} ${targetId} from @${from.username || from.id}`);
|
|
192
226
|
}
|
|
193
227
|
|
|
194
228
|
res.writeHead(200);
|
|
195
229
|
res.end("OK");
|
|
196
230
|
} catch (err) {
|
|
197
|
-
|
|
231
|
+
console.error("[Telegram] Error:", err);
|
|
198
232
|
res.writeHead(500);
|
|
199
233
|
res.end("Error");
|
|
200
234
|
}
|
|
201
235
|
}
|
|
202
236
|
|
|
203
|
-
// --- LINE
|
|
237
|
+
// --- LINE Handler ---
|
|
204
238
|
async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Config) {
|
|
205
239
|
if (!config.line) {
|
|
206
240
|
res.writeHead(404);
|
|
@@ -211,7 +245,6 @@ async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Con
|
|
|
211
245
|
// Verify signature
|
|
212
246
|
const signature = req.headers["x-line-signature"] as string;
|
|
213
247
|
if (!signature) {
|
|
214
|
-
log("LINE", "Missing signature");
|
|
215
248
|
res.writeHead(400);
|
|
216
249
|
res.end("Bad Request");
|
|
217
250
|
return;
|
|
@@ -224,108 +257,88 @@ async function handleLINE(req: IncomingMessage, res: ServerResponse, config: Con
|
|
|
224
257
|
|
|
225
258
|
// Verify
|
|
226
259
|
if (!(await verifyLINE(body, config.line.channelSecret, signature))) {
|
|
227
|
-
log("LINE", "Invalid signature");
|
|
228
260
|
res.writeHead(403);
|
|
229
261
|
res.end("Forbidden");
|
|
230
262
|
return;
|
|
231
263
|
}
|
|
232
264
|
|
|
233
265
|
try {
|
|
234
|
-
const
|
|
266
|
+
const bodyJson = JSON.parse(body);
|
|
267
|
+
const events = bodyJson.events || [];
|
|
235
268
|
|
|
236
269
|
for (const event of events) {
|
|
237
270
|
if (event.type === "postback") {
|
|
238
|
-
// Parse postback data
|
|
239
271
|
const data = event.postback?.data || "";
|
|
240
272
|
const colonIdx = data.indexOf(":");
|
|
241
|
-
let action
|
|
242
|
-
|
|
243
|
-
action = data.slice(0, colonIdx);
|
|
244
|
-
targetId = data.slice(colonIdx + 1);
|
|
245
|
-
} else {
|
|
246
|
-
action = data;
|
|
247
|
-
targetId = "";
|
|
248
|
-
}
|
|
273
|
+
let action = data.slice(0, colonIdx !== -1 ? colonIdx : undefined);
|
|
274
|
+
let targetId = colonIdx !== -1 ? data.slice(colonIdx + 1) : "";
|
|
249
275
|
|
|
250
|
-
|
|
276
|
+
const callback = {
|
|
251
277
|
type: "line_callback",
|
|
252
278
|
action,
|
|
253
279
|
targetId,
|
|
254
280
|
userId: event.source?.userId,
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
log("LINE", `Postback: ${action} ${targetId}`);
|
|
259
|
-
} else if (event.type === "message" && event.message?.type === "text") {
|
|
260
|
-
// Forward text messages to agent
|
|
261
|
-
queueCallback({
|
|
262
|
-
type: "line_message",
|
|
263
|
-
text: event.message.text,
|
|
264
|
-
userId: event.source?.userId,
|
|
265
|
-
replyToken: event.replyToken,
|
|
266
|
-
});
|
|
281
|
+
timestamp: new Date().toISOString(),
|
|
282
|
+
};
|
|
267
283
|
|
|
268
|
-
|
|
284
|
+
// Broadcast via WebSocket
|
|
285
|
+
const wsCount = broadcastCallback(callback);
|
|
286
|
+
if (wsCount === 0) {
|
|
287
|
+
queueCallback(callback);
|
|
288
|
+
}
|
|
289
|
+
console.log(`[LINE] Postback: ${action} ${targetId} (WS clients: ${wsCount})`);
|
|
269
290
|
}
|
|
270
291
|
}
|
|
271
292
|
|
|
272
293
|
res.writeHead(200);
|
|
273
|
-
res.end("
|
|
294
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
274
295
|
} catch (err) {
|
|
275
|
-
|
|
296
|
+
console.error("[LINE] Error:", err);
|
|
276
297
|
res.writeHead(500);
|
|
277
298
|
res.end("Error");
|
|
278
299
|
}
|
|
279
300
|
}
|
|
280
301
|
|
|
281
|
-
// --- Main
|
|
282
|
-
const
|
|
302
|
+
// --- Main ---
|
|
303
|
+
const config = loadConfig();
|
|
283
304
|
|
|
284
|
-
|
|
285
|
-
|
|
305
|
+
console.log("===========================================");
|
|
306
|
+
console.log("Unified Webhook Server");
|
|
307
|
+
console.log("===========================================");
|
|
308
|
+
if (config.telegram) console.log(" /telegram - Telegram webhook");
|
|
309
|
+
if (config.line) console.log(" /line - LINE webhook");
|
|
310
|
+
console.log("===========================================");
|
|
286
311
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
312
|
+
// Start WebSocket relay
|
|
313
|
+
startWebSocketServer();
|
|
291
314
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (config.line) console.log(" /line - LINE webhook");
|
|
296
|
-
|
|
297
|
-
const server = createServer(async (req, res) => {
|
|
298
|
-
const url = new URL(req.url || "/", `http://localhost:${PORT}`);
|
|
299
|
-
|
|
300
|
-
try {
|
|
301
|
-
if (url.pathname === "/telegram" && req.method === "POST") {
|
|
302
|
-
await handleTelegram(req, res, config);
|
|
303
|
-
} else if (url.pathname === "/line" && req.method === "POST") {
|
|
304
|
-
await handleLINE(req, res, config);
|
|
305
|
-
} else if (url.pathname === "/health") {
|
|
306
|
-
res.writeHead(200);
|
|
307
|
-
res.end("OK");
|
|
308
|
-
} else {
|
|
309
|
-
res.writeHead(404);
|
|
310
|
-
res.end("Not Found");
|
|
311
|
-
}
|
|
312
|
-
} catch (err) {
|
|
313
|
-
log("Server", "Error:", err);
|
|
314
|
-
res.writeHead(500);
|
|
315
|
-
res.end("Error");
|
|
316
|
-
}
|
|
317
|
-
});
|
|
315
|
+
// HTTP webhook server
|
|
316
|
+
const server = createServer(async (req, res) => {
|
|
317
|
+
const url = new URL(req.url || "/", `http://localhost:${PORT}`);
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
if (
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
319
|
+
try {
|
|
320
|
+
if (url.pathname === "/telegram" && req.method === "POST") {
|
|
321
|
+
await handleTelegram(req, res, config);
|
|
322
|
+
} else if (url.pathname === "/line" && req.method === "POST") {
|
|
323
|
+
await handleLINE(req, res, config);
|
|
324
|
+
} else if (url.pathname === "/health") {
|
|
325
|
+
res.writeHead(200);
|
|
326
|
+
res.end("OK");
|
|
327
|
+
} else {
|
|
328
|
+
res.writeHead(404);
|
|
329
|
+
res.end("Not Found");
|
|
327
330
|
}
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
331
|
+
} catch (err) {
|
|
332
|
+
console.error("[Server] Error:", err);
|
|
333
|
+
res.writeHead(500);
|
|
334
|
+
res.end("Error");
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
server.listen(PORT, () => {
|
|
339
|
+
console.log(`Server running on http://0.0.0.0:${PORT}`);
|
|
340
|
+
console.log(`WebSocket relay on ws://0.0.0.0:${WS_PORT}`);
|
|
341
|
+
console.log("Configure webhooks:");
|
|
342
|
+
if (config.telegram) console.log(` Telegram: https://webhook.bunchee.online/telegram`);
|
|
343
|
+
if (config.line) console.log(` LINE: https://webhook.bunchee.online/line`);
|
|
344
|
+
});
|