Skip to content

Commit 88df13c

Browse files
committed
feat(skip): added and fixed some constants
1 parent e9ea261 commit 88df13c

File tree

7 files changed

+98
-106
lines changed

7 files changed

+98
-106
lines changed

.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"printWidth": 180
3+
}

src/bot/commands/help.ts

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,42 @@
11
import { Context } from "npm:telegraf";
22

3+
// TODO add
4+
// schedule(change the frequency)
5+
// task - to get relevant task
6+
// leaderboard - View the leaderboard
7+
// group - to redirect to group
8+
39
export function helpCommand(ctx: Context) {
4-
const helpText = `
10+
const helpText = `
511
*LeetCode Study Bot Help*
612
713
*General Commands:*
814
• /start - Start or restart the bot
915
• /help - Show this help message
1016
1117
*Progress & Status:*
12-
• /status - Check your current progress
1318
• /stats - View your solving statistics
14-
• /today - Get today's problem suggestion(SOON)
1519
• /skip - Skip today's problem
1620
17-
*Schedule & Notifications:*
18-
• /schedule - Set your practice schedule
19-
20-
*Community Features:*
21-
• /groups - Manage your study groups
22-
• /leaderboard - View the leaderboard
23-
2421
_Need more assistance? Feel free to ask!_
2522
`.trim();
2623

27-
// Define inline keyboard buttons for quick actions.
28-
const keyboard = {
29-
inline_keyboard: [
30-
[
31-
{ text: "Start", callback_data: "/start" },
32-
{ text: "Status", callback_data: "/status" }
33-
],
34-
[
35-
{ text: "Stats", callback_data: "/stats" },
36-
{ text: "Streak", callback_data: "/streak" }
37-
],
38-
[
39-
{ text: "Today", callback_data: "/today" },
40-
{ text: "Skip", callback_data: "/skip" }
41-
],
42-
[
43-
{ text: "Schedule", callback_data: "/schedule" },
44-
{ text: "Groups", callback_data: "/groups" }
45-
],
46-
[
47-
{ text: "Leaderboard", callback_data: "/leaderboard" },
48-
{ text: "Help", callback_data: "/help" }
49-
]
50-
]
51-
};
52-
53-
return ctx.reply(helpText, {
54-
parse_mode: "Markdown",
55-
reply_markup: keyboard,
56-
});
24+
// Define inline keyboard buttons for quick actions.
25+
const keyboard = {
26+
inline_keyboard: [
27+
[
28+
{ text: "Start", callback_data: "/start" },
29+
{ text: "Status", callback_data: "/status" },
30+
],
31+
[
32+
{ text: "Stats", callback_data: "/stats" },
33+
{ text: "Skip", callback_data: "/skip" },
34+
],
35+
],
36+
};
37+
38+
return ctx.reply(helpText, {
39+
parse_mode: "Markdown",
40+
reply_markup: keyboard,
41+
});
5742
}

src/bot/commands/skip.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Context } from "npm:telegraf";
2+
import { UserRepository } from "../../db/repositories/user.ts";
3+
import { ONE_DAY } from "../../modules/constants.ts";
4+
5+
export async function skipCommand(ctx: Context) {
6+
const userRepo = new UserRepository();
7+
const telegramId = ctx.from?.id;
8+
9+
if (!telegramId) return ctx.reply("Could not identify you. Please try again later.");
10+
11+
const user = await userRepo.findByTelegramId(telegramId);
12+
if (!user) return ctx.reply("It seems you are not registered yet. Please use /start to begin the registration process.");
13+
14+
await userRepo.update(telegramId, { nextDueDate: new Date(Date.now() + user.frequency * ONE_DAY), skippedCount: user.skippedCount + 1 });
15+
16+
const statsMessage = `No worries! We'll skip for this time 👍`.trim();
17+
18+
return ctx.reply(statsMessage, { parse_mode: "HTML" });
19+
}

src/bot/index.ts

Lines changed: 25 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,16 @@ import { registrationWizard } from "./scenes/registration.ts";
88
import { getUserLeetcodeInfo } from "../modules/leetcode.ts";
99
import { User } from "../types/index.ts";
1010
import { CallbackQuery, Update } from "npm:telegraf/types";
11+
import { skipCommand } from "./commands/skip.ts";
12+
import { ONE_DAY } from "../modules/constants.ts";
1113

