From 6d2a7d5cb22e43a6295f513523f626d80c620a6b Mon Sep 17 00:00:00 2001 From: Miqhtiedev Date: Sat, 5 Jun 2021 00:16:33 -0400 Subject: [PATCH 1/2] database: add user documents --- src/commands/moderation/forcelink.ts | 65 ++++++++++++++++++++++ src/data/calm/channels.json | 5 ++ src/events/guildMemberUpdate.ts | 82 ++++++++++++++++++++++++++++ src/schemas/User.ts | 14 +++++ src/utils/api/MojangApi.ts | 20 +++++++ src/utils/database/Database.ts | 21 +++++++ 6 files changed, 207 insertions(+) create mode 100644 src/commands/moderation/forcelink.ts create mode 100644 src/events/guildMemberUpdate.ts create mode 100644 src/schemas/User.ts create mode 100644 src/utils/api/MojangApi.ts diff --git a/src/commands/moderation/forcelink.ts b/src/commands/moderation/forcelink.ts new file mode 100644 index 0000000..96dafe1 --- /dev/null +++ b/src/commands/moderation/forcelink.ts @@ -0,0 +1,65 @@ +import { Message } from "discord.js"; +import Client from "../../structures/Client"; +import Roles from "../../data/calm/roles.json"; +import { ICommand, PermissionsEnum, RunCallback } from "../../structures/Interfaces"; +import MojangApi from "../../utils/api/MojangApi"; +import Database from "../../utils/database/Database"; +import logger from "../../utils/logger/Logger"; + +function ForceLinkCommand(): ICommand { + const run: RunCallback = (client: Client, message: Message, args: string[]) => { + const calmiesRole = message.guild!!.id === "501501905508237312" ? message.guild?.roles.cache.get(Roles.GENERAL.CALMIES.id) : message.guild?.roles.cache.find((r) => r.name === Roles.GENERAL.CALMIES.name); + if (!calmiesRole) { + message.channel.send(`Could not find the ${Roles.GENERAL.CALMIES.name} role in this server!`); + return; + } + + message + .guild!!.members.fetch(args[0]!!) + .then((member) => { + Database.getUser(member.id) + .then((user) => { + message.channel.send(`Discord user is already linked to ${user.id}`); + }) + .catch(() => { + MojangApi.getUUID(args[1]!!) + .then((uuid) => { + if (!uuid) { + message.channel.send("Invalid minecraft name."); + return; + } + + const inCalm = member.roles.cache.has(calmiesRole.id); + Database.createUser(member.id, uuid, inCalm) + .then((user) => { + message.channel.send(`Created new database entry!\n\`\`\`${user}\`\`\``).catch(logger.error); + }) + .catch((err) => { + message.channel.send("An error occurred while running that command! Please contact a developer."); + logger.error(err); + }); + }) + .catch((err) => { + message.channel.send("An error occurred while running that command! Please contact a developer."); + logger.error(err); + }); + }); + }) + .catch(() => { + message.channel.send("Invalid discord id! (user not in server)"); + }); + }; + + return { + run: run, + settings: { + description: "Force link a discord and minecraft account", + usage: "forcelink ", + permissions: PermissionsEnum.STAFF, + guildOnly: true, + minimumArgs: 2, + }, + }; +} + +export default ForceLinkCommand(); diff --git a/src/data/calm/channels.json b/src/data/calm/channels.json index e7ceff1..e6020cf 100644 --- a/src/data/calm/channels.json +++ b/src/data/calm/channels.json @@ -256,6 +256,11 @@ } }, "STAFF": { + "STAFF_CHAT": { + "name": "staff-chat", + "id": "547923467522015263", + "public": false + }, "CHALLENGES": { "name": "challenge-requests", "id": "787202572032278539", diff --git a/src/events/guildMemberUpdate.ts b/src/events/guildMemberUpdate.ts new file mode 100644 index 0000000..6e715b9 --- /dev/null +++ b/src/events/guildMemberUpdate.ts @@ -0,0 +1,82 @@ +import { GuildMember } from "discord.js"; +import Roles from "../data/calm/roles.json"; +import Channels from "../data/calm/channels.json"; +import Client from "../structures/Client"; +import Database from "../utils/database/Database"; +import MojangApi from "../utils/api/MojangApi"; +import logger from "../utils/logger/Logger"; + +export default function guildMemberUpdate(client: Client, oldMember: GuildMember, newMember: GuildMember) { + const isCalmDiscord = newMember.guild.id === "501501905508237312"; + const calmMemberRole = isCalmDiscord ? newMember.guild.roles.cache.get(Roles.GENERAL.CALMIES.id) : newMember.guild.roles.cache.find((r) => r.name === Roles.GENERAL.CALMIES.name); + const staffChat = isCalmDiscord ? newMember.guild.channels.cache.get(Channels.STAFF.STAFF_CHAT.id) : newMember.guild.channels.cache.find((c) => c.name === Channels.STAFF.STAFF_CHAT.name); + if (!calmMemberRole) return; + + const newRoles = newMember.roles.cache.filter((role) => !oldMember.roles.cache.has(role.id)); + const removedRoles = oldMember.roles.cache.filter((role) => !newMember.roles.cache.has(role.id)); + + // Member was given calmie role + if (newRoles.size > 0 && newRoles.array()[0]?.id === calmMemberRole.id) { + Database.getUser(newMember.id) + .then((user) => { + // User was in the guild before + user.inCalm = true; + user.save(); + }) + .catch(() => { + // User was not in guild before + let name = newMember.nickname; + if (!name) { + sendErrorToStaffChat(); + return; + } + + // Get MC name from nickanme + name = name.slice(name.indexOf("]") + 2); + const j = name.indexOf(" "); + if (j !== -1) name = name.slice(0, j); + + if (!name) { + sendErrorToStaffChat(); + return; + } + + // If we were able to get name get uuid of person so we can link their minecraft and discord account + MojangApi.getUUID(name) + .then((uuid) => { + // Name is incorrect + if (!uuid) { + sendErrorToStaffChat(); + return; + } + + // UUID is correct so we can create the document + Database.createUser(newMember.id, uuid, true).catch(logger.error); + }) + .catch((err) => { + logger.error(err); + sendErrorToStaffChat(); + }); + }); + } + + // Member was removed from calmie role + else if (removedRoles.size > 0 && removedRoles.array()[0]?.id === calmMemberRole.id) { + Database.getUser(newMember.id) + .then((user) => { + user.inCalm = false; + user.save(); + }) + .catch(() => { + logger.warn(`User: ${newMember.id} was removed from calmies role but never had an entry in the database`); + }); + } + + function sendErrorToStaffChat() { + if (staffChat && staffChat.isText()) { + staffChat + .send(`ERROR! ${newMember}'s nickname is not correct and we could not create a database entry for them because we could not fetch their UUID.\n**Please do ${client.prefix}forcelink ${newMember.id} ** **__ASAP__**`) + .catch(logger.error); + } + } +} diff --git a/src/schemas/User.ts b/src/schemas/User.ts new file mode 100644 index 0000000..3183bf7 --- /dev/null +++ b/src/schemas/User.ts @@ -0,0 +1,14 @@ +import { Schema, model, Document } from "mongoose"; +export interface IUser extends Document { + discordID: string; + uuid: string; + inCalm: boolean; +} + +const UserSchema = new Schema({ + discordID: { type: String, requried: true, index: true, unique: true }, + uuid: { type: String, required: true, unique: true }, + inCalm: { type: Boolean, required: true }, +}); + +export default model("User", UserSchema); diff --git a/src/utils/api/MojangApi.ts b/src/utils/api/MojangApi.ts new file mode 100644 index 0000000..f18b58f --- /dev/null +++ b/src/utils/api/MojangApi.ts @@ -0,0 +1,20 @@ +import axios from "axios"; +import logger from "../logger/Logger"; + +const MOJANG_API_URL = "https://api.mojang.com"; + +export default { + getUUID(name: string): Promise { + return new Promise((resolve, reject) => { + axios + .get(`${MOJANG_API_URL}/users/profiles/minecraft/${name}`) + .then((response) => { + resolve(response.data?.id); + }) + .catch((err) => { + logger.error(err); + reject(err); + }); + }); + }, +}; diff --git a/src/utils/database/Database.ts b/src/utils/database/Database.ts index f1efc6b..2e0b538 100644 --- a/src/utils/database/Database.ts +++ b/src/utils/database/Database.ts @@ -4,6 +4,7 @@ import GuildSettings from "../../schemas/GuildSettings"; import ChallengeParticipant from "../../schemas/ChallengeParticipant"; import Client from "../../structures/Client"; import logger from "../logger/Logger"; +import User, { IUser } from "../../schemas/User"; export default { initialize: async (url: string, client: Client) => { @@ -37,4 +38,24 @@ export default { } return participant; }, + + getUser: (discordid: string): Promise => { + return new Promise(async (resolve, reject) => { + const user = await User.findOne({ discordID: discordid }); + if (user) resolve(user); + reject(undefined); + }); + }, + + createUser(discordid: string, uuid: string, inCalm: boolean): Promise { + return new Promise((resolve, reject) => { + const user = new User({ discordID: discordid, uuid: uuid, inCalm: inCalm }); + user + .save() + .then((savedUser) => { + resolve(savedUser); + }) + .catch((err) => reject(err)); + }); + }, }; From fcbb06cecff9b900d990c95370f44b2436d7a391 Mon Sep 17 00:00:00 2001 From: Miqhtiedev Date: Sat, 5 Jun 2021 10:12:05 -0400 Subject: [PATCH 2/2] use async/await for mojang api --- src/commands/moderation/forcelink.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/commands/moderation/forcelink.ts b/src/commands/moderation/forcelink.ts index 96dafe1..537ea5c 100644 --- a/src/commands/moderation/forcelink.ts +++ b/src/commands/moderation/forcelink.ts @@ -21,23 +21,18 @@ function ForceLinkCommand(): ICommand { .then((user) => { message.channel.send(`Discord user is already linked to ${user.id}`); }) - .catch(() => { - MojangApi.getUUID(args[1]!!) - .then((uuid) => { - if (!uuid) { - message.channel.send("Invalid minecraft name."); - return; - } + .catch(async () => { + const uuid = await MojangApi.getUUID(args[1]!!); - const inCalm = member.roles.cache.has(calmiesRole.id); - Database.createUser(member.id, uuid, inCalm) - .then((user) => { - message.channel.send(`Created new database entry!\n\`\`\`${user}\`\`\``).catch(logger.error); - }) - .catch((err) => { - message.channel.send("An error occurred while running that command! Please contact a developer."); - logger.error(err); - }); + if (!uuid) { + message.channel.send("Invalid minecraft name."); + return; + } + + const inCalm = member.roles.cache.has(calmiesRole.id); + Database.createUser(member.id, uuid, inCalm) + .then((user) => { + message.channel.send(`Created new database entry!\n\`\`\`${user}\`\`\``).catch(logger.error); }) .catch((err) => { message.channel.send("An error occurred while running that command! Please contact a developer.");