Repository navigation
Expand file tree
/
Copy pathserver.ts
More file actions
139 lines (128 loc) · 4.44 KB
/
Copy pathserver.ts
File metadata and controls
139 lines (128 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
import crypto from "node:crypto";
import http from "node:http";
import type { Message } from "@effect-ak/tg-bot-api";
import { PORT, ADDR, TELEGRAM_TOKEN, TELEGRAM_WEBHOOK, TELEGRAM_API } from "./src/config.ts";
import {
privateMessageHandler,
groupMessageHandler,
guestMessageHandler,
callbackQueryHandler,
} from "./src/handlers.ts";
import { getTranslation, locales } from "./src/i18n.ts";
import {
botName,
setBotName,
setMessageReaction,
messageIsMentioningBot,
setMyCommands,
} from "./src/telegram.ts";
console.log(`WEBHOOK: ${TELEGRAM_WEBHOOK}`);
console.log(`Use trace.moe API: ${process.env.TRACE_MOE_KEY ? "with" : "without"} API Key`);
console.log("Setting Telegram webhook...");
const SECRET_TOKEN = crypto.randomBytes(32).toString("hex");
const allowedUpdates = JSON.stringify([
"message",
"edited_message",
"guest_message",
"callback_query",
]);
await fetch(
`${TELEGRAM_API}/bot${TELEGRAM_TOKEN}/setWebhook?url=${TELEGRAM_WEBHOOK}&max_connections=100&allowed_updates=${encodeURIComponent(allowedUpdates)}&secret_token=${SECRET_TOKEN}`,
)
.then((e) => e.json())
.then((e) => {
console.log(e);
});
for (const langCode of Object.keys(locales)) {
setMyCommands({
commands: [
{ command: "help", description: getTranslation(langCode, "helpCommandDescription") },
{ command: "lang", description: getTranslation(langCode, "langCommandDescription") },
],
language_code: langCode,
}).catch((err) => {
console.error(`Failed to set bot commands for ${langCode}:`, err);
});
}
fetch(`${TELEGRAM_API}/bot${TELEGRAM_TOKEN}/getMe`)
.then((e) => e.json())
.then((e) => {
console.log(e);
const fetchedBotName = e.result?.username ?? "";
setBotName(fetchedBotName);
})
.catch((err) => {
console.error("Failed to get bot info:", err);
});
const getBody = async (req: http.IncomingMessage): Promise<string> => {
const chunks: Buffer[] = [];
let size = 0;
for await (const chunk of req) {
size += chunk.length;
if (size > 1024 * 1024) {
req.destroy();
throw new Error("Request entity too large");
}
chunks.push(chunk);
}
return Buffer.concat(chunks).toString();
};
const SECRET_TOKEN_BUFFER = Buffer.from(SECRET_TOKEN);
const server = http.createServer({ keepAliveTimeout: 60000 }, async (req, res) => {
if (req.method === "POST") {
const clientSecret = req.headers["x-telegram-bot-api-secret-token"];
if (
typeof clientSecret !== "string" ||
clientSecret.length !== SECRET_TOKEN.length ||
!crypto.timingSafeEqual(Buffer.from(clientSecret), SECRET_TOKEN_BUFFER)
) {
return res.writeHead(403).end();
}
try {
const request = JSON.parse(await getBody(req));
if (request.callback_query) {
await callbackQueryHandler(request.callback_query);
} else {
const message: Message = request.message ?? request.edited_message ?? request.guest_message;
if (message?.guest_query_id && messageIsMentioningBot(message)) {
await guestMessageHandler(message);
} else if (message?.chat?.type === "private") {
await privateMessageHandler(message);
setMessageReaction({
chat_id: message.chat.id,
message_id: message.message_id,
reaction: [],
});
} else if (message?.chat?.type === "group" || message?.chat?.type === "supergroup") {
if (messageIsMentioningBot(message)) {
await groupMessageHandler(message);
setMessageReaction({
chat_id: message.chat.id,
message_id: message.message_id,
reaction: [],
});
}
}
}
return res.writeHead(204).end();
} catch (e) {
console.error(e);
return res.writeHead(400).end();
}
}
if (req.method === "GET") {
return res
.writeHead(200, {
"Content-Type": "text/html",
"Content-Security-Policy": "default-src 'none'; frame-ancestors 'none'",
"Cross-Origin-Resource-Policy": "same-origin",
"Referrer-Policy": "no-referrer",
"Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload",
"X-Content-Type-Options": "nosniff",
})
.end(`<meta http-equiv="Refresh" content="0; URL=https://t.me/${botName ?? ""}">`);
}
return res.writeHead(400).end();
});
server.on("error", (e) => console.error(e));
server.listen(Number(PORT), ADDR, () => console.log("server listening on", server.address()));