Skip to content

Commit e2ffaf4

Browse files
committed
feat(bot): added hourly leaderboard updates
also tried messing around with eslint - never again
1 parent a07319b commit e2ffaf4

16 files changed

+188
-182
lines changed

.eslintrc.json

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
{
2+
"extends": "eslint:recommended",
3+
"env": {
4+
"node": true,
5+
"es6": true
6+
},
7+
"parserOptions": {
8+
"sourceType": "module",
9+
"ecmaVersion": "latest"
10+
},
11+
"rules": {
12+
"arrow-spacing": ["warn", { "before": true, "after": true }],
13+
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
14+
"comma-dangle": ["error", "always-multiline"],
15+
"comma-spacing": "error",
16+
"comma-style": "error",
17+
"curly": ["error", "multi-line", "consistent"],
18+
"dot-location": ["error", "property"],
19+
"handle-callback-err": "off",
20+
"indent": ["error", "tab"],
21+
"keyword-spacing": "error",
22+
"max-nested-callbacks": ["error", { "max": 4 }],
23+
"max-statements-per-line": ["error", { "max": 2 }],
24+
"no-console": "off",
25+
"no-empty-function": "error",
26+
"no-floating-decimal": "error",
27+
"no-inline-comments": "error",
28+
"no-lonely-if": "error",
29+
"no-multi-spaces": "error",
30+
"no-multiple-empty-lines": [
31+
"error",
32+
{ "max": 2, "maxEOF": 1, "maxBOF": 0 }
33+
],
34+
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
35+
"no-trailing-spaces": ["error"],
36+
"no-var": "error",
37+
"object-curly-spacing": ["error", "always"],
38+
"prefer-const": "error",
39+
"quotes": ["error", "single"],
40+
"semi": ["error", "always"],
41+
"space-before-blocks": "error",
42+
"space-before-function-paren": [
43+
"error",
44+
{
45+
"anonymous": "never",
46+
"named": "never",
47+
"asyncArrow": "always"
48+
}
49+
],
50+
"space-in-parens": "error",
51+
"space-infix-ops": "error",
52+
"space-unary-ops": "error",
53+
"spaced-comment": "error",
54+
"yoda": "error"
55+
}
56+
}

api/src/db/queries/updates.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ export async function disableUpdates(guildId: string): Promise<[QueryError | nul
4848
}
4949

