Skip to content
This repository was archived by the owner on Oct 9, 2025. It is now read-only.

Commit 9ff2e7e

Browse files
authored
feat: global user & server message leaderboards (#245)
1 parent d82672d commit 9ff2e7e

File tree

13 files changed

+432
-85
lines changed

13 files changed

+432
-85
lines changed

locales/en.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,18 @@ errors:
408408
banned: |
409409
{emoji} You have been banned from using InterChat for violating our [guidelines](https://interchat.tech/guidelines).
410410
If you think an appeal is applicable create a ticket in the [support server]( {support_invite} ).
411+
412+
config:
413+
setInvite:
414+
success: |
415+
### {emoji} Invite Link Set
416+
- Your server's invite will be used when people use `/joinserver`.
417+
- It will be displayed in `/leaderboard server`.
418+
removed: '{emoji} Invite removed successfully!'
419+
invalid: '{emoji} Invalid invite. Please make sure you have entered a valid invite link. Eg. `https://discord.gg/discord`'
420+
notFromServer: '{emoji} This invite is not from this server.'
421+
422+
411423
global:
412424
webhookNoLongerExists: '{emoji} The webhook for this channel no longer exists. To continue using InterChat, please re-create the webhook by using `/connection unpause`.'
413425
noReason: No reason provided.

prisma/schema.prisma

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -186,19 +186,20 @@ model UserData {
186186
}
187187

188188
model Announcement {
189-
id String @id @default(auto()) @map("_id") @db.ObjectId
190-
title String
191-
content String
192-
thumbnailUrl String?
193-
imageUrl String?
194-
createdAt DateTime @default(now())
189+
id String @id @default(auto()) @map("_id") @db.ObjectId
190+
title String
191+
content String
192+
thumbnailUrl String?
193+
imageUrl String?
194+
createdAt DateTime @default(now())
195195
}
196196

197197
model ServerData {
198198
id String @id @map("_id") @db.String
199199
premiumStatus Boolean @default(false)
200200
createdAt DateTime @default(now())
201201
updatedAt DateTime @updatedAt
202+
inviteCode String?
202203
messageCount Int @default(0)
203204
lastMessageAt DateTime @default(now())
204205
}

src/commands/Information/rank.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ export default class RankCommand extends BaseCommand {
8383
});
8484
}
8585