1214
export class LeetCodeBot {
1315
private bot: Telegraf;
1416
private userRepo: UserRepository;
1517

1618
constructor() {
1719
const token = Deno.env.get("TELEGRAM_BOT_TOKEN");
18-
if (!token)
19-
throw Error(
20-
"TELEGRAM_BOT_TOKEN is missing. Try setting in .env as `TELEGRAM_BOT_TOKEN=your_token`"
21-
);
20+
if (!token) throw Error("TELEGRAM_BOT_TOKEN is missing. Try setting in .env as `TELEGRAM_BOT_TOKEN=your_token`");
2221

2322
this.bot = new Telegraf(token);
2423
this.userRepo = new UserRepository();
@@ -42,41 +41,39 @@ export class LeetCodeBot {
4241

4342
// Create and use the stage middleware
4443
const stage = new Scenes.Stage([registrationWizard]);
45-
this.bot.use(
46-
stage.middleware() as never as MiddlewareFn<Context<Update>, Update>
47-
);
44+
this.bot.use(stage.middleware() as never as MiddlewareFn<Context<Update>, Update>);
4845

4946
// Log middleware
5047
this.bot.use(async (ctx, next) => {
51-
const start = Date.now();
52-
await next();
53-
const ms = Date.now() - start;
54-
console.log(`[${ctx.updateType}] Response time: ${ms}ms`);
48+
try {
49+
const start = Date.now();
50+
await next();
51+
const ms = Date.now() - start;
52+
console.log(`[${ctx.updateType}] Response time: ${ms}ms`);
53+
} catch (error: unknown) {
54+
return ctx.reply(`Sorry, something went wrong. Please try again later. Error - ${(error as Error).message}`);
55+
}
5556
});
5657
}
5758

5859
private registerCommands() {
59-
this.bot.command("start", (ctx) =>
60-
(ctx as unknown as Scenes.SceneContext).scene.enter("registration-wizard")
61-
);
60+
this.bot.command("start", (ctx) => (ctx as unknown as Scenes.SceneContext).scene.enter("registration-wizard"));
6261

6362
// TODO add skip command
6463
// TODO add reschedule logic
6564

6665
this.bot.command("help", helpCommand);
6766
this.bot.command("stats", statsCommand);
67+
this.bot.command("skip", skipCommand);
6868

6969
this.bot.on("callback_query", async (ctx) => {
70-
const command = (ctx.callbackQuery as unknown as CallbackQuery.DataQuery)
71-
.data;
70+
const command = (ctx.callbackQuery as unknown as CallbackQuery.DataQuery).data;
7271

7372
await ctx.answerCbQuery();
7473

7574
switch (command) {
7675
case "/start":
77-
await (ctx as never as Scenes.SceneContext).scene.enter(
78-
"registration-wizard"
79-
);
76+
await (ctx as never as Scenes.SceneContext).scene.enter("registration-wizard");
8077
break;
8178
case "/help":
8279
await helpCommand(ctx);
@@ -85,9 +82,7 @@ export class LeetCodeBot {
8582
await statsCommand(ctx);
8683
break;
8784
default:
88-
await ctx.reply(
89-
"Unknown command. Please use /help to see available commands."
90-
);
85+
await ctx.reply("Unknown command. Please use /help to see available commands.");
9186
}
9287
});
9388
}
@@ -115,27 +110,20 @@ export class LeetCodeBot {
115110
console.log(`Checking for ${user.telegramId}`);
116111
if (user.nextDueDate && Date.now() < user.nextDueDate.getTime()) return;
117112

118-
const updatedInfo = await getUserLeetcodeInfo(
119-
user.leetcodeUsername
120-
).catch(() => null);
113+
const updatedInfo = await getUserLeetcodeInfo(user.leetcodeUsername).catch(() => null);
121114
if (!updatedInfo) return; // Optionally handle later
122115

123116
// Get the overall submission stats (for "All" difficulty)
124-
const allSubmissions = updatedInfo.submitStats.acSubmissionNum.find(
125-
(item: { difficulty: string }) => item.difficulty === "All"
126-
);
117+
const allSubmissions = updatedInfo.submitStats.acSubmissionNum.find((item: { difficulty: string }) => item.difficulty === "All");
127118
if (!allSubmissions) {
128-
console.error(
129-
`Could not find submissions for leetcodeUsername: ${user.leetcodeUsername}`
130-
);
119+
console.error(`Could not find submissions for leetcodeUsername: ${user.leetcodeUsername}`);
131120
return;
132121
}
133122

134123
// Compute "missing" as the difference between expected count and what the user achieved.
135124
// If your intended logic is that a positive "missing" means the user is behind,
136125
// you might compute it as:
137-
const missing =
138-
allSubmissions.count - (user.stats.totalSolved + user.tasksCount);
126+
const missing = allSubmissions.count - (user.stats.totalSolved + user.tasksCount);
139127

140128
console.log(`Sending message to ${user.telegramId}`);
141129

@@ -148,17 +136,11 @@ export class LeetCodeBot {
148136
);
149137
} else if (missing === 0) {
150138
// User is exactly on track.
151-
await this.bot.telegram.sendMessage(
152-
user.telegramId,
153-
`Good job ${user.telegramUsername}, you're right on track today! Keep up the great work! 👍`
154-
);
139+
await this.bot.telegram.sendMessage(user.telegramId, `Good job ${user.telegramUsername}, you're right on track today! Keep up the great work! 👍`);
155140
} else if (Math.abs(Math.round(missing / user.tasksCount)) >= 2) {
156141
// If the user is ahead by a significant margin (using absolute value in case missing is negative),
157142
// congratulate them.
158-
await this.bot.telegram.sendMessage(
159-
user.telegramId,
160-
`YOU'RE A BEAST, ${user.telegramUsername}! You're crushing it and surpassing your targets! 🚀`
161-
);
143+
await this.bot.telegram.sendMessage(user.telegramId, `YOU'RE A BEAST, ${user.telegramUsername}! You're crushing it and surpassing your targets! 🚀`);
162144
}
163145

164146
await this.userRepo.update(user.telegramId, {
@@ -167,7 +149,7 @@ export class LeetCodeBot {
167149
streakCount: user.stats.streakCount + 1,
168150
},
169151
updatedAt: new Date(),
170-
nextDueDate: new Date(Date.now() + user.frequency * 24 * 60 * 60_000),
152+
nextDueDate: new Date(Date.now() + user.frequency * ONE_DAY),
171153
});
172154
} catch (error) {
173155
console.error("Error during daily check:", error);
@@ -180,8 +162,7 @@ export class LeetCodeBot {
180162
await this.bot.launch(() => console.log("Bot is running..."));
181163

182164
const signals = ["SIGTERM", "SIGINT"];
183-
for (const signal of signals)
184-
Deno.addSignalListener(signal as Deno.Signal, () => this.stop());
165+
for (const signal of signals) Deno.addSignalListener(signal as Deno.Signal, () => this.stop());
185166
} catch (error) {
186167
console.error("Failed to start bot:", error);
187168
throw error;

src/bot/scenes/registration.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { UserRepository } from "../../db/repositories/user.ts";
33
import { ExperienceLevel, UserInput, UserState } from "../../types/index.ts";
44
import { getUserLeetcodeInfo } from "../../modules/leetcode.ts";
55
import { LeetcodeUserInfo } from "../../modules/types.ts";
6+
import { ONE_DAY } from "../../modules/constants.ts";
67

78
// 1. Define an interface for your custom wizard state.
89
interface RegistrationWizardState {
@@ -239,7 +240,7 @@ export const registrationWizard = new Scenes.WizardScene<MyContext>(
239240
frequency,
240241
tasksCount,
241242
stats: { streakCount: 0, totalSolved: allSubmissions?.count ?? 0 },
242-
nextDueDate: new Date(Date.now() + frequency * 24 * 60 * 60_000),
243+
nextDueDate: new Date(Date.now() + frequency * ONE_DAY),
243244
};
244245

245246
try {

src/modules/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const ONE_DAY = 24 * 60 * 60_000;

src/types/index.ts

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
11
export enum UserState {
2-
// Active states
3-
ACTIVE = "ACTIVE", // Normal active state
4-
PAUSED = "PAUSED", // User temporarily paused notifications
2+
// Active states
3+
ACTIVE = "ACTIVE", // Normal active state
4+
PAUSED = "PAUSED", // User temporarily paused notifications
55
}
66

77
export enum ExperienceLevel {
8-
BEGINNER = "beginner",
9-
INTERMEDIATE = "intermediate",
10-
ADVANCED = "advanced"
8+
BEGINNER = "beginner",
9+
INTERMEDIATE = "intermediate",
10+
ADVANCED = "advanced",
1111
}
1212

1313
export interface User {
14-
telegramId: number;
15-
telegramUsername: string;
14+
telegramId: number;
15+
telegramUsername: string;
1616

17-
leetcodeUsername: string;
17+
leetcodeUsername: string;
1818

19-
state: UserState;
20-
experienceLevel: ExperienceLevel;
19+
state: UserState;
20+
experienceLevel: ExperienceLevel;
2121

22-
frequency: number;
23-
tasksCount: number;
22+
frequency: number;
23+
tasksCount: number;
2424

25-
stats: {
26-
streakCount: number;
27-
totalSolved: number;
28-
};
25+
stats: {
26+
streakCount: number;
27+
totalSolved: number;
28+
};
2929

30-
createdAt: Date; // Registration date
31-
updatedAt: Date; // Used to update the last time leetcode stats were fetched
32-
nextDueDate: Date; // The date which until which the taskCount should be updated
30+
createdAt: Date; // Registration date
31+
updatedAt: Date; // Used to update the last time leetcode stats were fetched
32+
33+
nextDueDate: Date; // The date which until which the taskCount should be updated
34+
skippedCount: number;
3335
}
3436

35-
export type UserInput = Omit<User, 'createdAt' | 'updatedAt'>;
37+
export type UserInput = Omit<User, "createdAt" | "updatedAt">;

0 commit comments

Comments
 (0)