Skip to content

Commit c02994f

Browse files
authored
Merge pull request #2 from frkn-dev/site-integration
Site integration
2 parents 91d9c4c + c7de622 commit c02994f

File tree

8 files changed

+138
-6
lines changed

8 files changed

+138
-6
lines changed

package-lock.json

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"axios": "^1.10.0",
2828
"dotenv": "^16.5.0",
2929
"express": "^5.1.0",
30+
"js-sha3": "0.9.3",
3031
"qr-image": "^3.2.0",
3132
"qrcode": "^1.5.4",
3233
"telegraf": "^4.16.3"

src/bot.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as dotenv from "dotenv";
22
import express from "express";
33
import { Telegraf, TelegramError } from "telegraf";
4-
import { statHandler } from "./handlers";
4+
import { awaitingMnemonic, siteHandler, statHandler } from "./handlers";
55
import { handleInboundCallback } from "./handlers/callback/inbound";
66
import { handleProtoCallback } from "./handlers/callback/proto";
77
import { handleSubscriptionCallback } from "./handlers/callback/sub";
@@ -16,7 +16,7 @@ import { scoreHandler } from "./handlers/score";
1616
import { startHandler } from "./handlers/start";
1717
import { subHandler } from "./handlers/sub";
1818
import { BotState } from "./state";
19-
import QRCode from "qrcode";
19+
import { connectWithMnemonic } from "./site";
2020

2121
dotenv.config();
2222

@@ -31,7 +31,7 @@ const missingVars = requiredEnvVars.filter((varName) => !process.env[varName]);
3131

3232
if (missingVars.length > 0) {
3333
throw new Error(
34-
`Missing required environment variables: ${missingVars.join(", ")}`,
34+
`Missing required environment variables: ${missingVars.join(", ")}`
3535
);
3636
}
3737

@@ -51,7 +51,7 @@ const botState = new BotState(
5151
API_AUTH_TOKEN,
5252
GOOGLE_SCRIPT_URL,
5353
TOKEN,
54-
ADMIN_CHAT_ID,
54+
ADMIN_CHAT_ID
5555
);
5656

5757
const app = express();
@@ -114,6 +114,7 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
114114
bot.command("connect", (ctx) => connectHandler(ctx, botState));
115115
bot.command("sub", (ctx) => subHandler(ctx, botState));
116116
bot.command("stat", (ctx) => statHandler(ctx, botState));
117+
bot.command("site", (ctx) => siteHandler(ctx, botState));
117118
bot.command("delete", (ctx) => deleteHandler(ctx, botState));
118119
bot.command("stop", (ctx) => deleteHandler(ctx, botState));
119120
bot.command("status", (ctx) => scoreHandler(ctx, botState));
@@ -122,6 +123,7 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
122123

