Skip to content

Commit 8122f1f

Browse files
authored
Merge pull request #4 from frkn-dev/unlock
Simplify bot commands
2 parents c02994f + 674ce00 commit 8122f1f

File tree

25 files changed

+168
-791
lines changed

25 files changed

+168
-791
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the column `daily_limit_mb` on the `users` table. All the data in the column will be lost.
5+
6+
*/
7+
-- AlterTable
8+
ALTER TABLE "public"."users" DROP COLUMN "daily_limit_mb",
9+
ADD COLUMN "expired_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
10+
ADD COLUMN "is_trial" BOOLEAN NOT NULL DEFAULT false;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- AlterTable
2+
ALTER TABLE "public"."users" ALTER COLUMN "expired_at" SET DEFAULT (now() + '3 days'::interval);

prisma/schema.prisma

Lines changed: 0 additions & 15 deletions
This file was deleted.

schema.prisma

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ model User {
1616
username String @unique
1717
password String
1818
env String @default("tg")
19-
dailyLimitMb Int @default(1024) @map("daily_limit_mb")
2019
isDeleted Boolean @default(false) @map("is_deleted")
2120
createdAt DateTime @default(now()) @map("created_at")
2221
modifiedAt DateTime @updatedAt @map("modified_at")
22+
expiredAt DateTime @default(dbgenerated("(now() + '3 days'::interval)")) @map("expired_at")
23+
isTrial Boolean @default(false) @map("is_trial")
2324
2425
@@index([telegramId])
2526
@@index([username])

src/bot.ts

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import * as dotenv from "dotenv";
22
import express from "express";
33
import { Telegraf, TelegramError } from "telegraf";
4-
import { awaitingMnemonic, siteHandler, statHandler } from "./handlers";
5-
import { handleInboundCallback } from "./handlers/callback/inbound";
6-
import { handleProtoCallback } from "./handlers/callback/proto";
4+
import {
5+
awaitingMnemonic,
6+
siteHandler,
7+
statHandler,
8+
subPlainHandler,
9+
subClashHandler,
10+
} from "./handlers";
711
import { handleSubscriptionCallback } from "./handlers/callback/sub";
8-
import { connectHandler } from "./handlers/connect";
12+
import { handleSubscriptionClashCallback } from "./handlers/callback/clash";
913
import { deleteHandler } from "./handlers/delete";
1014
import {
1115
feedbackHandler,
@@ -14,7 +18,6 @@ import {
1418
} from "./handlers/feedback";
1519
import { scoreHandler } from "./handlers/score";
1620
import { startHandler } from "./handlers/start";
17-
import { subHandler } from "./handlers/sub";
1821
import { BotState } from "./state";
1922
import { connectWithMnemonic } from "./site";
2023

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

3235
if (missingVars.length > 0) {
3336
throw new Error(
34-
`Missing required environment variables: ${missingVars.join(", ")}`
37+
`Missing required environment variables: ${missingVars.join(", ")}`,
3538
);
3639
}
3740

@@ -51,7 +54,7 @@ const botState = new BotState(
5154
API_AUTH_TOKEN,
5255
GOOGLE_SCRIPT_URL,
5356
TOKEN,
54-
ADMIN_CHAT_ID
57+
ADMIN_CHAT_ID,
5558
);
5659

5760
const app = express();
@@ -111,8 +114,8 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
111114
(async () => {
112115
// Command handlers
113116
bot.command("start", (ctx) => startHandler(ctx, botState));
114-
bot.command("connect", (ctx) => connectHandler(ctx, botState));
115-
bot.command("sub", (ctx) => subHandler(ctx, botState));
117+
bot.command("connect", (ctx) => subPlainHandler(ctx, botState));
118+
bot.command("clash", (ctx) => subClashHandler(ctx, botState));
116119
bot.command("stat", (ctx) => statHandler(ctx, botState));
117120
bot.command("site", (ctx) => siteHandler(ctx, botState));
118121
bot.command("delete", (ctx) => deleteHandler(ctx, botState));
@@ -139,7 +142,7 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
139142
const words = message.trim().split(/\s+/);
140143
if (words.length !== 12) {
141144
return ctx.reply(
142-
"Фраза должна содержать 12 слов. Попробуйте ещё раз командой /site"
145+
"Фраза должна содержать 12 слов. Попробуйте ещё раз командой /site",
143146
);
144147
}
145148

@@ -150,7 +153,7 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
150153
} else {
151154
await ctx.reply(
152155
`Ваша ссылка: <code>${data.subscription_url}</code>`,
153-
{ parse_mode: "HTML" }
156+
{ parse_mode: "HTML" },
154157
);
155158
}
156159
} catch (err) {
@@ -166,13 +169,13 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
166169
await ctx.reply(
167170
"Неизвестная команда 🫣\n\nДоступные команды:\n" +
168171
"• /start - Начать работу\n" +
169-
"• /connect - Подключиться к VPN\n" +
170-
"• /sub - Управление подпиской\n" +
172+
"• /connect - Получить VPN ссылку\n" +
173+
"• /clash - Получить VPN Clash ссылку\n" +
171174
"• /status - Проверить статус\n" +
172175
"• /stat - Статистика\n" +
173176
"• /site - Если оплачивали подписку на сайте\n" +
174177
"• /support - Поддержка и обратная связь\n" +
175-
"• /delete - Удалить аккаунт"
178+
"• /delete - Удалить аккаунт",
176179
);
177180
}
178181
});
@@ -206,12 +209,10 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
206209
// Route callback data to appropriate handlers
207210
if (data.startsWith("feedback_")) {
208211
await handleFeedbackCallback(ctx, botState);
209-
} else if (data.startsWith("proto_")) {
210-
await handleProtoCallback(ctx, botState);
211-
} else if (data.startsWith("inbound_")) {
212-
await handleInboundCallback(ctx, botState);
213-
} else if (data.startsWith("sub_")) {
212+
} else if (data.startsWith("plain")) {
214213
await handleSubscriptionCallback(ctx, botState);
214+
} else if (data.startsWith("clash")) {
215+
await handleSubscriptionClashCallback(ctx, botState);
215216
} else {
216217
console.log(`[CALLBACK] Unknown callback data: ${data}`);
217218
await ctx.reply("Неизвестная команда.");
@@ -250,7 +251,7 @@ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
250251
` Domain: ${DOMAIN}\n` +
251252
` Port: ${PORT}\n` +
252253
` Webhook path: ${WEBHOOK_PATH}\n` +
253-
` Environment: ${process.env.NODE_ENV || "development"}`
254+
` Environment: ${process.env.NODE_ENV || "development"}`,
254255
);
255256
})().catch((err) => {
256257
console.error("[STARTUP] Fatal error during startup:", err);

src/handlers/callback/clash.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import QRCode from "qrcode";
2+
import { Context } from "telegraf";
3+
import { BotState } from "../../state";
4+
5+
export async function handleSubscriptionClashCallback(
6+
ctx: Context,
7+
botState: BotState,
8+
) {
9+
const callbackQuery = ctx.callbackQuery;
10+
11+
if (!callbackQuery || !("data" in callbackQuery)) {
12+
return await ctx.answerCbQuery();
13+
}
14+
15+
const data = callbackQuery.data;
16+
if (!data) {
17+
return await ctx.answerCbQuery();
18+
}
19+
20+
const user = ctx.from;
21+
if (!user || !user.username) {
22+
return ctx.answerCbQuery("Не удалось определить пользователя.");
23+
}
24+
25+
const userEntry = await botState.findUserByTelegramId(user.id);
26+
if (!userEntry || userEntry.is_deleted) {
27+
return ctx.answerCbQuery("Для начала используйте /start");
28+
}
29+
30+
const subLink = botState.getSubLink(userEntry.id, "clash");
31+
32+
const qrBuffer = await QRCode.toBuffer(subLink, {
33+
errorCorrectionLevel: "H",
34+
type: "png",
35+
margin: 8,
36+
scale: 6,
37+
});
38+
39+
await ctx.editMessageText(
40+
`*Нажмите на ссылку, чтобы скопировать* \n\`\`\`\n${subLink}\n\`\`\``,
41+
{ parse_mode: "MarkdownV2" },
42+
);
43+
44+
await ctx.replyWithPhoto(
45+
{ source: qrBuffer },
46+
{ caption: `QR-код для VPN Clash подписки 🧷` },
47+
);
48+
}

0 commit comments

Comments
 (0)