86-
async execute(ctx: Context): Promise<void> {
86+
async execute(ctx: Context) {
87+
await ctx.reply('This command is currently disabled. Use `/leaderboard user` instead.');
88+
}
89+
90+
async executeOld(ctx: Context): Promise<void> {
8791
await ctx.deferReply();
8892

8993
try {

src/commands/Main/config/index.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright (C) 2025 InterChat
3+
*
4+
* InterChat is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as published
6+
* by the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* InterChat is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with InterChat. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
import ConfigSetInviteSubcommand from '#src/commands/Main/config/set-invite.js';
19+
import BaseCommand from '#src/core/BaseCommand.js';
20+
export default class ConfigCommand extends BaseCommand {
21+
constructor() {
22+
super({
23+
name: 'config',
24+
description: 'Configure Server settings for InterChat.',
25+
types: { slash: true, prefix: true },
26+
subcommands: {
27+
'set-invite': new ConfigSetInviteSubcommand(),
28+
},
29+
});
30+
}
31+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
* Copyright (C) 2025 InterChat
3+
*
4+
* InterChat is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as published
6+
* by the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* InterChat is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with InterChat. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
import BaseCommand from '#src/core/BaseCommand.js';
19+
import Context from '#src/core/CommandContext/Context.js';
20+
import db from '#src/utils/Db.js';
21+
import { ApplicationCommandOptionType, Invite } from 'discord.js';
22+
23+
export default class ConfigSetInviteSubcommand extends BaseCommand {
24+
constructor() {
25+
super({
26+
name: 'set-invite',
27+
description:
28+
'Set the invite link for the server. People can use it to join through InterChat leaderboards.',
29+
types: { slash: true, prefix: true },
30+
options: [
31+
{
32+
type: ApplicationCommandOptionType.String,
33+
name: 'invite',
34+
description: 'The invite link to set for the server. (Leave empty to remove)',
35+
required: false,
36+
},
37+
],
38+
});
39+
}
40+
async execute(ctx: Context) {
41+
if (!ctx.inGuild()) return;
42+
await ctx.deferReply();
43+
44+
const inviteLink = ctx.options.getString('invite');
45+
if (!inviteLink?.length) {
46+
await db.serverData.upsert({
47+
where: { id: ctx.guild.id },
48+
create: { id: ctx.guildId },
49+
update: { inviteCode: null },
50+
});
51+
52+
await ctx.replyEmbed('config.setInvite.removed', {
53+
edit: true,
54+
t: { emoji: ctx.getEmoji('tick_icon') },
55+
});
56+
return;
57+
}
58+
59+
const inviteCode = inviteLink.match(Invite.InvitesPattern)?.[1];
60+
if (!inviteCode) {
61+
await ctx.replyEmbed('config.setInvite.invalid', {
62+
edit: true,
63+
t: { emoji: ctx.getEmoji('x_icon') },
64+
});
65+
return;
66+
}
67+
68+
const inviteInGuild = (await ctx.guild.invites.fetch()).get(inviteCode);
69+
if (!inviteInGuild) {
70+
await ctx.replyEmbed('config.setInvite.notFromServer', {
71+
edit: true,
72+
t: { emoji: ctx.getEmoji('x_icon') },
73+
});
74+
return;
75+
}
76+
77+
await db.serverData.upsert({
78+
where: { id: ctx.guild.id },
79+
create: { id: ctx.guildId, inviteCode },
80+
update: { inviteCode },
81+
});
82+
83+
await ctx.replyEmbed('config.setInvite.success', {
84+
edit: true,
85+
t: { emoji: ctx.getEmoji('tick_icon') },
86+
});
87+
}
88+
}

src/commands/Main/leaderboard.ts

Lines changed: 0 additions & 76 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright (C) 2025 InterChat
3+
*
4+
* InterChat is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as published
6+
* by the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* InterChat is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with InterChat. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
import ServerLeaderboardCommand from '#src/commands/Main/leaderboard/server.js';
19+
import UserLeaderboardCommand from '#src/commands/Main/leaderboard/user.js';
20+
import BaseCommand from '#src/core/BaseCommand.js';
21+
export default class LeaderboardCommand extends BaseCommand {
22+
constructor() {
23+
super({
24+
name: 'leaderboard',
25+
description: 'leaderboard rahhh',
26+
types: { slash: true, prefix: true },
27+
subcommands: {
28+
user: new UserLeaderboardCommand(),
29+
server: new ServerLeaderboardCommand(),
30+
},
31+
});
32+
}
33+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (C) 2025 InterChat
3+
*
4+
* InterChat is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as published
6+
* by the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* InterChat is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with InterChat. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
import BaseCommand from '#src/core/BaseCommand.js';
19+
import type Context from '#src/core/CommandContext/Context.js';
20+
import Constants from '#src/utils/Constants.js';
21+
import { formatServerLeaderboard, getLeaderboard } from '#src/utils/Leaderboard.js';
22+
import { resolveColor } from 'discord.js';
23+
24+
export default class ServerLeaderboardCommand extends BaseCommand {
25+
constructor() {
26+
super({
27+
name: 'server',
28+
description: 'Shows the global server leaderboard for InterChat (with invites).',
29+
types: { slash: true, prefix: true },
30+
});
31+
}
32+
33+
async execute(ctx: Context) {
34+
const leaderboard = await getLeaderboard('server', 10);
35+
const leaderboardTable = await formatServerLeaderboard(leaderboard, ctx.client);
36+
37+
await ctx.reply({
38+
embeds: [
39+
{
40+
title: `${ctx.getEmoji('hash_icon')} Global Server Leaderboard`,
41+
description: leaderboardTable,
42+
color: resolveColor(Constants.Colors.invisible),
43+
footer: { text: 'Resets every month. Send a message in any hub to get on it!' },
44+
},
45+
],
46+
});
47+
}
48+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright (C) 2025 InterChat
3+
*
4+
* InterChat is free software: you can redistribute it and/or modify
5+
* it under the terms of the GNU Affero General Public License as published
6+
* by the Free Software Foundation, either version 3 of the License, or
7+
* (at your option) any later version.
8+
*
9+
* InterChat is distributed in the hope that it will be useful,
10+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
* GNU Affero General Public License for more details.
13+
*
14+
* You should have received a copy of the GNU Affero General Public License
15+
* along with InterChat. If not, see <https://www.gnu.org/licenses/>.
16+
*/
17+
18+
import BaseCommand from '#src/core/BaseCommand.js';
19+
import type Context from '#src/core/CommandContext/Context.js';
20+
import Constants from '#src/utils/Constants.js';
21+
import { formatUserLeaderboard, getLeaderboard } from '#src/utils/Leaderboard.js';
22+
import { resolveColor } from 'discord.js';
23+
24+
export default class UserLeaderboardCommand extends BaseCommand {
25+
constructor() {
26+
super({
27+
name: 'user',
28+
description: 'Shows the global user leaderboard for InterChat (with messages).',
29+
types: { slash: true, prefix: true },
30+
});
31+
}
32+
33+
async execute(ctx: Context) {
34+
const leaderboard = await getLeaderboard('user', 10);
35+
const leaderboardTable = await formatUserLeaderboard(leaderboard, ctx.client);
36+
37+
await ctx.reply({
38+
embeds: [
39+
{
40+
title: `${ctx.getEmoji('hash_icon')} Global User Leaderboard`,
41+
description: leaderboardTable,
42+
color: resolveColor(Constants.Colors.invisible),
43+
footer: { text: 'Resets every month. Send a message in any hub to get on it!' },
44+
},
45+
],
46+
});
47+
}
48+
}

0 commit comments

Comments
 (0)