Skip to content

Commit a4e26ea

Browse files
committed
feat: breaking changes
- drop quiz table - move quiz data to redis for faster performance - quiz no longer accepts other answer if it was already answered regardless of correctness - new top leaderboards - seperates points to commmand call and added quiz wrong counter per user - update to rate limiter - chnage speedtest ttl to 12hour - and more
1 parent 8fe329e commit a4e26ea

22 files changed

Lines changed: 410 additions & 346 deletions

File tree

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/*
2+
Warnings:
3+
4+
- You are about to drop the `Quiz` table. If the table is not empty, all the data it contains will be lost.
5+
6+
*/
7+
-- DropTable
8+
DROP TABLE `Quiz`;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
-- AlterTable
2+
ALTER TABLE `User` ADD COLUMN `points` DOUBLE NOT NULL DEFAULT 0.0,
3+
ADD COLUMN `quizAnsweredWrong` INTEGER NOT NULL DEFAULT 0;

prisma/schema.prisma

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@ datasource db {
1414
}
1515

1616
model User {
17-
id Int @id @default(autoincrement())
18-
lid String @unique
19-
name String
20-
number String
21-
countryCode String
22-
type String
23-
mode String
24-
about String?
25-
commandCount Int @default(0)
26-
quizAnswered Int @default(0)
27-
createdAt DateTime @default(now())
28-
updatedAt DateTime @updatedAt
17+
id Int @id @default(autoincrement())
18+
lid String @unique
19+
name String
20+
number String
21+
countryCode String
22+
type String
23+
mode String
24+
about String?
25+
commandCount Int @default(0)
26+
quizAnswered Int @default(0)
27+
quizAnsweredWrong Int @default(0)
28+
points Float @default(0.0)
29+
createdAt DateTime @default(now())
30+
updatedAt DateTime @updatedAt
2931
}
3032