123124
// Text message handler
124125
bot.on("text", async (ctx) => {
126+
const userId = ctx.from.id;
125127
const message = ctx.message.text;
126128

127129
// Handle feedback messages first
@@ -130,6 +132,35 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
130132
return;
131133
}
132134

135+
// Handle site command
136+
if (awaitingMnemonic[userId]) {
137+
awaitingMnemonic[userId] = false; // reset
138+
139+
const words = message.trim().split(/\s+/);
140+
if (words.length !== 12) {
141+
return ctx.reply(
142+
"Фраза должна содержать 12 слов. Попробуйте ещё раз командой /site"
143+
);
144+
}
145+
146+
try {
147+
const data = await connectWithMnemonic(message.trim());
148+
if (typeof data === "string") {
149+
await ctx.reply(data);
150+
} else {
151+
await ctx.reply(
152+
`Ваша ссылка: <code>${data.subscription_url}</code>`,
153+
{ parse_mode: "HTML" }
154+
);
155+
}
156+
} catch (err) {
157+
console.error("Ошибка в siteHandler:", err);
158+
await ctx.reply("Ошибка при обработке фразы.");
159+
} finally {
160+
return;
161+
}
162+
}
163+
133164
// Handle unknown commands
134165
if (message.startsWith("/")) {
135166
await ctx.reply(
@@ -139,8 +170,9 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
139170
"• /sub - Управление подпиской\n" +
140171
"• /status - Проверить статус\n" +
141172
"• /stat - Статистика\n" +
173+
"• /site - Если оплачивали подписку на сайте\n" +
142174
"• /support - Поддержка и обратная связь\n" +
143-
"• /delete - Удалить аккаунт",
175+
"• /delete - Удалить аккаунт"
144176
);
145177
}
146178
});
@@ -218,7 +250,7 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
218250
` Domain: ${DOMAIN}\n` +
219251
` Port: ${PORT}\n` +
220252
` Webhook path: ${WEBHOOK_PATH}\n` +
221-
` Environment: ${process.env.NODE_ENV || "development"}`,
253+
` Environment: ${process.env.NODE_ENV || "development"}`
222254
);
223255
})().catch((err) => {
224256
console.error("[STARTUP] Fatal error during startup:", err);

src/handlers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
export * from "./connect";
22
export * from "./start";
3+
export * from "./site";
34
export * from "./stat";
45
export * from "./sub";

src/handlers/site.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Context } from "telegraf";
2+
import { BotState } from "../state";
3+
4+
export const awaitingMnemonic: Record<number, boolean> = {};
5+
6+
export const siteHandler = async (ctx: Context, botState: BotState) => {
7+
const user = ctx.from;
8+
9+
if (!user) {
10+
return ctx.reply("/start для начала");
11+
}
12+
13+
const userEntry = await botState.findUserByTelegramId(user.id);
14+
15+
if (!userEntry || userEntry.is_deleted) {
16+
return ctx.reply("Для начала /start");
17+
}
18+
19+
awaitingMnemonic[ctx.from.id] = true;
20+
return ctx.reply("Введите вашу мнемоническую фразу (12 слов):");
21+
};

src/handlers/start.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export const startHandler = async (ctx: Context, botState: BotState) => {
2828
"🔗 /connect \\- Получить VPN\\-ключ",
2929
"📈 /status \\- Проверить статус серверов/нагрузка",
3030
"💎 /sub \\- Подписочная ссылка",
31+
"🌐 /site \\- Если оплачивали подписку на сайте",
3132
"🆘 /support \\- Помощь и поддержка",
3233
"💬 /feedback \\- Оставить отзыв",
3334
].join("\n");

src/site/index.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import axios from "axios";
2+
import { sha3_512 } from "js-sha3";
3+
import { ActiveConnection, LoginResponse } from "./types";
4+
5+
const SITE_URL = process.env.SITE_URL || "https://frkn.org";
6+
7+
export async function connectWithMnemonic(
8+
mnemonic: string
9+
): Promise<string | ActiveConnection> {
10+
const passwordHash = sha3_512(mnemonic);
11+
12+
try {
13+
const loginResp = await axios.post<LoginResponse>(
14+
SITE_URL + "/api/login",
15+
{ password: passwordHash },
16+
{ headers: { "Content-Type": "application/json" } }
17+
);
18+
19+
if (loginResp.data.status === "error") {
20+
return loginResp.data.message;
21+
}
22+
23+
const connectResp = await axios.get<ActiveConnection>(
24+
SITE_URL + "/api/connect",
25+
{
26+
headers: {
27+
"Content-Type": "application/json",
28+
Authorization: loginResp.data.token,
29+
},
30+
}
31+
);
32+
33+
return connectResp.data as ActiveConnection;
34+
} catch (error) {
35+
console.error("connectWithMnemonic failed:", error);
36+
return "Failed to connect to server. Please try again later.";
37+
}
38+
}

src/site/types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
interface ErrorResponse {
2+
status: "error";
3+
message: string;
4+
cause?: {
5+
name: string;
6+
code?: string;
7+
issues?: [];
8+
};
9+
}
10+
11+
interface SuccessResponse {
12+
status: "success";
13+
token: string;
14+
}
15+
16+
export type LoginResponse = ErrorResponse | SuccessResponse;
17+
18+
export type ActiveConnection = {
19+
status: "active" | "inactive";
20+
subscription_url: string;
21+
ss_links: {
22+
country: string;
23+
link: string;
24+
}[];
25+
used_traffic: number;
26+
limit: number | null;
27+
limit_reset_strategy: string;
28+
created_at: string;
29+
expire: string | null;
30+
online_at: string | null;
31+
};

0 commit comments

Comments
 (0)