Skip to content

Commit 854f939

Browse files
authored
Merge pull request #24 from ToastedDev/dev
feat: add ability to sync xp from other bots
2 parents 13d8448 + d5ae4e1 commit 854f939

File tree

4 files changed

+322
-3
lines changed

4 files changed

+322
-3
lines changed

api/index.ts

Lines changed: 248 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => {
206206
} catch (err) {
207207
return res.status(500).json({ message: "Internal server error", err });
208208
}
209-
case 'set':
209+
case 'set':
210210
if (!extraData || typeof extraData.channelId === "undefined") {
211211
return res.status(400).json({ message: "Illegal request" });
212212
}
@@ -308,7 +308,7 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => {
308308
return res.status(400).json({ message: "Illegal request" });
309309
}
310310

311-
if(!extraData || !extraData.user || !extraData.value) {
311+
if (!extraData || !extraData.user || !extraData.value) {
312312
return res.status(400).json({ message: "Illegal request" });
313313
}
314314

@@ -339,6 +339,61 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => {
339339
return res.status(500).json({ message: "Internal server error" });
340340
}
341341
}
342+
case "sync": {
343+
if (target !== "polaris" && target !== "mee6" && target !== "lurkr") {
344+
return res.status(400).json({ message: "Illegal request" });
345+
}
346+
347+
switch (target) {
348+
case "polaris": {
349+
try {
350+
const [err, success] = await syncFromPolaris(guild);
351+
if (err) {
352+
if (err instanceof Error && err.message === "Server not found in Polaris") {
353+
return res.status(404).json({ message: "Server not found in Polaris" });
354+
}
355+
return res.status(500).json({ message: "Internal server error", err });
356+
} else {
357+
return res.status(200).json(success);
358+
}
359+
} catch (err) {
360+
return res.status(500).json({ message: "Internal server error", err });
361+
}
362+
}
363+
case "mee6": {
364+
try {
365+
const [err, success] = await syncFromMee6(guild);
366+
if (err) {
367+
if (err instanceof Error && err.message === "Server not found in MEE6") {
368+
return res.status(404).json({ message: "Server not found in MEE6" });
369+
}
370+
return res.status(500).json({ message: "Internal server error", err });
371+
} else {
372+
return res.status(200).json(success);
373+
}
374+
} catch (err) {
375+
return res.status(500).json({ message: "Internal server error", err });
376+
}
377+
}
378+
case "lurkr": {
379+
try {
380+
const [err, success] = await syncFromLurkr(guild);
381+
if (err) {
382+
if (err instanceof Error && err.message === "Server not found in Lurkr") {
383+
return res.status(404).json({ message: "Server not found in Lurkr" });
384+
}
385+
return res.status(500).json({ message: "Internal server error", err });
386+
} else {
387+
return res.status(200).json(success);
388+
}
389+
} catch (err) {
390+
return res.status(500).json({ message: "Internal server error", err });
391+
}
392+
}
393+
default:
394+
return res.status(500).json({ message: "Internal server error" });
395+
}
396+
}
342397
default:
343398
return res.status(400).json({ message: "Illegal request" });
344399
}
@@ -445,3 +500,194 @@ async function adminRolesAdd(guild: string, role: string, level: number) {
445500
});
446501
}
447502
//#endregion
503+
504+
//#region Syncing
505+
async function syncFromPolaris(guild: string) {
506+
const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}`);
507+
const data = await res.json();
508+
if (data.apiError && data.code === "invalidServer") {
509+
return [new Error("Server not found in Polaris"), false];
510+
}
511+
const users = data.leaderboard;
512+
for (let i = 1; i < data.pageInfo.pageCount; i++) {
513+
const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}?page=${i + 1}`);
514+
const data = await res.json();
515+
users.push(...data.leaderboard);
516+
}
517+
518+
if (users.length === 0) {
519+
return [new Error("No users found"), false];
520+
}
521+
522+
try {
523+
for (const user of users) {
524+
const xpValue = user.xp;
525+
const level = Math.floor(Math.sqrt(xpValue / 100));
526+
const nextLevel = level + 1;
527+
const nextLevelXp = Math.pow(nextLevel, 2) * 100;
528+
const xpNeededForNextLevel = nextLevelXp - xpValue;
529+
const currentLevelXp = Math.pow(level, 2) * 100;
530+
const progressToNextLevel =
531+
((xpValue - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100;
532+
533+
await new Promise((resolve, reject) => {
534+
pool.query(
535+
`INSERT INTO users (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
536+
[
537+
user.id,
538+
guild,
539+
xpValue,
540+
user.avatar,
541+
user.username,
542+
user.nickname ?? user.displayName,
543+
level,
544+
xpNeededForNextLevel,
545+
progressToNextLevel.toFixed(2),
546+
],
547+
(err) => {
548+
if (err) {
549+
console.error("Error syncing from Polaris:", err);
550+
reject(err);
551+
} else {
552+
resolve(null);
553+
}
554+
},
555+
);
556+
});
557+
}
558+
return [null, true]
559+
} catch (err) {
560+
return [err, false];
561+
}
562+
563+
}
564+
565+
async function syncFromMee6(guild: string) {
566+
const res = await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${guild}?limit=1000&page=0`);
567+
const data = await res.json();
568+
if (data.status_code === 404) {
569+
return [new Error("Server not found in MEE6"), false];
570+
}
571+
const users = data.players;
572+
let pageNumber = 1;
573+
// this is needed because MEE6 doesn't give us the total amount of pages
574+
// eslint-disable-next-line no-constant-condition
575+
while (true) {
576+
const res = await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${guild}?limit=1000&page=${pageNumber}`);
577+
const data = await res.json();
578+
users.push(...data.players);
579+
if (data.players.length < 1000) break;
580+
pageNumber += 1;
581+
}
582+
583+
if (users.length === 0) {
584+
return [new Error("No users found"), false];
585+
}
586+
587+
try {
588+
for (const user of users) {
589+
const xpValue = user.xp;
590+
const level = Math.floor(Math.sqrt(xpValue / 100));
591+
const nextLevel = level + 1;
592+
const nextLevelXp = Math.pow(nextLevel, 2) * 100;
593+
const xpNeededForNextLevel = nextLevelXp - xpValue;
594+
const currentLevelXp = Math.pow(level, 2) * 100;
595+
const progressToNextLevel =
596+
((xpValue - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100;
597+
598+
await new Promise((resolve, reject) => {
599+
pool.query(
600+
`INSERT INTO users (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
601+
[
602+
user.id,
603+
guild,
604+
xpValue,
605+
`https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`,
606+
user.username,
607+
user.username,
608+
level,
609+
xpNeededForNextLevel,
610+
progressToNextLevel.toFixed(2),
611+
],
612+
(err) => {
613+
if (err) {
614+
console.error("Error syncing from MEE6:", err);
615+
reject(err);
616+
} else {
617+
resolve(null);
618+
}
619+
},
620+
);
621+
});
622+
}
623+
return [null, true]
624+
} catch (err) {
625+
return [err, false];
626+
}
627+
}
628+
629+
async function syncFromLurkr(guild: string) {
630+
const res = await fetch(`https://api.lurkr.gg/v2/levels/${guild}?page=1`);
631+
const data = await res.json();
632+
if (data.message === "Guild no found") {
633+
return [new Error("Server not found in Lurkr"), false];
634+
}
635+
const users = data.levels;
636+
637+
if (users.length === 0) {
638+
return [new Error("No users found"), false];
639+
}
640+
641+
let pageNumber = 2;
642+
// this is needed because Lurkr doesn't give us the total amount of pages
643+
// eslint-disable-next-line no-constant-condition
644+
while (true) {
645+
const res = await fetch(`https://api.lurkr.gg/v2/levels/${guild}?page=${pageNumber}`);
646+
const data = await res.json();
647+
users.push(...data.levels);
648+
if (data.levels.length < 100) break;
649+
pageNumber += 1;
650+
}
651+
652+
try {
653+
for (const user of users) {
654+
const xpValue = user.xp;
655+
const level = Math.floor(Math.sqrt(user.xp / 100));
656+
const nextLevel = level + 1;
657+
const nextLevelXp = Math.pow(nextLevel, 2) * 100;
658+
const xpNeededForNextLevel = nextLevelXp - user.xp;
659+
const currentLevelXp = Math.pow(level, 2) * 100;
660+
const progressToNextLevel =
661+
((user.xp - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100;
662+
663+
await new Promise((resolve, reject) => {
664+
pool.query(
665+
`INSERT INTO users (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
666+
[
667+
user.userId,
668+
guild,
669+
xpValue,
670+
`https://cdn.discordapp.com/avatars/${user.userId}/${user.user.avatar}.webp`,
671+
user.user.username,
672+
user.user.username,
673+
level,
674+
xpNeededForNextLevel,
675+
progressToNextLevel.toFixed(2),
676+
],
677+
(err) => {
678+
if (err) {
679+
console.error("Error syncing from Lurkr:", err);
680+
reject(err);
681+
} else {
682+
resolve(null);
683+
}
684+
},
685+
);
686+
});
687+
}
688+
return [null, true]
689+
} catch (err) {
690+
return [err, false];
691+
}
692+
}
693+
//#endregion

bot/commands.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import client from '.';
44
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type CommandInteraction, ChannelType, type APIApplicationCommandOption, GuildMember, AttachmentBuilder, ComponentType } from 'discord.js';
55
import { heapStats } from 'bun:jsc';
6-
import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, getUpdatesChannel, setUpdatesChannel, setXP, setLevel } from './utils/requestAPI';
6+
import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, getUpdatesChannel, setUpdatesChannel, setXP, setLevel, syncFromBot } from './utils/requestAPI';
77
import convertToLevels from './utils/convertToLevels';
88
import quickEmbed from './utils/quickEmbed';
99
import { Font, RankCardBuilder } from 'canvacord';
@@ -721,6 +721,65 @@ const commands: Record<string, Command> = {
721721
return;
722722
}
723723
}
724+
},
725+
sync: {
726+
data: {
727+
options: [{
728+
name: 'bot',
729+
description: 'Select the bot to sync XP data from',
730+
type: 3,
731+
required: true,
732+
choices: [
733+
{
734+
name: 'Polaris',
735+
value: 'polaris',
736+
},
737+
{
738+
name: 'MEE6',
739+
value: 'mee6',
740+
},
741+
{
742+
name: 'Lurkr',
743+
value: 'lurkr',
744+
},
745+
]
746+
}],
747+
name: 'sync',
748+
description: 'Sync XP data from another bot!',
749+
integration_types: [0],
750+
contexts: [0, 2],
751+
},
752+
execute: async (interaction) => {
753+
if (!interaction.memberPermissions?.has('ManageGuild')) {
754+
const errorEmbed = quickEmbed({
755+
color: 'Red',
756+
title: 'Error!',
757+
description: 'Missing permissions: `Manage Server`'
758+
}, interaction);
759+
await interaction.reply({
760+
ephemeral: true,
761+
embeds: [errorEmbed]
762+
})
763+
.catch(console.error);
764+
return;
765+
}
766+
767+
const bot = interaction.options.get('bot')?.value;
768+
const formattedBotNames = {
769+
'polaris': 'Polaris',
770+
'mee6': 'MEE6',
771+
'lurkr': 'Lurkr'
772+
};
773+
774+
await interaction.reply({ ephemeral: true, content: `Syncing data from ${formattedBotNames[bot as keyof typeof formattedBotNames]}...` });
775+
const apiSuccess = await syncFromBot(interaction.guildId as string, bot as string);
776+
if (!apiSuccess) {
777+
await interaction.editReply({ content: `Error syncing data! This might mean that ${formattedBotNames[bot as keyof typeof formattedBotNames]} is not set up for this server, or the leaderboard for this server is not public.` });
778+
return;
779+
}
780+
await interaction.editReply({ content: 'Data synced!' });
781+
return;
782+
}
724783
}
725784
};
726785

bot/utils/requestAPI.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,3 +215,17 @@ export async function setCooldown(guild: string, cooldown: number) {
215215
return response.status === 200;
216216
}
217217
//#endregion
218+
219+
//#region Sync
220+
export async function syncFromBot(guild: string, bot: string) {
221+
const response = await fetch(`http://localhost:18103/admin/sync/${guild}/${bot}`, {
222+
"headers": {
223+
'Content-Type': 'application/json',
224+
'Authorization': process.env.AUTH as string,
225+
},
226+
"body": JSON.stringify({}),
227+
"method": "POST"
228+
});
229+
return response.status === 200;
230+
}
231+
//#endregion

bun.lockb

100644100755
-1.58 KB
Binary file not shown.

0 commit comments

Comments
 (0)