5050
export async function setUpdatesChannel(guildId: string, channelId: string | null): Promise<[QueryError | null, boolean]> {
51+
console.log("Setting updates channel", guildId, channelId);
5152
return new Promise((resolve, reject) => {
5253
pool.query(
5354
`
@@ -67,3 +68,20 @@ export async function setUpdatesChannel(guildId: string, channelId: string | nul
6768
);
6869
});
6970
}
71+
72+
export async function getAllServersWithUpdatesEnabled(): Promise<[QueryError | null, Updates[]]> {
73+
return new Promise((resolve, reject) => {
74+
pool.query(
75+
`
76+
SELECT id, updates_channel_id, updates_enabled FROM guilds WHERE updates_enabled = TRUE
77+
`,
78+
(err, results) => {
79+
if (err) {
80+
reject([err, []]);
81+
} else {
82+
resolve([null, results as Updates[]]);
83+
}
84+
},
85+
);
86+
});
87+
}

api/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import express, { type NextFunction, type Request, type Response } from "express";
22
import cors from "cors";
3-
import { getBotInfo, getGuild, getUser, getUsers, initTables, pool, updateGuild, enableUpdates, disableUpdates, setCooldown, setUpdatesChannel, setXP, setLevel, removeGuild, removeUser } from "./db";
3+
import { getBotInfo, getGuild, getUser, getUsers, initTables, pool, updateGuild, enableUpdates, disableUpdates, setCooldown, setUpdatesChannel, setXP, setLevel, removeGuild, removeUser, getAllServersWithUpdatesEnabled } from "./db";
44

55
const app = express();
66
const PORT = 18103;
@@ -295,6 +295,17 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => {
295295
return res.status(500).json({ message: 'Internal server error', err });
296296
}
297297
default:
298+
if (guild == "all") {
299+
try {
300+
const [err, data] = await getAllServersWithUpdatesEnabled();
301+
if (err) {
302+
return res.status(500).json({ message: "Internal server error", err });
303+
}
304+
return res.status(200).json(data);
305+
} catch (error) {
306+
return res.status(500).json({ message: "Internal server error" });
307+
}
308+
}
298309
try {
299310
const [err, data] = await getGuild(guild);
300311
if (err) {

bot/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"dependencies": {
1010
"canvacord": "^6.0.2",
1111
"colorthief": "^2.4.0",
12+
"cron": "^3.1.7",
1213
"discord.js": "^14.15.3"
1314
},
1415
"devDependencies": {

bot/src/commands.ts

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, ena
77
import convertToLevels from './utils/convertToLevels';
88
import quickEmbed from './utils/quickEmbed';
99
import { Font, RankCardBuilder } from 'canvacord';
10+
import leaderboardEmbed from './utils/leaderboardEmbed';
1011

1112
Font.loadDefault();
1213

@@ -273,41 +274,8 @@ const commands: Record<string, Command> = {
273274
const guild = interaction.guild?.id;
274275

275276
try {
276-
const leaderboard = await getGuildLeaderboard(guild as string);
277-
278-
if (leaderboard.length === 0) {
279-
await interaction.reply('No leaderboard data available.');
280-
return;
281-
}
282-
283-
// Create a new embed using the custom embed function
284-
const leaderboardEmbed = quickEmbed({
285-
color: 'Blurple',
286-
title: `Leaderboard for ${interaction.guild?.name}`,
287-
description: 'Top 10 Users'
288-
}, interaction);
289-
290-
// Add a field for each user with a mention
291-
leaderboard.leaderboard.slice(0, 10).forEach((entry: { id: string; xp: number; }, index: number) => {
292-
leaderboardEmbed.addFields([
293-
{
294-
name: `${index + 1}.`,
295-
value: `<@${entry.id}>: ${entry.xp.toLocaleString("en-US")} XP`,
296-
inline: false
297-
}
298-
]);
299-
});
300-
301-
const button = new ButtonBuilder()
302-
.setLabel('Leaderboard')
303-
.setURL(`https://chatr.imgalvin.me/leaderboard/${interaction.guildId}`)
304-
.setStyle(ButtonStyle.Link);
305-
306-
const row = new ActionRowBuilder<ButtonBuilder>()
307-
.addComponents(button);
308-
309-
// Send the embed
310-
await interaction.reply({ embeds: [leaderboardEmbed], components: [row] });
277+
const [embed, row] = await leaderboardEmbed(guild as string, interaction);
278+
await interaction.reply({ embeds: [embed], components: [row] });
311279
} catch (error) {
312280
console.error('Error executing command:', error);
313281
await interaction.reply('There was an error retrieving the leaderboard.');

bot/src/events/ready.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { ActivityType, Events, PresenceUpdateStatus } from 'discord.js';
22
import client from '../index';
3+
import cron from 'cron';
4+
import sendAutoUpdates from '../utils/sendAutoUpdates';
35

46
// update the bot's presence
57
function updatePresence() {
@@ -19,6 +21,9 @@ function updatePresence() {
1921
client.once(Events.ClientReady, async (bot) => {
2022
console.log(`Ready! Logged in as ${bot.user?.tag}`);
2123
updatePresence();
24+
// Create a cron job to update the server count in the status every minute
25+
const job = new cron.CronJob('0 * * * *', sendAutoUpdates);
26+
job.start();
2227
});
2328

2429
// Update the server count in the status every minute

bot/src/utils/leaderboardEmbed.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ActionRowBuilder, ButtonBuilder, ButtonStyle } from "discord.js";
2+
import quickEmbed from "./quickEmbed";
3+
import { getGuildLeaderboard } from "./requestAPI";
4+
import client from "..";
5+
6+
export default async function (guild: string, interaction?: any) {
7+
const leaderboard = await getGuildLeaderboard(guild);
8+
9+
if (leaderboard.length === 0) {
10+
await interaction.reply('No leaderboard data available.');
11+
return;
12+
}
13+
14+
// Create a new embed using the custom embed function
15+
const leaderboardEmbed = quickEmbed({
16+
color: 'Blurple',
17+
title: `Leaderboard for ${interaction ? interaction.guild?.name : (await client.guilds.fetch(guild)).name}`,
18+
description: 'Top 10 Users'
19+
}, interaction);
20+
21+
// Add a field for each user with a mention
22+
leaderboard.leaderboard.slice(0, 10).forEach((entry: { id: string; xp: number; }, index: number) => {
23+
leaderboardEmbed.addFields([
24+
{
25+
name: `${index + 1}.`,
26+
value: `<@${entry.id}>: ${entry.xp.toLocaleString("en-US")} XP`,
27+
inline: false
28+
}
29+
]);
30+
});
31+
32+
const button = new ButtonBuilder()
33+
.setLabel('Leaderboard')
34+
.setURL(`https://chatr.fun/leaderboard/${guild}`)
35+
.setStyle(ButtonStyle.Link);
36+
37+
const row = new ActionRowBuilder<ButtonBuilder>()
38+
.addComponents(button);
39+
40+
return [leaderboardEmbed, row];
41+
}

bot/src/utils/quickEmbed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default function (
1414
text:
1515
interaction?.client.user.displayName ??
1616
client?.user?.displayName ??
17-
'No name',
17+
'Chatr',
1818
iconURL:
1919
interaction?.client?.user?.avatarURL() ??
2020
client?.user?.avatarURL() ??

bot/src/utils/requestAPI.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,24 @@ export async function disableUpdates(guild: string) {
210210
});
211211
return response.status === 200;
212212
}
213+
214+
export async function getAllGuildsWithUpdatesEnabled() {
215+
const response = await fetch(`http://localhost:18103/admin/updates/all/get`, {
216+
"headers": {
217+
'Content-Type': 'application/json',
218+
'Authorization': process.env.AUTH as string,
219+
},
220+
"body": JSON.stringify({}),
221+
"method": "POST"
222+
});
223+
return response.json();
224+
}
213225
//#endregion
214226

215227
//#region Cooldowns
216228
export async function getCooldown(guild: string) {
217229
const response = await fetch(`http://localhost:18103/admin/cooldown/${guild}/get`, {
218-
"headers": {
230+
"headers": {
219231
'Content-Type': 'application/json',
220232
'Authorization': process.env.AUTH as string,
221233
},
@@ -228,7 +240,7 @@ export async function getCooldown(guild: string) {
228240
export async function setCooldown(guild: string, cooldown: number) {
229241
const response = await fetch(`http://localhost:18103/admin/cooldown/${guild}/set`, {
230242
"headers": {
231-
'Content-Type': 'application/json',
243+
'Content-Type': 'application/json',
232244
'Authorization': process.env.AUTH as string,
233245
},
234246
"body": JSON.stringify({ extraData: { cooldown } }),

bot/src/utils/sendAutoUpdates.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { TextChannel } from "discord.js";
2+
import client from "..";
3+
import leaderboardEmbed from "./leaderboardEmbed";
4+
import { getAllGuildsWithUpdatesEnabled } from "./requestAPI";
5+
6+
export default async function () {
7+
const allGuildsData = await getAllGuildsWithUpdatesEnabled()
8+
9+
// TODO: Type guild
10+
allGuildsData.forEach(async (guild: any) => {
11+
const [embed, row] = await leaderboardEmbed(guild.id)
12+
const channel = await client.channels.fetch(guild.updates_channel_id) as TextChannel;
13+
await channel?.send({ embeds: [embed], components: [row] });
14+
})
15+
}

bun.lockb

-472 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)