3133
model Block {
@@ -71,13 +73,3 @@ model Log {
7173
createdAt DateTime @default(now())
7274
updatedAt DateTime @updatedAt
7375
}
74-
75-
model Quiz {
76-
id Int @id @default(autoincrement())
77-
mid String @unique
78-
lid String
79-
qid String
80-
answeredAt DateTime?
81-
createdAt DateTime @default(now())
82-
updatedAt DateTime @updatedAt
83-
}

src/commands/play.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,28 @@ export default async function play(msg: Message) {
5353
}
5454

5555
const yt = await Innertube.create({
56-
cache: new UniversalCache(false),
56+
cache: new UniversalCache(true, "./.youtubei"),
5757
generate_session_locally: true,
5858
player_id: "0004de42",
5959
});
6060

61-
const audio: any = await search(yt, query);
61+
const [audio] = await Promise.all([search(yt, query), msg.react("🔍")]);
6262
if (!audio) {
6363
await msg.reply(`No youtube music found for "${query}".`);
6464
return;
6565
}
6666

6767
// Only allow audios shorter than 20 minutes (1200 seconds)
6868
if (audio.duration && audio.duration.seconds > 1200) {
69-
await msg.reply(
70-
"Opps, the music is quite long we can only process max of 20 minutes.",
71-
);
69+
await Promise.all([
70+
msg.reply(
71+
"Opps, the music is quite long we can only process max of 20 minutes.",
72+
),
73+
msg.react(""),
74+
]);
7275
return;
7376
}
7477

75-
await msg.react("🔍");
76-
7778
const tempDir = "./.temp";
7879
await fs.promises.mkdir(tempDir, { recursive: true });
7980
const tempPath = path.join(tempDir, `${audio.id}.mp3`);
@@ -85,11 +86,14 @@ export default async function play(msg: Message) {
8586
return;
8687
}
8788

88-
const stream = await yt.download(audio.id, {
89-
type: "video+audio",
90-
quality: "best",
91-
format: "mp4",
92-
});
89+
const [stream] = await Promise.all([
90+
yt.download(audio.id, {
91+
type: "video+audio",
92+
quality: "best",
93+
format: "mp4",
94+
}),
95+
msg.react("⬇️"),
96+
]);
9397

9498
if (!stream) {
9599
await Promise.all([
@@ -99,12 +103,13 @@ export default async function play(msg: Message) {
99103
return;
100104
}
101105

102-
await msg.react("⬇️");
103-
104106
let writeStream = fs.createWriteStream(tempPath);
105-
106107
for await (const chunk of Utils.streamToIterable(stream)) {
107-
writeStream.write(chunk);
108+
if (!writeStream.write(chunk)) {
109+
await new Promise<void>((resolve) =>
110+
writeStream.once("drain", () => resolve()),
111+
);
112+
}
108113
}
109114

110115
await new Promise<void>((resolve, reject) => {

src/commands/quiz.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { Message } from "../../types/message"
1+
import { Message } from "../../types/message";
22
import { quiz } from "../components/utils/data";
3-
import { newQuizAttempt } from "../components/services/quiz";
43
import log from "../components/utils/log";
4+
import redis from "../components/redis";
55

66
export const info = {
77
command: "quiz",
@@ -32,8 +32,9 @@ export default async function (msg: Message) {
3232
}
3333

3434
const messageReturn = await msg.reply(text);
35-
await Promise.all([
36-
newQuizAttempt(messageReturn, id.toString()),
37-
log.info("quiz", `Quiz question sent: ${response.question}`),
38-
]);
35+
redis.set(
36+
`quiz:${messageReturn.id.id}`,
37+
JSON.stringify({ quiz_id: id.toString() }),
38+
);
39+
log.info("quiz", `Quiz question sent: ${response.question}`);
3940
}

src/commands/stalk.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ export default async function (msg: Message) {
4545
Type: ${user.type}
4646
Mode: ${user.mode}
4747
Command Count: ${user.commandCount}
48+
Quiz Answered: ${user.quizAnswered}
49+
Quiz Answered Wrong: ${user.quizAnsweredWrong}
50+
Points: ${user.points}
4851
Last Seen: ${new Date(user.updatedAt).toLocaleString()}
4952
Blocked: ${
5053
isBlockPermanently

src/commands/top.ts

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { Message } from "../../types/message";
22
import log from "../components/utils/log";
3-
import { getUsers } from "../components/services/user";
3+
import {
4+
getUsersPoints,
5+
getUsersCommandCount,
6+
getUsersQuiz,
7+
} from "../components/services/user";
48

59
export const info = {
610
command: "top",
@@ -14,22 +18,41 @@ export const info = {
1418
export default async function (msg: Message) {
1519
if (!/^top$/i.test(msg.body)) return;
1620

17-
const user = await getUsers();
18-
if (!user || user.length === 0) {
19-
await msg.reply("No users found.");
20-
return;
21-
}
21+
const [usersPoints, usersCommandCount, usersQuiz] = await Promise.all([
22+
getUsersPoints(),
23+
getUsersCommandCount(),
24+
getUsersQuiz(),
25+
]);
2226

2327
const text = `
24-
\`Top Users by Activity:\`
28+
\`Points:\`
2529
26-
${user
27-
.sort((a, b) => (b.totalActivity ?? 0) - (a.totalActivity ?? 0))
28-
.slice(0, 50)
30+
${usersPoints
2931
.map((u, index) => {
3032
const displayName =
31-
u.name.length > 12 ? u.name.slice(0, 12) + ".." : u.name;
32-
return `${index + 1}. ${displayName}: ${u.totalActivity} Points`;
33+
u.name.length > 16 ? u.name.slice(0, 16) + ".. " : u.name;
34+
return `${index + 1}. ${displayName}: ${u.points} Points`;
35+
})
36+
.join("\n ")}
37+
38+
\`Quiz:\`
39+
40+
${usersQuiz
41+
.sort((a, b) => b.score - a.score)
42+
.map((u, index) => {
43+
const displayName =
44+
u.name.length > 16 ? u.name.slice(0, 16) + ".. " : u.name;
45+
return `${index + 1}. ${displayName}: ${u.score} Score`;
46+
})
47+
.join("\n ")}
48+
49+
\`Reputation:\`
50+
51+
${usersCommandCount
52+
.map((u, index) => {
53+
const displayName =
54+
u.name.length > 16 ? u.name.slice(0, 16) + ".. " : u.name;
55+
return `${index + 1}. ${displayName}: ${u.commandCount} Rep`;
3356
})
3457
.join("\n ")}
3558
`;

src/commands/video.ts

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,27 +45,28 @@ export default async function (msg: Message) {
4545
}
4646

4747
const yt = await Innertube.create({
48-
cache: new UniversalCache(false),
48+
cache: new UniversalCache(true, "./.youtubei"),
4949
generate_session_locally: true,
5050
player_id: "0004de42",
5151
});
5252

53-
const video: any = await search(yt, query);
53+
const [video] = await Promise.all([search(yt, query), msg.react("🔍")]);
5454
if (!video) {
5555
await msg.reply(`No youtube video found for "${query}".`);
5656
return;
5757
}
5858

5959
// Only allow audios shorter than 20 minutes (1200 seconds)
6060
if (video.duration && video.duration.seconds > 1200) {
61-
await msg.reply(
62-
"Opps, the video is quite long we can only process max of 20 minutes.",
63-
);
61+
await Promise.all([
62+
msg.reply(
63+
"Opps, the video is quite long we can only process max of 20 minutes.",
64+
),
65+
msg.react(""),
66+
]);
6467
return;
6568
}
6669

67-
await msg.react("🔍");
68-
6970
const tempDir = "./.temp";
7071
await fs.promises.mkdir(tempDir, { recursive: true });
7172
const tempPath = path.join(tempDir, `${video.video_id}.mp4`);
@@ -82,11 +83,14 @@ export default async function (msg: Message) {
8283
return;
8384
}
8485

85-
const stream = await yt.download(video.video_id, {
86-
type: "video+audio",
87-
quality: "best",
88-
format: "mp4",
89-
});
86+
const [stream] = await Promise.all([
87+
yt.download(video.video_id, {
88+
type: "video+audio",
89+
quality: "best",
90+
format: "mp4",
91+
}),
92+
msg.react("⬇️"),
93+
]);
9094

9195
if (!stream) {
9296
await Promise.all([
@@ -96,12 +100,13 @@ export default async function (msg: Message) {
96100
return;
97101
}
98102

99-
await msg.react("⬇️");
100-
101103
let writeStream = fs.createWriteStream(tempPath);
102-
103104
for await (const chunk of Utils.streamToIterable(stream)) {
104-
writeStream.write(chunk);
105+
if (!writeStream.write(chunk)) {
106+
await new Promise<void>((resolve) =>
107+
writeStream.once("drain", () => resolve()),
108+
);
109+
}
105110
}
106111

107112
await new Promise<void>((resolve, reject) => {

src/components/ai/agentHandler.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import crypto from "crypto";
12
import { openrouter, generateText } from "./openRouter";
23
import { groq } from "./groq";
34
import { gemini } from "./gemini";
@@ -9,11 +10,11 @@ const aiProvider = process.env.AI_PROVIDER || "groq";
910
const isQueryCachingEnabled = process.env.ALLOW_QUERY_CACHING === "true";
1011
const queryCachingCount = parseInt(
1112
process.env.QUERY_CACHING_COUNT || "1000",
12-
10
13+
10,
1314
);
1415
const queryCachingTTL = parseInt(process.env.QUERY_CACHING_TTL || "3600", 10);
1516
/*
16-
* As time goes by and new models are released,
17+
* As time goes by and new models are released,
1718
* these defaults may need to be updated.
1819
*/
1920
const openRouterModel =
@@ -25,7 +26,8 @@ const openAiModel = process.env.OPENAI_MODEL || "gpt-4o";
2526
const ollamaModel = process.env.OLLAMA_MODEL || "llama3.1";
2627

2728
function getCacheKey(prompt: string) {
28-
return `ai:prompt:${Buffer.from(prompt).toString("base64")}`;
29+
const hash = crypto.createHash("sha256").update(prompt).digest("hex");
30+
return `ai:prompt:${hash}`;
2931
}
3032

3133
export default async function (prompt: string, model?: string) {

src/components/events/edit.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ export default async function (
2323
const isMustResent = await getSetting("resent_edit");
2424
if (!isMustResent || isMustResent == "off") return;
2525

26-
const isGroup = !!msg.author;
27-
const user = (await getUserbyLid(msg.from)) || "Your";
26+
const user = (await getUserbyLid(msg.from)) || "User";
2827
await msg.reply(
29-
`${isGroup ? user : "Your"} message was edited from "${prevBody}".`,
28+
`${msg.author ? user : "Your"} message was edited from "${prevBody}".`,
3029
);
3130
}

0 commit comments

Comments
 (0)