From e9396a3efe036c2313283d9e88dae1c8a784d73f Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Tue, 16 Jul 2024 22:17:27 +0800 Subject: [PATCH 1/6] feat: add ability to sync xp from polaris (wip) --- api/index.ts | 92 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/api/index.ts b/api/index.ts index d169605..553f2cb 100644 --- a/api/index.ts +++ b/api/index.ts @@ -175,7 +175,7 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { switch (target) { case "enable": try { - const [err, success] = await enableUpdates(guild, extraData.channelId); + const [err, success] = await enableUpdates(guild); if (err) { return res.status(500).json({ message: "Internal server error", err }); } else { @@ -328,6 +328,28 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { return res.status(500).json({ message: "Internal server error" }); } } + case "sync": { + if(target !== "polaris") { + return res.status(400).json({ message: "Illegal request" }); + } + + switch(target) { + case "polaris": { + try { + const [err, success] = await syncFromPolaris(guild); + if (err) { + return res.status(500).json({ message: "Internal server error", err }); + } else { + return res.status(200).json(success); + } + } catch (err) { + return res.status(500).json({ message: "Internal server error", err }); + } + } + default: + return res.status(500).json({ message: "Internal server error" }); + } + } default: return res.status(400).json({ message: "Illegal request" }); } @@ -434,3 +456,71 @@ async function adminRolesAdd(guild: string, role: string, level: number) { }); } //#endregion + +//#region Syncing +async function syncFromPolaris(guild: string) { + const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}`); + const data = await res.json(); + const users = data.leaderboard; + for(let i = 1; i < data.pageInfo.pageCount; i++) { + const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}?page=${i + 1}`); + const data = await res.json(); + users.push(...data.leaderboard); + } + + if(users.length === 0) { + return [new Error("No users found"), false]; + } + + console.log(users.length) + + const insertQuery = ` + INSERT INTO users + (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) + VALUES + `; + + const insertValues: string[] = []; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + for(const _user of users) { + insertValues.push(`(?, ?, ?, ?, ?, ?, ?, ?, ?)`); + } + + console.log(insertValues.length) + + const formattedUsers = users.map((user: { id: string, xp: number, avatar: string, username: string, nickname: string, displayName: string }) => { + const xpValue = user.xp; + const level = Math.floor(Math.sqrt(xpValue / 100)); + const nextLevel = level + 1; + const nextLevelXp = Math.pow(nextLevel, 2) * 100; + const xpNeededForNextLevel = nextLevelXp - xpValue; + const currentLevelXp = Math.pow(level, 2) * 100; + const progressToNextLevel = + ((xpValue - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100; + return [ + user.id, + guild, + xpValue, + user.avatar, + user.username, + user.nickname ?? user.displayName, + level, + xpNeededForNextLevel, + progressToNextLevel.toFixed(2), + ]; + }) + + return new Promise((resolve, reject) => { + pool.query(insertQuery + "\n" + insertValues.join(","), formattedUsers, (err) => + { + if (err) { + console.error("Error syncing from Polaris:", err); + reject([err, false]); + } else { + resolve([null, true]); + } + }); + }); +} +//#endregion From 4bf983d8213b79777a59a00c2368aee5b0cb72cb Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Tue, 16 Jul 2024 22:34:44 +0800 Subject: [PATCH 2/6] fix: syncing from polaris actually works now --- api/index.ts | 87 +++++++++++++++++++++++----------------------------- 1 file changed, 39 insertions(+), 48 deletions(-) diff --git a/api/index.ts b/api/index.ts index 553f2cb..cbcad04 100644 --- a/api/index.ts +++ b/api/index.ts @@ -472,55 +472,46 @@ async function syncFromPolaris(guild: string) { return [new Error("No users found"), false]; } - console.log(users.length) - - const insertQuery = ` - INSERT INTO users - (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) - VALUES - `; - - const insertValues: string[] = []; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - for(const _user of users) { - insertValues.push(`(?, ?, ?, ?, ?, ?, ?, ?, ?)`); + try { + for(const user of users) { + const xpValue = user.xp; + const level = Math.floor(Math.sqrt(xpValue / 100)); + const nextLevel = level + 1; + const nextLevelXp = Math.pow(nextLevel, 2) * 100; + const xpNeededForNextLevel = nextLevelXp - xpValue; + const currentLevelXp = Math.pow(level, 2) * 100; + const progressToNextLevel = + ((xpValue - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100; + + await new Promise((resolve, reject) => { + pool.query( + `INSERT INTO users (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + user.id, + guild, + xpValue, + user.avatar, + user.username, + user.nickname ?? user.displayName, + level, + xpNeededForNextLevel, + progressToNextLevel.toFixed(2), + ], + (err) => { + if (err) { + console.error("Error syncing from Polaris:", err); + reject(err); + } else { + resolve(null); + } + }, + ); + }); + } + return [null, true] + } catch (err) { + return [err, false]; } - console.log(insertValues.length) - - const formattedUsers = users.map((user: { id: string, xp: number, avatar: string, username: string, nickname: string, displayName: string }) => { - const xpValue = user.xp; - const level = Math.floor(Math.sqrt(xpValue / 100)); - const nextLevel = level + 1; - const nextLevelXp = Math.pow(nextLevel, 2) * 100; - const xpNeededForNextLevel = nextLevelXp - xpValue; - const currentLevelXp = Math.pow(level, 2) * 100; - const progressToNextLevel = - ((xpValue - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100; - return [ - user.id, - guild, - xpValue, - user.avatar, - user.username, - user.nickname ?? user.displayName, - level, - xpNeededForNextLevel, - progressToNextLevel.toFixed(2), - ]; - }) - - return new Promise((resolve, reject) => { - pool.query(insertQuery + "\n" + insertValues.join(","), formattedUsers, (err) => - { - if (err) { - console.error("Error syncing from Polaris:", err); - reject([err, false]); - } else { - resolve([null, true]); - } - }); - }); } //#endregion From e8815e5eed353e3adf54b694225e25b82cdb8db9 Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Tue, 16 Jul 2024 22:51:20 +0800 Subject: [PATCH 3/6] feat(bot): add /sync command --- api/index.ts | 6 +++++ bot/commands.ts | 51 ++++++++++++++++++++++++++++++++++++++++- bot/utils/requestAPI.ts | 14 +++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/api/index.ts b/api/index.ts index cbcad04..4d6b623 100644 --- a/api/index.ts +++ b/api/index.ts @@ -338,6 +338,9 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { try { const [err, success] = await syncFromPolaris(guild); if (err) { + if(err instanceof Error && err.message === "Server not found in Polaris") { + return res.status(404).json({ message: "Server not found in Polaris" }); + } return res.status(500).json({ message: "Internal server error", err }); } else { return res.status(200).json(success); @@ -461,6 +464,9 @@ async function adminRolesAdd(guild: string, role: string, level: number) { async function syncFromPolaris(guild: string) { const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}`); const data = await res.json(); + if(data.apiError && data.code === "invalidServer") { + return [new Error("Server not found in Polaris"), false]; + } const users = data.leaderboard; for(let i = 1; i < data.pageInfo.pageCount; i++) { const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}?page=${i + 1}`); diff --git a/bot/commands.ts b/bot/commands.ts index 190d32c..60dcc6c 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -3,7 +3,7 @@ import client from '.'; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type CommandInteraction, ChannelType, type APIApplicationCommandOption, GuildMember, AttachmentBuilder, ComponentType } from 'discord.js'; import { heapStats } from 'bun:jsc'; -import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, getUpdatesChannel, setUpdatesChannel, setXP, setLevel } from './utils/requestAPI'; +import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, getUpdatesChannel, setUpdatesChannel, setXP, setLevel, syncFromPolaris } from './utils/requestAPI'; import convertToLevels from './utils/convertToLevels'; import quickEmbed from './utils/quickEmbed'; import { Font, RankCardBuilder } from 'canvacord'; @@ -721,6 +721,55 @@ const commands: Record = { return; } } + }, + sync: { + data: { + options: [{ + name: 'bot', + description: 'Select the bot to sync XP data from', + type: 3, + required: true, + choices: [ + { + name: 'Polaris', + value: 'polaris', + }, + ] + }], + name: 'sync', + description: 'Sync XP data from another bot!', + integration_types: [0], + contexts: [0, 2], + }, + execute: async (interaction) => { + if (!interaction.memberPermissions?.has('ManageGuild')) { + const errorEmbed = quickEmbed({ + color: 'Red', + title: 'Error!', + description: 'Missing permissions: `Manage Server`' + }, interaction); + await interaction.reply({ + ephemeral: true, + embeds: [errorEmbed] + }) + .catch(console.error); + return; + } + + const bot = interaction.options.get('bot')?.value; + + let apiSuccess; + switch (bot) { + case 'polaris': + apiSuccess = await syncFromPolaris(interaction.guildId as string); + if (!apiSuccess) { + await interaction.reply({ ephemeral: true, content: 'Error syncing data! This might mean that Polaris is not set up for this server, or the leaderboard for this server is not public.' }); + return; + } + await interaction.reply({ ephemeral: true, content: 'Data synced!' }); + return; + } + } } }; diff --git a/bot/utils/requestAPI.ts b/bot/utils/requestAPI.ts index 9945cfb..01a93f5 100644 --- a/bot/utils/requestAPI.ts +++ b/bot/utils/requestAPI.ts @@ -205,3 +205,17 @@ export async function setCooldown(guild: string, cooldown: number) { return response.status === 200; } //#endregion + +//#region Sync +export async function syncFromPolaris(guild: string) { + const response = await fetch(`http://localhost:18103/admin/sync/${guild}/polaris`, { + "headers": { + 'Content-Type': 'application/json', + 'Authorization': process.env.AUTH as string, + }, + "body": JSON.stringify({}), + "method": "POST" + }); + return response.status === 200; +} +//#endregion From c5bfb8083972b2b88f45554d2a43b96efadccf1c Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Wed, 17 Jul 2024 19:22:03 +0800 Subject: [PATCH 4/6] feat(syncing): allow syncing from mee6 --- api/index.ts | 97 ++++++++++++++++++++++++++++++++++++----- bot/commands.ts | 29 +++++++----- bot/utils/requestAPI.ts | 4 +- 3 files changed, 106 insertions(+), 24 deletions(-) diff --git a/api/index.ts b/api/index.ts index 4d6b623..e13b28b 100644 --- a/api/index.ts +++ b/api/index.ts @@ -195,7 +195,7 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { } catch (err) { return res.status(500).json({ message: "Internal server error", err }); } - case 'set': + case 'set': if (!extraData || typeof extraData.channelId === "undefined") { return res.status(400).json({ message: "Illegal request" }); } @@ -297,7 +297,7 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { return res.status(400).json({ message: "Illegal request" }); } - if(!extraData || !extraData.user || !extraData.value) { + if (!extraData || !extraData.user || !extraData.value) { return res.status(400).json({ message: "Illegal request" }); } @@ -329,16 +329,16 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { } } case "sync": { - if(target !== "polaris") { + if (target !== "polaris" && target !== "mee6") { return res.status(400).json({ message: "Illegal request" }); } - - switch(target) { + + switch (target) { case "polaris": { try { const [err, success] = await syncFromPolaris(guild); if (err) { - if(err instanceof Error && err.message === "Server not found in Polaris") { + if (err instanceof Error && err.message === "Server not found in Polaris") { return res.status(404).json({ message: "Server not found in Polaris" }); } return res.status(500).json({ message: "Internal server error", err }); @@ -349,6 +349,21 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { return res.status(500).json({ message: "Internal server error", err }); } } + case "mee6": { + try { + const [err, success] = await syncFromMee6(guild); + if (err) { + if (err instanceof Error && err.message === "Server not found in MEE6") { + return res.status(404).json({ message: "Server not found in MEE6" }); + } + return res.status(500).json({ message: "Internal server error", err }); + } else { + return res.status(200).json(success); + } + } catch (err) { + return res.status(500).json({ message: "Internal server error", err }); + } + } default: return res.status(500).json({ message: "Internal server error" }); } @@ -464,22 +479,22 @@ async function adminRolesAdd(guild: string, role: string, level: number) { async function syncFromPolaris(guild: string) { const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}`); const data = await res.json(); - if(data.apiError && data.code === "invalidServer") { + if (data.apiError && data.code === "invalidServer") { return [new Error("Server not found in Polaris"), false]; } const users = data.leaderboard; - for(let i = 1; i < data.pageInfo.pageCount; i++) { + for (let i = 1; i < data.pageInfo.pageCount; i++) { const res = await fetch(`https://gdcolon.com/polaris/api/leaderboard/${guild}?page=${i + 1}`); const data = await res.json(); users.push(...data.leaderboard); } - if(users.length === 0) { + if (users.length === 0) { return [new Error("No users found"), false]; } try { - for(const user of users) { + for (const user of users) { const xpValue = user.xp; const level = Math.floor(Math.sqrt(xpValue / 100)); const nextLevel = level + 1; @@ -520,4 +535,66 @@ async function syncFromPolaris(guild: string) { } } + +async function syncFromMee6(guild: string) { + const res = await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${guild}?limit=1000&page=0`); + const data = await res.json(); + if (data.status_code === 404) { + return [new Error("Server not found in MEE6"), false]; + } + const users = data.players; + let pageNumber = 1; + while (true) { + const res = await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${guild}?limit=1000&page=${pageNumber}`); + const data = await res.json(); + users.push(...data.players); + if (data.players.length < 1000) break; + pageNumber += 1; + } + + if (users.length === 0) { + return [new Error("No users found"), false]; + } + + try { + for (const user of users) { + const xpValue = user.xp; + const level = Math.floor(Math.sqrt(xpValue / 100)); + const nextLevel = level + 1; + const nextLevelXp = Math.pow(nextLevel, 2) * 100; + const xpNeededForNextLevel = nextLevelXp - xpValue; + const currentLevelXp = Math.pow(level, 2) * 100; + const progressToNextLevel = + ((xpValue - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100; + + await new Promise((resolve, reject) => { + pool.query( + `INSERT INTO users (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + user.id, + guild, + xpValue, + `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.webp`, + user.username, + user.username, + level, + xpNeededForNextLevel, + progressToNextLevel.toFixed(2), + ], + (err) => { + if (err) { + console.error("Error syncing from MEE6:", err); + reject(err); + } else { + resolve(null); + } + }, + ); + }); + } + return [null, true] + } catch (err) { + return [err, false]; + } +} //#endregion diff --git a/bot/commands.ts b/bot/commands.ts index 60dcc6c..70464f4 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -3,7 +3,7 @@ import client from '.'; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type CommandInteraction, ChannelType, type APIApplicationCommandOption, GuildMember, AttachmentBuilder, ComponentType } from 'discord.js'; import { heapStats } from 'bun:jsc'; -import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, getUpdatesChannel, setUpdatesChannel, setXP, setLevel, syncFromPolaris } from './utils/requestAPI'; +import { getGuildLeaderboard, makeGETRequest, getRoles, removeRole, addRole, enableUpdates, disableUpdates, getCooldown, setCooldown, getUpdatesChannel, setUpdatesChannel, setXP, setLevel, syncFromBot } from './utils/requestAPI'; import convertToLevels from './utils/convertToLevels'; import quickEmbed from './utils/quickEmbed'; import { Font, RankCardBuilder } from 'canvacord'; @@ -734,6 +734,10 @@ const commands: Record = { name: 'Polaris', value: 'polaris', }, + { + name: 'MEE6', + value: 'mee6', + } ] }], name: 'sync', @@ -757,18 +761,19 @@ const commands: Record = { } const bot = interaction.options.get('bot')?.value; - - let apiSuccess; - switch (bot) { - case 'polaris': - apiSuccess = await syncFromPolaris(interaction.guildId as string); - if (!apiSuccess) { - await interaction.reply({ ephemeral: true, content: 'Error syncing data! This might mean that Polaris is not set up for this server, or the leaderboard for this server is not public.' }); - return; - } - await interaction.reply({ ephemeral: true, content: 'Data synced!' }); - return; + const formattedBotNames = { + 'polaris': 'Polaris', + 'mee6': 'MEE6' + }; + + await interaction.reply({ ephemeral: true, content: `Syncing data from ${formattedBotNames[bot as keyof typeof formattedBotNames]}...` }); + const apiSuccess = await syncFromBot(interaction.guildId as string, bot as string); + if (!apiSuccess) { + 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.` }); + return; } + await interaction.editReply({ content: 'Data synced!' }); + return; } } }; diff --git a/bot/utils/requestAPI.ts b/bot/utils/requestAPI.ts index 01a93f5..80e5a38 100644 --- a/bot/utils/requestAPI.ts +++ b/bot/utils/requestAPI.ts @@ -207,8 +207,8 @@ export async function setCooldown(guild: string, cooldown: number) { //#endregion //#region Sync -export async function syncFromPolaris(guild: string) { - const response = await fetch(`http://localhost:18103/admin/sync/${guild}/polaris`, { +export async function syncFromBot(guild: string, bot: string) { + const response = await fetch(`http://localhost:18103/admin/sync/${guild}/${bot}`, { "headers": { 'Content-Type': 'application/json', 'Authorization': process.env.AUTH as string, From 3fa059aa30137d1889ddf1825a07d78b2bafae49 Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Wed, 17 Jul 2024 19:24:43 +0800 Subject: [PATCH 5/6] fix: ignore eslint error --- api/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/index.ts b/api/index.ts index e13b28b..4842f73 100644 --- a/api/index.ts +++ b/api/index.ts @@ -544,6 +544,8 @@ async function syncFromMee6(guild: string) { } const users = data.players; let pageNumber = 1; + // this is needed because MEE6 doesn't give us the total amount of pages + // eslint-disable-next-line no-constant-condition while (true) { const res = await fetch(`https://mee6.xyz/api/plugins/levels/leaderboard/${guild}?limit=1000&page=${pageNumber}`); const data = await res.json(); From d5ae4e1eeff34fbfcaca8353283d106a40169ccf Mon Sep 17 00:00:00 2001 From: ToastedToast Date: Fri, 19 Jul 2024 19:41:36 +0800 Subject: [PATCH 6/6] feat: allow syncing from lurkr --- api/index.ts | 82 +++++++++++++++++++++++++++++++++++++++++++++++- bot/commands.ts | 9 ++++-- bun.lockb | Bin 146572 -> 144956 bytes 3 files changed, 88 insertions(+), 3 deletions(-) mode change 100644 => 100755 bun.lockb diff --git a/api/index.ts b/api/index.ts index 37a4118..82a92a9 100644 --- a/api/index.ts +++ b/api/index.ts @@ -340,7 +340,7 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { } } case "sync": { - if (target !== "polaris" && target !== "mee6") { + if (target !== "polaris" && target !== "mee6" && target !== "lurkr") { return res.status(400).json({ message: "Illegal request" }); } @@ -375,6 +375,21 @@ app.post("/admin/:action/:guild/:target", authMiddleware, async (req, res) => { return res.status(500).json({ message: "Internal server error", err }); } } + case "lurkr": { + try { + const [err, success] = await syncFromLurkr(guild); + if (err) { + if (err instanceof Error && err.message === "Server not found in Lurkr") { + return res.status(404).json({ message: "Server not found in Lurkr" }); + } + return res.status(500).json({ message: "Internal server error", err }); + } else { + return res.status(200).json(success); + } + } catch (err) { + return res.status(500).json({ message: "Internal server error", err }); + } + } default: return res.status(500).json({ message: "Internal server error" }); } @@ -610,4 +625,69 @@ async function syncFromMee6(guild: string) { return [err, false]; } } + +async function syncFromLurkr(guild: string) { + const res = await fetch(`https://api.lurkr.gg/v2/levels/${guild}?page=1`); + const data = await res.json(); + if (data.message === "Guild no found") { + return [new Error("Server not found in Lurkr"), false]; + } + const users = data.levels; + + if (users.length === 0) { + return [new Error("No users found"), false]; + } + + let pageNumber = 2; + // this is needed because Lurkr doesn't give us the total amount of pages + // eslint-disable-next-line no-constant-condition + while (true) { + const res = await fetch(`https://api.lurkr.gg/v2/levels/${guild}?page=${pageNumber}`); + const data = await res.json(); + users.push(...data.levels); + if (data.levels.length < 100) break; + pageNumber += 1; + } + + try { + for (const user of users) { + const xpValue = user.xp; + const level = Math.floor(Math.sqrt(user.xp / 100)); + const nextLevel = level + 1; + const nextLevelXp = Math.pow(nextLevel, 2) * 100; + const xpNeededForNextLevel = nextLevelXp - user.xp; + const currentLevelXp = Math.pow(level, 2) * 100; + const progressToNextLevel = + ((user.xp - currentLevelXp) / (nextLevelXp - currentLevelXp)) * 100; + + await new Promise((resolve, reject) => { + pool.query( + `INSERT INTO users (id, guild_id, xp, pfp, name, nickname, level, xp_needed_next_level, progress_next_level) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + user.userId, + guild, + xpValue, + `https://cdn.discordapp.com/avatars/${user.userId}/${user.user.avatar}.webp`, + user.user.username, + user.user.username, + level, + xpNeededForNextLevel, + progressToNextLevel.toFixed(2), + ], + (err) => { + if (err) { + console.error("Error syncing from Lurkr:", err); + reject(err); + } else { + resolve(null); + } + }, + ); + }); + } + return [null, true] + } catch (err) { + return [err, false]; + } +} //#endregion diff --git a/bot/commands.ts b/bot/commands.ts index 70464f4..afc717d 100644 --- a/bot/commands.ts +++ b/bot/commands.ts @@ -737,7 +737,11 @@ const commands: Record = { { name: 'MEE6', value: 'mee6', - } + }, + { + name: 'Lurkr', + value: 'lurkr', + }, ] }], name: 'sync', @@ -763,7 +767,8 @@ const commands: Record = { const bot = interaction.options.get('bot')?.value; const formattedBotNames = { 'polaris': 'Polaris', - 'mee6': 'MEE6' + 'mee6': 'MEE6', + 'lurkr': 'Lurkr' }; await interaction.reply({ ephemeral: true, content: `Syncing data from ${formattedBotNames[bot as keyof typeof formattedBotNames]}...` }); diff --git a/bun.lockb b/bun.lockb old mode 100644 new mode 100755 index 68e429669017c55ddde4989e1fb95df5c8a2a9a4..bdf942bab4ed6a4a62ccdde58436895f07620ad0 GIT binary patch delta 4536 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 delta 4667 zcmeHLd0bOh7Jm0ZAc2HE5_Y6OSxg`Zq9Ta66w9JWQE)-Fux}D5A}R>AU}-IwvDZ4S z+N!NqZN=zB6a{g`f|aSe($)oOZKa}E1qYpTLeher&cE}|ydPiAx!*bW+;f-r?!7er z!hN@&TPtyQQ_kZu;l$n5p+@A~QJMN%)OPFj3qq=lo-L>r_O3SmR{+c4^H$wF2ez-Q zerc^iSw|Sdh#024G?`(XLE5G;3?C%=8Gw|_a^92`Fw9m9hT%c^E=XgLY1w&6NhJ&; z&rX!5gKv(kAWi1OFj=x8Ka13%`tw0zy`+M~WSN{{IyAgx8p9YdOj&sbDCpoA!7zp( zQ!)y~7~Bb>BA);O)x0dJ2wa&Q2!hWbBT!I;(Yg>kezm&XD;rW{@XcW=#*`JnLloJDIepxT9zc7*)>vJxuH*DCsJsF8-WkC{0w{w+`Ta3>wyf2&4UZ*EL?yBt&q537rNujzVhd&ntiu z#?x1~wHr!7YFoXvKkput#t%gSPfu7I=m~8>Rx&7|C#zVJr4np~(i>3HA2BFTfSvIt zHZX%>=8j~Gczhs3N0K70Vn<53;QF84VZ0+@LCyY8{>pTDxGvphN4%AHimMIEIFQjD z!Z2Krzkq~4=05(sB$4XT*nb772j#~azu^+&4FHcX^}jTBl&B7BcI~t#s`W10wgsOUROp0WlYdcW4C9A+Gp&6&;^4N zNF9(aAmNXB6@UIhqQ9rcjuKTd{9wZpjr7v+QZ&LdT%w#s=8xld;?z6s^Za?gk zTt1Q8JgRN}BcJZL!jlg=ze#M3uS?l==qUOJCxT zj??bRR-U=n2f|;^Oz)_RJeeS=+q1u-GT3m*hc8Z3F`tOa9&+-&n|^3>#O$9Yp1Ihn zNV~)zCGN@heOB|2>bp@+>moMgMg*Hpe6aYqPQ)d8C+Pb?Ig#}LlCg&?^p*_xI|K|% zFKc*unN8CMB3q)*UaCoJFl#(t?N#jd35Vp5^9bKx-|Q0D7MnZg{-ds|RtJl$S{80z zSn+VmS0$5o&aUkKu)?9!sQvjo+6n2Y2Qx=Km>UEzn4gPN*93hUvTmVrc9QFah97js zF1u~ad8Nm#KW)~ru9e*jmPoP^yZYbT@soJ)?w$*YkIzpwZC@QT!*|V-^}ar%(}rc6 zH$2<-U+bCEX7R4{`<;s~6y<&<`L;IX^x7GpcWx|?;75Ovm84$rsZEdHI_p-@rr@AFf7V50xO%$n z*}<&n=K;s3RcvoR1`EBxZO>c>usX9|8IZy;n{?(8KQc z6Z-Zl5~+s=m1l%rTSctR@Q`upR-uY%YMaAf-~@cuu+j z_y({Zy7B~Mbv69Jv#<__!BzMM6mf`F0dTUG0}51oDhTCpazW2h&8#M0ld{>MX9DnJ zGzahuVxXQ5NCm_K_<*^97(g@tW6uLbfv*f?GGGC~7%(3Y2M7jC1SA2FTL_2;ECMVB zBmffe9EQPD0QfRv!yX!m2@FpR%h>=-FbCu;jXe)!6aeie089dxFeZwhF!aZcp-(;l zdE}OWTt?u~zZ8^WfE@5901np-F4rQBj%Bo$0FW;o#^bsz1K?UkKU|9_;b70y)(n3aYKub=h`A3M*22}0c zR(q$@23Lnu@D3tQ|a<%BC^$9e*+&i*Pk`9 znMU`52b+;b&teUybow01)TDt}YjY&tA2bq6eP7Ag-~0DR+Nk}h`}Y5(`y>0i?-=wK z8>0{Q5}}^d8-{Zz9LCVznzc7m?GVC+UOhj5qyaM(*+^_@f|3Yz#!93#bU(3WHG@PB z(tsnxieh6ne>E-InCf}Z)>Kzf=ckHnA#+)S;ID_)+7Dmj_t=8AoFEd~bAqh1PKOna z|H61{yk8qxYV_QsZOF4PK(+BCX$XT1o7`DT`jIIsp!ufk7*0`PMz&nF)07Rdq%*u( zbFIe~FYs_c-)rvdN-aCfo#h)A%EgKK8DgwS9WOwC%{puLyL@Mo*oSoYs4=K6Jo~g~8Ues!tWI#6q>+o8@xgt=#0tZu&Qf%Zk?k