From 702c7198330409e4a55a5bfac8ae590b727c741f Mon Sep 17 00:00:00 2001 From: Neko-Life Date: Wed, 14 Jul 2021 13:05:48 +0700 Subject: [PATCH 1/3] First step --- cmds/moderation/mute.js | 36 ++++------ resources/structures.js | 152 +++++++++++++++++++++------------------- 2 files changed, 92 insertions(+), 96 deletions(-) diff --git a/cmds/moderation/mute.js b/cmds/moderation/mute.js index e5eb08d..97bc2fd 100644 --- a/cmds/moderation/mute.js +++ b/cmds/moderation/mute.js @@ -57,8 +57,8 @@ module.exports = class mute extends commando.Command { */ async run(msg, arg) { if (!msg.guild.dbLoaded) msg.guild.dbLoad(); - const muteSettingsDoc = msg.guild.moderation.mute, - defaultDurationDoc = muteSettingsDoc.defaultDuration, + const MOD = msg.guild.moderation, + MUTE = MOD.mute || { defaultDuration: {} }, args = parseDoubleDash(arg), mentions = parseComa(args.shift()), durationRegExp = /\d+(?![^ymwdhs])[ymwdhs]?o?/gi, @@ -71,14 +71,7 @@ module.exports = class mute extends commando.Command { minute: invokedAt.getMinutes(), second: invokedAt.getSeconds() }; - let theSettingUp = { - role: undefined, - defaultDuration: { - date: undefined, - string: undefined - } - }, - durationHasSet = false, + let durationHasSet = false, settingUp = false, settingRole = false, settingRoleHasSet = false, @@ -141,7 +134,7 @@ module.exports = class mute extends commando.Command { const key = cleanMentionID(argument); let role = getRole(msg.guild, key)?.id; if (role || /^none$/i.test(key)) { - theSettingUp.role = role; + MUTE.role = role; } else { resultMsg += `No role found for: **${argument}**\n`; } @@ -149,15 +142,15 @@ module.exports = class mute extends commando.Command { } } } - const roleConfCheck = msg.guild.roles.cache.get(muteSettingsDoc?.role); + const roleConfCheck = msg.guild.roles.cache.get(MUTE?.role); if (!roleConfCheck && !settingUp) { resultMsg += `No mute role configured! Run \`${msg.guild.commandPrefix}${this.name} --settings <--role --> [--duration --\` to set it up.`; } let untilDate = new Date(String(duration.year), String(duration.month), String(duration.date), String(duration.hour), String(duration.minute), String(duration.second)); if (untilDate.toString() === "Invalid Date") untilDate = "Indefinite"; - if (untilDate?.toUTCString() === invokedAt.toUTCString() && !settingDuration) { - if (defaultDurationDoc?.date?.valueOf() > 0) { - untilDate = new Date(invokedAt.valueOf() + defaultDurationDoc.date.valueOf() - 1000); + if (untilDate?.toUTCString?.() === invokedAt.toUTCString() && !settingDuration) { + if (MUTE.defaultDuration.date?.valueOf() > 0) { + untilDate = new Date(invokedAt.valueOf() + MUTE.defaultDuration.date.valueOf() - 1000); } else { untilDate = "Indefinite"; } @@ -196,19 +189,14 @@ module.exports = class mute extends commando.Command { } if (settingDuration && !settingDurationHasSet && timeForMessage.length > 0) { settingDurationHasSet = true; - theSettingUp.defaultDuration.date = elapsedTime, - theSettingUp.defaultDuration.string = timeForMessage.join(" "); + MUTE.defaultDuration.date = elapsedTime, + MUTE.defaultDuration.string = timeForMessage.join(" "); } } if (settingUp || !roleConfCheck && !settingUp) { - if (settingRoleHasSet) { - await col.updateOne({ document: msg.guild.id }, { $set: { "moderation.settings.mute.role": theSettingUp.role } }, { upsert: true }).catch(e => { return trySend(this.client, msg, "```js\n" + e.stack + "```") }); + if (settingDurationHasSet || settingRoleHasSet) { + MOD.mute = MUTE; } - if (durationHasSet) { - await col.updateOne({ document: msg.guild.id }, { $set: { "moderation.settings.mute.defaultDuration": theSettingUp.defaultDuration } }, { upsert: true }).catch(e => { return trySend(this.client, msg, "```js\n" + e.stack + "```") }); - } - const defaultDurationDoc = muteSettingsDoc?.defaultDuration, - roleDoc = muteSettingsDoc?.role; let settings = defaultImageEmbed(msg); settings .setTitle("Mute Configuration") diff --git a/resources/structures.js b/resources/structures.js index e6cef10..f28e35d 100644 --- a/resources/structures.js +++ b/resources/structures.js @@ -8,22 +8,25 @@ Structures.extend("Guild", g => { return class Guild extends g { constructor(client, data) { super(client, data); - this.dbLoaded = false; } async dbLoad() { - return database.collection("Guild").findOne({ document: this.id }).then((r, e) => { + return database.collection("Guild").findOne({ document: this.id }, (e, r) => { if (e) return errLog(e, null, this.client); - this.infractions = r?.moderation?.infractions || []; - this.moderation = r?.moderation?.settings || {}; - this.defaultEmbed = r?.settings?.defaultEmbed || {}; - this.quoteOTD = r?.settings?.quoteOTD || {}; - this.eventChannels = r?.settings?.eventChannels || {}; - - return this.dbLoaded = true; + return this.DB = r || {}; }); } + async setDb(Db, empty = false) { + if (typeof Db !== "object") throw new TypeError("Expected 'object'; Got '" + typeof Db + "'"); + if (Db === {} && !empty) throw new Error("Empty!"); + return database.collection("Guild").updateOne({ document: this.id }, { $set: Db, $setOnInsert: { document: this.id } }, + { upsert: true }, (e) => { + if (e) return errLog(e, null, this.client); + return this.DB = Db; + }); + } + /** * Get user infractions * @param {String} get - User ID @@ -31,11 +34,10 @@ Structures.extend("Guild", g => { */ async getInfractions(get) { try { - const r = await database.collection("Guild").findOne({ document: this.id }); - this.infractions = r?.moderation?.infractions; + if (!this.DB) await this.dbLoad(); let found = []; - if (this.infractions.length > 0) { - for (const inf of this.infractions) { + if (this.DB.moderation?.infractions?.length > 0) { + for (const inf of this.DB.moderation.infractions) { for (const user of inf.by) { if (user.id === get) { found.push(inf); @@ -50,46 +52,35 @@ Structures.extend("Guild", g => { async addInfraction(add) { try { - const r = await database.collection("Guild").findOne({ document: this.id }); - this.infractions = r?.moderation?.infractions; - return database.collection("Guild").updateOne({ document: this.id }, { $push: { "moderation.infractions": add } }, (e) => { - if (e) return errLog(e, null, this.client); - this.infractions.push(add); - return true; - }); + if (!this.DB) await this.dbLoad(); + if (!this.DB.moderation?.infractions) this.DB.moderation.infractions = []; + this.DB.moderation.infractions.push(add); + return this.setDb(this.DB); } catch (e) { } } async setQuoteOTD(set) { - return database.collection("Guild").updateOne({ document: this.id }, { $set: { "settings.quoteOTD": set }, $setOnInsert: { document: this.id } }, { upsert: true }, (e) => { - if (e) return errLog(e, null, this.client); - this.quoteOTD = set; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.settings.quoteOTD = set; + return this.setDb(this.DB); } async setEventChannels(set) { - return database.collection("Guild").updateOne({ document: this.id }, { $set: { "settings.eventChannels": set }, $setOnInsert: { document: this.id } }, { upsert: true }, (e) => { - if (e) return errLog(e, null, this.client); - this.eventChannels = set; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.settings.eventChannels = set; + return this.setDb(this.DB); } async setDefaultEmbed(set) { - return database.collection("Guild").updateOne({ document: this.id }, { $set: { "settings.defaultEmbed": set }, $setOnInsert: { document: this.id } }, { upsert: true }, (e) => { - if (e) return errLog(e, null, this.client); - this.defaultEmbed = set; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.settings.defaultEmbed = set; + return this.setDb(this.DB); } async setModerationSettings(set) { - return database.collection("Guild").updateOne({ document: this.id }, { $set: { "moderation.settings": set }, $setOnInsert: { document: this.id } }, { upsert: true }, (e) => { - if (e) return errLog(e, null, this.client); - this.moderation = set; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.moderation.settings = set; + return this.setDb(this.DB); } } }); @@ -98,53 +89,49 @@ Structures.extend("User", u => { return class User extends u { constructor(client, data) { super(client, data); - this.dbLoaded = false; this.cutie = true; - this.F = "F"; - } - - async setF(string) { - return database.collection("User").updateOne({ document: this.id }, { $set: { F: string }, $setOnInsert: { document: this.id } }, { upsert: true }, (e, r) => { - if (e) return errLog(e, null, this.client); - this.F = string; - return true; - }); } async dbLoad() { - return database.collection("User").findOne({ document: this.id }).then((r, e) => { + return database.collection("User").findOne({ document: this.id }, (e, r) => { if (e) return errLog(e, null, this.client); - this.defaultEmbed = r?.settings?.defaultEmbed || {}; - this.cachedAvatarURL = this.displayAvatarURL({ format: "png", size: 4096, dynamic: true }); - this.interactions = r?.interactions || {}; - this.description = r?.description; - this.F = r?.F; - return this.dbLoaded = true; + if (!r.F) r.F = "F"; + return this.DB = r || {}; }); } + async setDb(Db, empty = false) { + if (typeof Db !== "object") throw new TypeError("Expected 'object'; Got '" + typeof Db + "'"); + if (Db === {} && !empty) throw new Error("Empty!"); + return database.collection("User").updateOne({ document: this.id }, { $set: Db, $setOnInsert: { document: this.id } }, + { upsert: true }, (e) => { + if (e) return errLog(e, null, this.client); + return this.DB = Db; + }); + } + + async setF(string) { + if (!this.DB) await this.dbLoad(); + this.DB.F = string; + return this.setDb(this.DB); + } + async setInteractions(count) { - return database.collection("User").updateOne({ document: this.id }, { $set: { interactions: count }, $setOnInsert: { document: this.id } }, { upsert: true }, (e, r) => { - if (e) return errLog(e, null, this.client); - this.interactions = count; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.interactions = count; + return this.setDb(this.DB); } async setDescription(set) { - return database.collection("User").updateOne({ document: this.id }, { $set: { description: set }, $setOnInsert: { document: this.id } }, { upsert: true }, (e, r) => { - if (e) return errLog(e, null, this.client); - this.description = set; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.description = set; + return this.setDb(this.DB); } async setDefaultEmbed(set) { - return database.collection("User").updateOne({ document: this.id }, { $set: { "settings.defaultEmbed": set }, $setOnInsert: { document: this.id } }, { upsert: true }, (e) => { - if (e) return errLog(e, null, this.client); - this.defaultEmbed = set; - return true; - }); + if (!this.DB) await this.dbLoad(); + this.DB.defaultEmbed = set; + return this.setDb(this.DB); } } }); @@ -200,8 +187,29 @@ Structures.extend("GuildMember", e => { super(client, data, guild); } + async dbLoad() { + return database.collection("GuildMember").findOne({ document: this.id }, (e, r) => { + if (e) return errLog(e, null, this.client); + return this.DB = r || {}; + }); + } + + async setDb(Db, empty = false) { + if (typeof Db !== "object") throw new TypeError("Expected 'object'; Got '" + typeof Db + "'"); + if (Db === {} && !empty) throw new Error("Empty!"); + return database.collection("GuildMember").updateOne({ document: this.id }, { $set: Db, $setOnInsert: { document: this.id } }, + { upsert: true }, (e) => { + if (e) return errLog(e, null, this.client); + return this.DB = Db; + }); + } + async infractions() { return this.guild.getInfractions(this.id); } + + async mute() { + + } } }); \ No newline at end of file From 7a0cad4bfeb244391215213cbc117cc3b5c1cf20 Mon Sep 17 00:00:00 2001 From: Neko-Life Date: Wed, 14 Jul 2021 14:14:38 +0700 Subject: [PATCH 2/3] Mute: Methods written --- resources/structures.js | 60 ++++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 10 deletions(-) diff --git a/resources/structures.js b/resources/structures.js index f28e35d..2f7a97e 100644 --- a/resources/structures.js +++ b/resources/structures.js @@ -4,8 +4,8 @@ const { Structures } = require("discord.js"), { database } = require("../database/mongo"), { errLog } = require("./functions"); -Structures.extend("Guild", g => { - return class Guild extends g { +Structures.extend("Guild", u => { + return class Guild extends u { constructor(client, data) { super(client, data); } @@ -30,7 +30,7 @@ Structures.extend("Guild", g => { /** * Get user infractions * @param {String} get - User ID - * @returns {Promise} Infractions object + * @returns {Promise} Array of infractions objects */ async getInfractions(get) { try { @@ -136,8 +136,8 @@ Structures.extend("User", u => { } }); -Structures.extend("TextChannel", e => { - return class TextChannel extends e { +Structures.extend("TextChannel", u => { + return class TextChannel extends u { constructor(guild, data) { super(guild, data); this.lastMessagesID = []; @@ -152,8 +152,8 @@ Structures.extend("TextChannel", e => { } }); -Structures.extend("DMChannel", e => { - return class DMChannel extends e { +Structures.extend("DMChannel", u => { + return class DMChannel extends u { constructor(client, data) { super(client, data); this.lastMessagesID = []; @@ -181,8 +181,8 @@ Structures.extend("Message", e => { }; }); -Structures.extend("GuildMember", e => { - return class GuildMember extends e { +Structures.extend("GuildMember", u => { + return class GuildMember extends u { constructor(client, data, guild) { super(client, data, guild); } @@ -208,8 +208,48 @@ Structures.extend("GuildMember", e => { return this.guild.getInfractions(this.id); } - async mute() { + /** + * @param {Date | number} until + * @returns + */ + async mute(until) { + if (!this.DB) await this.dbLoad(); + if (!this.guild.DB) await this.guild.dbLoad(); + this.DB.takenRoles = this.roles.cache.map(r => r.id); + this.DB.muteRole = this.guild.DB.moderation.settings.mute.role; + if ((typeof until) === "number") until = new Date(until); + try { + const RM = await this.roles.remove(this.DB.takenRoles); + await RM.roles.add(this.DB.muteRole); + this.DB.muted = { + state: true, + until: until + }; + return this.setDb(this.DB); + } catch (e) { + if (this.DB.takenRoles?.length > 0) this.roles.add(this.DB.takenRoles).catch(() => { }); + this.DB.takenRoles = []; + this.DB.muteRole = undefined; + this.DB.muted = { state: false }; + const R = await this.setDb(this.DB); + if (e) throw e; + return R; + } + } + async unmute() { + if (!this.DB) await this.dbLoad(); + try { + const RM = await this.roles.add(this.DB.takenRoles); + await RM.roles.remove(this.DB.muteRole); + this.DB.takenRoles = []; + this.DB.muteRole = undefined; + this.DB.muted = { state: false }; + return this.setDb(this.DB); + } catch (e) { + if (e) throw e; + return; + } } } }); \ No newline at end of file From d1de71fe4116153ad6069fd784f13cd3759884a4 Mon Sep 17 00:00:00 2001 From: Neko-Life Date: Wed, 21 Jul 2021 11:28:21 +0700 Subject: [PATCH 3/3] DB update + Docker support --- Dockerfile | 13 + Main.js | 51 +-- cmds/fun/f.js | 14 +- cmds/fun/say.js | 2 +- cmds/fun/send.js | 88 ++--- cmds/image/cuddle.js | 2 +- cmds/image/feed.js | 2 +- cmds/image/hug.js | 2 +- cmds/image/kiss.js | 2 +- cmds/image/pat.js | 2 +- cmds/image/poke.js | 2 +- cmds/image/rsc/interactEmbed.js | 131 ++++++++ cmds/image/slap.js | 2 +- cmds/image/tickle.js | 2 +- cmds/moderation/eventlog.js | 8 +- cmds/moderation/mute.js | 337 +++++++------------- cmds/moderation/src/configureMuteRole.js | 50 +++ cmds/moderation/src/duration.js | 91 ++++++ cmds/moderation/src/muteSetting.js | 44 +++ cmds/moderation/src/targetUser.js | 45 +++ cmds/moderation/unmute.js | 67 ++++ cmds/owner/eval.js | 2 +- cmds/utility/embmaker.js | 4 +- cmds/utility/perms.js | 1 + config_copy.json | 30 ++ database/mongo.js | 2 +- package.json | 10 +- resources/debug.js | 7 +- resources/eventsLogger/guildBanAdd.js | 6 +- resources/eventsLogger/guildMemberAdd.js | 4 +- resources/eventsLogger/guildMemberRemove.js | 4 +- resources/eventsLogger/guildMemberUpdate.js | 24 +- resources/eventsLogger/messageDelete.js | 8 +- resources/eventsLogger/messageUpdate.js | 8 +- resources/functions.js | 120 ++++--- resources/getColor.js | 100 +++++- resources/structures.js | 256 ++++++++++++--- 37 files changed, 1083 insertions(+), 460 deletions(-) create mode 100644 Dockerfile create mode 100644 cmds/image/rsc/interactEmbed.js create mode 100644 cmds/moderation/src/configureMuteRole.js create mode 100644 cmds/moderation/src/muteSetting.js create mode 100644 cmds/moderation/src/targetUser.js create mode 100644 cmds/moderation/unmute.js create mode 100644 config_copy.json diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b0631fa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 + +FROM node:14.15.3 +ENV NODE_ENV=production +WORKDIR /app + +COPY ["package.json", "./"] +RUN npm install --production +COPY . . +#RUN export DOCKER_HOST_IP=$(route -n | awk '/UG[ \t]/{print $2}') +ENV MONGO_HOST=mongodb://host.docker.internal:27017 + +CMD [ "node", "Main.js" ] \ No newline at end of file diff --git a/Main.js b/Main.js index ae63f6e..27f31d1 100644 --- a/Main.js +++ b/Main.js @@ -56,7 +56,7 @@ client.on('ready', async () => { client.on("message", async msg => { if (!client.matchTimestamp) client.matchTimestamp = 0;//getUTCComparison(msg.createdTimestamp); - if (!msg.author.dbLoaded && !msg.author.bot) await msg.author.dbLoad(); + if (!msg.author.DB) await msg.author.dbLoad(); lgr.message.letsChat(msg); if (msg.mentions.has(client.user) && !msg.isCommand && msg.channel.id != configFile.chatChannel) { @@ -72,7 +72,7 @@ client.on("message", async msg => { if (!msg.guild) { //console.log(`(${msg.channel.recipient.id}) ${msg.channel.recipient.tag}: (${msg.author.id}) ${msg.author.tag}: ${msg.content}`); } else { - if (!msg.guild.dbLoaded) await msg.guild.dbLoad(); + if (!msg.guild.DB) await msg.guild.dbLoad(); lgr.message.giveNickHeart(msg); } @@ -81,7 +81,7 @@ client.on("message", async msg => { client.on("guildMemberRemove", async (member) => { //console.log(`User ${memberLeave.displayName} (${memberLeave.user.tag}) (${memberLeave.id}) left ${memberLeave.guild.name} (${memberLeave.guild.id}). Now it has ${memberLeave.guild.memberCount} total members count.`); - if (!member.guild.dbLoaded) await member.guild.dbLoad(); + if (!member.guild.DB) await member.guild.dbLoad(); lgr.guildMemberRemove(member); }); @@ -97,8 +97,8 @@ client.on("guildDelete", leaveShaGuild => { client.on("guildMemberAdd", async (member) => { //console.log(`New member ${newMember.displayName} (${newMember.user.tag}) (${newMember.id}) joined ${newMember.guild.name} (${newMember.guild.id})! Now it has ${newMember.guild.memberCount} total members count.`); - if (!member.guild.dbLoaded) await member.guild.dbLoad(); - if (!member.user.dbLoaded && !member.user.bot) await member.user.dbLoad(); + if (!member.guild.DB) await member.guild.dbLoad(); + if (!member.user.DB && !member.user.bot) await member.user.dbLoad(); lgr.guildMemberAdd(member); }); @@ -107,49 +107,52 @@ client.on("guildBanAdd", async (GUILD, USER) => { }); client.on("messageDelete", async (msg) => { - if (msg.author && !msg.author.dbLoaded && !msg.author.bot) await msg.author.dbLoad(); + if (msg.author && !msg.author.DB) await msg.author.dbLoad(); if (msg.guild) { - if (!msg.guild.dbLoaded) await msg.guild.dbLoad(); + if (!msg.guild.DB) await msg.guild.dbLoad(); lgr.messageDelete(msg); } }); client.on("messageUpdate", async (msgold, msgnew) => { - if (msgnew.author && !msgnew.author.dbLoaded && !msgnew.author.bot) await msgnew.author.dbLoad(); + if (!msgnew.author?.DB) await msgnew.author?.dbLoad(); if (msgnew.guild) { - if (!msgnew.guild.dbLoaded) await msgnew.guild.dbLoad(); + if (!msgnew.guild.DB) await msgnew.guild.dbLoad(); lgr.messageUpdate(msgold, msgnew); } }); client.on("guildMemberUpdate", async (memberold, membernew) => { //console.log(memberold.toJSON(), "\n\n", membernew.toJSON()); - if (!membernew.user.dbLoaded && !membernew.user.bot) await membernew.user.dbLoad(); - if (!membernew.guild.dbLoaded) await membernew.guild.dbLoad(); + if (!membernew.user.DB) await membernew.user.dbLoad(); + if (!membernew.guild.DB) await membernew.guild.dbLoad(); lgr.guildMemberUpdate(memberold, membernew); }); -client.on("shardReady", (shard) => { +client.on("shardReady", async (shard) => { const log = client.channels.cache.get(configFile.shardChannel); - const emb = defaultEventLogEmbed(client.guilds.cache.get(configFile.home)); + if (!log.guild.DB) await log.guild.dbLoad(); + const emb = defaultEventLogEmbed(log.guild); emb.setTitle("Shard #" + shard) .setDescription("**CONNECTED**") .setColor(getColor("blue")); trySend(client, log, emb); }); -client.on("shardReconnecting", (shard) => { +client.on("shardReconnecting", async (shard) => { const log = client.channels.cache.get(configFile.shardChannel); - const emb = defaultEventLogEmbed(client.guilds.cache.get(configFile.home)); + if (!log.guild.DB) await log.guild.dbLoad(); + const emb = defaultEventLogEmbed(log.guild); emb.setTitle("Shard #" + shard) .setDescription("**RECONNECTING**") .setColor(getColor("cyan")); trySend(client, log, emb); }); -client.on("shardDisconnect", (e, shard) => { +client.on("shardDisconnect", async (e, shard) => { const log = client.channels.cache.get(configFile.shardChannel); - const emb = defaultEventLogEmbed(client.guilds.cache.get(configFile.home)); + if (!log.guild.DB) await log.guild.dbLoad(); + const emb = defaultEventLogEmbed(log.guild); emb.setTitle("Shard #" + shard) .setDescription("**DISCONNECTED\n\nTARGET:**```js\n" + JSON.stringify(e.target, (k, v) => v ?? undefined, 2) + "```") .addField("CODE", e.code, true) @@ -159,18 +162,20 @@ client.on("shardDisconnect", (e, shard) => { trySend(client, log, emb); }); -client.on("shardResume", (shard) => { +client.on("shardResume", async (shard) => { const log = client.channels.cache.get(configFile.shardChannel); - const emb = defaultEventLogEmbed(client.guilds.cache.get(configFile.home)); + if (!log.guild.DB) await log.guild.dbLoad(); + const emb = defaultEventLogEmbed(log.guild); emb.setTitle("Shard #" + shard) .setDescription("**RESUMED**") .setColor(getColor("green")); trySend(client, log, emb); }); -client.on("shardError", (e, shard) => { +client.on("shardError", async (e, shard) => { const log = client.channels.cache.get(configFile.shardChannel); - const emb = defaultEventLogEmbed(client.guilds.cache.get(configFile.home)); + if (!log.guild.DB) await log.guild.dbLoad(); + const emb = defaultEventLogEmbed(log.guild); emb.setTitle("Shard #" + shard) .setDescription("**ERROR**") .setColor(getColor("red")); @@ -179,8 +184,8 @@ client.on("shardError", (e, shard) => { }); client.on("commandRun", async (c, u, msg) => { - if (!msg.author.dbLoaded) await msg.author.dbLoad(); - if (msg.guild && !msg.guild.dbLoaded) await msg.guild.dbLoad(); + if (!msg.author.DB) await msg.author.dbLoad(); + if (msg.guild && !msg.guild.DB) await msg.guild.dbLoad(); }); client.on("warn", a => console.log("warn", typeof a, a)); diff --git a/cmds/fun/f.js b/cmds/fun/f.js index 14b77eb..a62b182 100644 --- a/cmds/fun/f.js +++ b/cmds/fun/f.js @@ -13,16 +13,16 @@ module.exports = class f extends commando.Command { }); } async run(msg, arg) { - if (!msg.author.dbLoaded) await msg.author.dbLoad(); + if (!msg.author.DB) await msg.author.dbLoad(); if (arg) { - msg.author.F = arg; + msg.author.DB.F = arg; await msg.author.setF(arg) }; return trySend(msg.client, msg, - msg.author.F + msg.author.F + msg.author.F + "\n" + - msg.author.F + "\n" + - msg.author.F + msg.author.F + msg.author.F + "\n" + - msg.author.F + "\n" + - msg.author.F); + msg.author.DB.F + msg.author.DB.F + msg.author.DB.F + "\n" + + msg.author.DB.F + "\n" + + msg.author.DB.F + msg.author.DB.F + msg.author.DB.F + "\n" + + msg.author.DB.F + "\n" + + msg.author.DB.F); } }; \ No newline at end of file diff --git a/cmds/fun/say.js b/cmds/fun/say.js index 361f48f..b4bfb1e 100644 --- a/cmds/fun/say.js +++ b/cmds/fun/say.js @@ -21,7 +21,7 @@ module.exports = class say extends commando.Command { sendThis.disableMentions = "none"; } const sent = await trySend(this.client, msg, sendThis); - if (args != '​' && msg.channel.guild && msg.member.hasPermission("MANAGE_MESSAGES") && !/^<@\!?\d{17,19}>\s.+/.test(msg.content)) { + if (args != '​' && msg.channel.guild && msg.member.hasPermission("MANAGE_MESSAGES") && !(new RegExp("^<@\!?" + msg.client.user.id + ">\s")).test(msg.content)) { tryDelete(msg); } ranLog(msg, sent.content); diff --git a/cmds/fun/send.js b/cmds/fun/send.js index ae0cd67..33da130 100644 --- a/cmds/fun/send.js +++ b/cmds/fun/send.js @@ -4,51 +4,51 @@ const emoteMessage = require("../../resources/emoteMessage"); const { ranLog, errLog, trySend, tryReact, findChannelRegEx, cleanMentionID, getChannel } = require("../../resources/functions"); module.exports = class send extends commando.Command { - constructor(client) { - super(client, { - name: "send", - memberName: "send", - group: "fun", - description: "Send message to designated channel.", - guildOnly:true - }); + constructor(client) { + super(client, { + name: "send", + memberName: "send", + group: "fun", + description: "Send message to designated channel.", + guildOnly: true + }); + } + async run(msg, args) { + const comarg = args.trim().split(/ +/); + if (!comarg[0]) { + return trySend(this.client, msg, 'Where?!?'); } - async run(msg, args ) { - const comarg = args.trim().split(/ +/); - if (!comarg[0]) { - return trySend(this.client, msg, 'Where?!?'); - } - const search = cleanMentionID(comarg[0]), - sendTheMes = emoteMessage(this.client, args.slice(comarg[0].length).trim()); - let channel = getChannel(msg, search, ["category", "voice"]); - if (!channel) { - return trySend(this.client, msg, "That channel is like your gf. Doesn't exist <:yeLife:796401669188354090>"); - } - if (!channel.permissionsFor(msg.author).has("SEND_MESSAGES") || !channel.permissionsFor(msg.author).has("VIEW_CHANNEL")) { - return trySend(this.client, msg, "No <:yeLife:796401669188354090>"); - } + const search = cleanMentionID(comarg[0]), + sendTheMes = emoteMessage(this.client, args.slice(comarg[0].length).trim()); + let channel = getChannel(msg, search, ["category", "voice"]); + if (!channel) { + return trySend(this.client, msg, "That channel is like your gf. Doesn't exist <:yeLife:796401669188354090>"); + } + if (!channel.permissionsFor(msg.author).has("SEND_MESSAGES") || !channel.permissionsFor(msg.author).has("VIEW_CHANNEL")) { + return trySend(this.client, msg, "No <:yeLife:796401669188354090>"); + } + try { + if (sendTheMes.length === 0) { + return trySend(this.client, channel, `<@!${msg.author.id}>, If you wanna send nothin then why you even typed that <:bruhLife:798789686242967554>`); + } + const sendThis = { content: sendTheMes, disableMentions: "all" }; + if (msg.member?.hasPermission("MENTION_EVERYONE")) sendThis.disableMentions = "none"; + const send = await trySend(this.client, channel, sendThis); + const filter = () => true, + collector = send.createReactionCollector(filter, { time: 15 * 6 * 1000 }); + collector.on('collect', r => { try { - if (sendTheMes.length === 0) { - return trySend(this.client, channel, `<@!${msg.author.id}>, If you wanna send nothin then why you even typed that <:bruhLife:798789686242967554>`); - } - const sendThis = {content:sendTheMes, disableMentions:"all"}; - if (msg.member?.hasPermission("MENTION_EVERYONE")) sendThis.disableMentions = "none"; - const send = await trySend(this.client, channel, sendThis); - const filter = () => true, - collector = send.createReactionCollector(filter, {time: 15*6*1000, dispose:true}); - collector.on('collect', r => { - try { - msg.react(r.emoji); - } catch (e) {} - }); - collector.on('remove', async r => await msg.reactions.resolve(r).id.remove(r.id)); - if (send) { - ranLog(msg, send.content.slice(0, 1900) + "\n\nSent to: " + `[${send.channel.name}](${send.url}) <#${send.channel.id}> (${send.channel.id})`); - tryReact(msg, 'yeLife:796401669188354090'); - } - return send; - } catch (e) { - return errLog(e, msg, this.client); - } + msg.react(r.emoji); + } catch (e) { } + }); + collector.on('remove', async r => msg.reactions.resolve(r).remove()); + if (send) { + ranLog(msg, send.content.slice(0, 1900) + "\n\nSent to: " + `[${send.channel.name}](${send.url}) <#${send.channel.id}> (${send.channel.id})`); + tryReact(msg, 'yeLife:796401669188354090'); + } + return send; + } catch (e) { + return errLog(e, msg, this.client); } + } }; \ No newline at end of file diff --git a/cmds/image/cuddle.js b/cmds/image/cuddle.js index 7e28959..10cead4 100644 --- a/cmds/image/cuddle.js +++ b/cmds/image/cuddle.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class cuddle extends commando.Command { constructor(client) { diff --git a/cmds/image/feed.js b/cmds/image/feed.js index b3a4bbb..7909f33 100644 --- a/cmds/image/feed.js +++ b/cmds/image/feed.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class feed extends commando.Command { constructor(client) { diff --git a/cmds/image/hug.js b/cmds/image/hug.js index 6ae8c91..e39ef7c 100644 --- a/cmds/image/hug.js +++ b/cmds/image/hug.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class hug extends commando.Command { constructor(client) { diff --git a/cmds/image/kiss.js b/cmds/image/kiss.js index d47a7ac..40fbcb4 100644 --- a/cmds/image/kiss.js +++ b/cmds/image/kiss.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class kiss extends commando.Command { constructor(client) { diff --git a/cmds/image/pat.js b/cmds/image/pat.js index de92cc1..4b29b30 100644 --- a/cmds/image/pat.js +++ b/cmds/image/pat.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class pat extends commando.Command { constructor(client) { diff --git a/cmds/image/poke.js b/cmds/image/poke.js index 37d07b5..b431c7d 100644 --- a/cmds/image/poke.js +++ b/cmds/image/poke.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class poke extends commando.Command { constructor(client) { diff --git a/cmds/image/rsc/interactEmbed.js b/cmds/image/rsc/interactEmbed.js new file mode 100644 index 0000000..ba425f1 --- /dev/null +++ b/cmds/image/rsc/interactEmbed.js @@ -0,0 +1,131 @@ +'use strict'; + +const { default: fetchNeko } = require("nekos-best.js"); +const { parseComa, getMember, defaultImageEmbed } = require("../../../resources/functions"); + +module.exports = async (msg, arg, name, endsaT = "") => { + msg.channel.startTyping(); + let shoot = msg.member, + target = [], + iC = 0; + if (!arg) { + shoot = msg.guild.member(msg.client.user); + target.push(msg.member.displayName); + } + if (!shoot.user.DB) await shoot.user.dbLoad(); + const args = parseComa(arg); + if (args?.length > 0) { + const mul = { + H: { + l: 0, + i: -1 + }, + C: {} + } + for (const key of args) { + if (!key || key.length === 0) continue; + const t = getMember(msg.guild, key)?.[0]?.displayName; + if (!t) continue; + if (t === shoot.displayName) { + const ifH = target.includes("themself (is this even physically possible)"); + if (ifH) { + target.filter((v, i) => { + if (v === "themself (is this even physically possible)") { + mul.H.i = i; + mul.H.l++; + } + }); + } else { + target.push("themself (is this even physically possible)"); + } + } else { + const ifC = target.includes(t); + if (ifC) { + target.filter((v, i) => { + if (v === t) { + if (!mul.C[v]) { + mul.C[v] = { + l: 1, + i: i + }; + } else { + mul.C[v].l++; + } + } + }); + } else { + target.push(t); + } + } + } + if (mul.H.i > -1) { + switch (mul.H.l) { + case 1: + target[mul.H.i] += " twice!"; + break; + case 2: + target[mul.H.i] += " thrice!!"; + break; + default: + target[mul.H.i] += ` ${mul.H.l++} times LMFAO`; + } + } + for (const li in mul.C) { + const d = mul.C[li]; + d.l++; + switch (d.l) { + case 2: + target[d.i] += " twice"; + break; + case 3: + target[d.i] += " thrice XD"; + break; + default: + target[d.i] += ` ${d.l} times ❤️`; + } + } + } + let lT, tN, sT; + if (target.length > 1) { + lT = target[target.length - 1]; + sT = target.slice(0, -1); + tN = sT.join(", ") + ` and ${lT}`; + } else { + if (target.length === 1) tN = target[0]; + } + if (target.length > 0) iC += target.length; + let ss; + if (tN) { + ss = name.endsWith("s") ? name + "es" : name + "s"; + const aT = `${shoot.displayName} ${ss} ${tN} ${tN.endsWith(" times LMFAO") ? "" : endsaT}`, + count = shoot.user.DB.interactions[name] + (iC > 0 ? 1 : 0), + emb = defaultImageEmbed(msg, await fetchNeko(name)); + let num; + if (count) { + const u = count?.toString(); + if (u?.endsWith("1") && !u.endsWith("11")) { + num = count + "st"; + } else { + if (u?.endsWith("2") && !u.endsWith("12")) { + num = count + "nd"; + } else { + if (u?.endsWith("3") && !u.endsWith("13")) { + num = count + "rd"; + } else { + num = count + "th"; + } + } + } + } else { + shoot.user.DB.interactions[name] = 0; + num = "First"; + } + shoot.user.DB.interactions[name] += iC; + shoot.user.setInteractions(shoot.user.DB.interactions); + emb.setAuthor(aT.length > 256 ? `${shoot.displayName} ${ss} so many friends ❤️❤️❤️` : aT, shoot.user.displayAvatarURL({ size: 128, format: "png", dynamic: true })) + .setFooter((emb.footer.text ? emb.footer.text + "・" : "") + num + ` ${name} from ` + shoot.displayName + " ❤️"); + return emb; + } else { + return "ERROR 404 partner not found <:yeLife:796401669188354090>"; + } +} \ No newline at end of file diff --git a/cmds/image/slap.js b/cmds/image/slap.js index 70d4039..fbb7c4f 100644 --- a/cmds/image/slap.js +++ b/cmds/image/slap.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class slap extends commando.Command { constructor(client) { diff --git a/cmds/image/tickle.js b/cmds/image/tickle.js index 0dbd308..dc6a825 100644 --- a/cmds/image/tickle.js +++ b/cmds/image/tickle.js @@ -2,7 +2,7 @@ const commando = require("@iceprod/discord.js-commando"); const { trySend } = require("../../resources/functions"); -const interactEmbed = require("./interactEmbed"); +const interactEmbed = require("./rsc/interactEmbed"); module.exports = class tickle extends commando.Command { constructor(client) { diff --git a/cmds/moderation/eventlog.js b/cmds/moderation/eventlog.js index 78dee99..9bc6acf 100644 --- a/cmds/moderation/eventlog.js +++ b/cmds/moderation/eventlog.js @@ -16,14 +16,14 @@ module.exports = class eventlog extends commando.Command { }); } async run(msg, arg) { - if (!msg.guild.dbLoaded) await msg.guild.dbLoad(); + if (!msg.guild.DB) await msg.guild.dbLoad(); const set = parseDoubleDash(arg); - let eventChannels = msg.guild.eventChannels; + let eventChannels = msg.guild.DB.settings.eventChannels; if (set.length < 2 && set[0].length === 0) return trySend(this.client, msg, await resultEmbed(this)); let report = "", joinlog, leavelog, channellog, banlog, unbanlog, mesEdlog = { channel: undefined, ignore: [] }, invitelog, rolelog, guildlog, membernicklog, emotelog, memberroleslog, remove = false, [setMesEdIgnore, setMesDelIgnore] = [false, false], mesDellog = { channel: undefined, ignore: [] }; for (const args of set) { - if (args.startsWith("r ")) remove = true; + if (args.startsWith("rm ")) remove = true; if (args.startsWith("j ")) { if (remove) eventChannels.join = undefined; else { leavelog = getChannel(msg, args.slice("j".length).trim(), ["category", "voice"])?.id; @@ -217,7 +217,7 @@ module.exports = class eventlog extends commando.Command { async function resultEmbed(the) { const emb = defaultImageEmbed(msg, null, "Event Log Channels Configuration"); emb - .setDescription(`Set configuration using \`\`\`js\n${msg.guild.commandPrefix + the.name} [--remove] -- \`\`\`**Categories:** \`\`\`js\n[MESSAGE[EDIT, DELETE]: --[e, d] [IGNORE: -i ], JOIN: --j, LEAVE: --l, MEMBER: --p, MEMBERROLE: --mr, BAN: --b, UNBAN: --u, GUILD: --g, ROLE: --r, CHANNEL: --c, EMOJI: --em, INVITE: --i]\`\`\``) + .setDescription(`Set configuration using \`\`\`js\n${msg.guild.commandPrefix + the.name} [--rm] -- \`\`\`**Categories:** \`\`\`js\n[MESSAGE[EDIT, DELETE]: --[e, d] IGNORE: -i, JOIN: --j, LEAVE: --l, MEMBER: --p, MEMBERROLE: --mr, BAN: --b, UNBAN: --u, GUILD: --g, ROLE: --r, CHANNEL: --c, EMOJI: --em, INVITE: --i]\`\`\``) .addField(`Message Edit`, eventChannels?.mesEd?.channel ? `<#${eventChannels?.mesEd.channel}>\n**Ignores:** ${eventChannels?.mesEd?.ignore?.length > 0 ? "<#" + eventChannels?.mesEd.ignore.join(">, <#") + ">" : "None"}` : "Not set", true) diff --git a/cmds/moderation/mute.js b/cmds/moderation/mute.js index 97bc2fd..192fe0c 100644 --- a/cmds/moderation/mute.js +++ b/cmds/moderation/mute.js @@ -1,11 +1,19 @@ 'use strict'; const commando = require("@iceprod/discord.js-commando"); -const { trySend, findMemberRegEx, cleanMentionID, findChannelRegEx, findRoleRegEx, defaultImageEmbed, parseDoubleDash, parseComa, getRole } = require("../../resources/functions"); +const { trySend, findMemberRegEx, cleanMentionID, findChannelRegEx, findRoleRegEx, defaultImageEmbed, parseDoubleDash, parseComa, getRole, defaultEventLogEmbed } = require("../../resources/functions"); const { database } = require("../../database/mongo"); const col = database.collection("Guild"); const schedule = database.collection("Schedule"); const { scheduler } = require("../../resources/scheduler"); +const { DateTime, Settings, Interval } = require("luxon"); +const muteSetting = require("./src/muteSetting"); +const fn = require("./src/duration"); +const durationFn = fn.duration; +const targetUser = require("./src/targetUser"); +const configureMuteRole = require("./src/configureMuteRole"); +const { makeJSONMessage } = require("../../resources/debug"); +Settings.defaultZone = "utc"; /*{ footer: { @@ -46,8 +54,14 @@ module.exports = class mute extends commando.Command { memberName: "mute", group: "moderation", description: "Mute.", + details: `Run \`${client.commandPrefix}mute --s -r role_[name|ID|mention] -d [duration]\` to set up a mute role.\n` + + `Or if you're too lazy you can run \`${client.commandPrefix}mute --cmr -n [name] -c color_[name|hex|number]\` to make a new mute role and let me set it up for you. ` + + `You can view server as the newly created mute role and override my default settings later.\n` + + `Example:\`\`\`\n--s -r muted -d 69y420mo36w49d69h4m420s\n` + + `--s -r none -d 0\n--cmr -n Muted -c black\`\`\``, guildOnly: true, - userPermissions: ['MANAGE_ROLES'] + userPermissions: ['MANAGE_ROLES'], + clientPermissions: ['MANAGE_ROLES'] }); } /** @@ -56,217 +70,85 @@ module.exports = class mute extends commando.Command { * @returns */ async run(msg, arg) { - if (!msg.guild.dbLoaded) msg.guild.dbLoad(); - const MOD = msg.guild.moderation, - MUTE = MOD.mute || { defaultDuration: {} }, + msg.channel.startTyping(); + if (!msg.guild.DB) await msg.guild.dbLoad(); + const MOD = msg.guild.DB.moderation.settings, + MUTE = MOD.mute || {}, args = parseDoubleDash(arg), - mentions = parseComa(args.shift()), - durationRegExp = /\d+(?![^ymwdhs])[ymwdhs]?o?/gi, - invokedAt = msg.createdAt, - duration = { - year: invokedAt.getFullYear(), - month: invokedAt.getMonth(), - date: invokedAt.getDate(), - hour: invokedAt.getHours(), - minute: invokedAt.getMinutes(), - second: invokedAt.getSeconds() - }; - let durationHasSet = false, - settingUp = false, - settingRole = false, - settingRoleHasSet = false, - settingDuration = false, - settingDurationHasSet = false, - [timeForMessage, targetUser] = [["Indefinite"], []], - reason = "No reason provided.", - resultMsg = ""; - for (const argument of args) { - const setArg = argument.toLowerCase().trim(); - if (/^settings?$/i.test(setArg)) { - settingUp = true; - } - if (settingUp && /^durations?$/i.test(setArg)) { - settingDuration = true; - } - if (settingUp && /^role$/i.test(setArg)) { - settingRole = true; - } - if (/^\d{1,16}(?![^ymwdhs])[ymwdhs]?o?/i.test(argument.trim()) && !durationHasSet) { - const durationArg = argument.match(durationRegExp); - for (const value of durationArg) { - const val = parseInt(value.match(/\d+/)[0], 10); - if (value.endsWith("h") || value.endsWith("ho")) { - duration.hour = duration.hour + val; - continue; - } - if (value.endsWith("y")) { - duration.year = duration.year + val; - continue; - } - if (value.endsWith("mo")) { - duration.month = duration.month + val; - continue; - } - if (value.endsWith("w")) { - duration.date = duration.date + 7 * val; - continue; - } - if (value.endsWith("d")) { - duration.date = duration.date + val; - continue; - } - if (value.endsWith("m") || !/\D/.test(value)) { - duration.minute = duration.minute + val; - continue; - } - if (value.endsWith("s")) { - duration.second = duration.second + val; - continue; - } - } - durationHasSet = true; - } else { - if (!settingRole && argument.length > 0 && argument !== "--") { - reason = argument.trim(); - } else { - if (settingRole && !settingRoleHasSet && argument.length > 0 && argument !== "--" && setArg !== "role") { - settingRoleHasSet = true; - const key = cleanMentionID(argument); - let role = getRole(msg.guild, key)?.id; - if (role || /^none$/i.test(key)) { - MUTE.role = role; - } else { - resultMsg += `No role found for: **${argument}**\n`; - } - } - } - } - } - const roleConfCheck = msg.guild.roles.cache.get(MUTE?.role); - if (!roleConfCheck && !settingUp) { - resultMsg += `No mute role configured! Run \`${msg.guild.commandPrefix}${this.name} --settings <--role --> [--duration --\` to set it up.`; - } - let untilDate = new Date(String(duration.year), String(duration.month), String(duration.date), String(duration.hour), String(duration.minute), String(duration.second)); - if (untilDate.toString() === "Invalid Date") untilDate = "Indefinite"; - if (untilDate?.toUTCString?.() === invokedAt.toUTCString() && !settingDuration) { - if (MUTE.defaultDuration.date?.valueOf() > 0) { - untilDate = new Date(invokedAt.valueOf() + MUTE.defaultDuration.date.valueOf() - 1000); - } else { - untilDate = "Indefinite"; - } - } - if (untilDate instanceof Date) { - timeForMessage = []; - const elapsedTime = new Date(untilDate.valueOf() - invokedAt.valueOf() + 1000), - elapsed = [ - elapsedTime.getFullYear() - 1970, - elapsedTime.getMonth(), - elapsedTime.getDate() - 1, - elapsedTime.getHours(), - elapsedTime.getMinutes(), - elapsedTime.getSeconds() - ], - elapsedName = [ - "year", - "month", - "day", - "hour", - "minute", - "second" - ]; + mentions = parseComa(args?.shift()); - for (let index = 0; index < elapsed.length; index++) { - if (elapsed[index] > 0) { - let mes = `${elapsed[index]} ${elapsedName[index]}`; - if (elapsed[index] > 1) { - mes += "s"; - } else { } - timeForMessage.push(mes); - } else { } - } - if (timeForMessage.length > 1) { - timeForMessage[timeForMessage.length - 2] += " and"; - } - if (settingDuration && !settingDurationHasSet && timeForMessage.length > 0) { - settingDurationHasSet = true; - MUTE.defaultDuration.date = elapsedTime, - MUTE.defaultDuration.string = timeForMessage.join(" "); + if (!MOD.mute) msg.guild.DB.moderation.settings.mute = {}; + let reason = "No reason provided", duration = {}, resultMsg = "", targetUsers = []; + + if (args?.[1]) { + for (const ARG of args) { + const U = ARG.slice(2).trim(); + if (/^cmr(\s|$)/.test(ARG)) return configureMuteRole(msg, ARG.slice(3).trim()); + if (/^s(\s|$)/.test(ARG)) return muteSetting(msg, U); + if (/^[\-\+]?\d{1,16}(?![^ymwdhs])[ymwdhs]?o?/i.test(ARG.trim())) { + duration = durationFn(msg.editedAt || msg.createdAt, ARG.trim()); + } else if (!ARG || ARG === "--" || ARG.trim().length === 0) continue; else reason = ARG.trim(); } + } else if (!MUTE.role || !msg.guild.roles.cache.get(MUTE.role)) { + return trySend(this.client, msg, `No mute role configured!\n\n**[ADMINISTRATOR]**\nRun \`${msg.guild.commandPrefix + this.name} --s -r role_[name|ID|mention] -d [duration]\` to set it up.\n` + + `Or if you're too lazy you can run \`${msg.guild.commandPrefix + this.name} --cmr -n [name] -c color_[name|hex|number]\` to make a new mute role and let me set it up for you. ` + + `You can view server as the new mute role and override my default settings later.\n` + + `Example:\`\`\`\n--s -r muted -d 69y420mo36w49d69h4m420s\n` + + `--s -r none -d 0\n--cmr -n Muted -c black\`\`\``); } - if (settingUp || !roleConfCheck && !settingUp) { - if (settingDurationHasSet || settingRoleHasSet) { - MOD.mute = MUTE; - } - let settings = defaultImageEmbed(msg); - settings - .setTitle("Mute Configuration") - .addField("Role", roleDoc ? "<@&" + roleDoc + ">" : "Not set") - .addField("Duration", defaultDurationDoc?.string ?? "Not set"); - return trySend(this.client, msg, { content: resultMsg, embed: settings }); - } - for (const usermention of mentions) { - if (usermention.length > 0) { - let found = [], - nameid = cleanMentionID(usermention); - if (/^\d{17,19}$/.test(nameid)) { - const findmem = msg.guild.member(nameid); - if (findmem) { - found.push(findmem.user); - } else { - await this.client.users.fetch(nameid).then(fetchUser => found.push(fetchUser)).catch(() => { }); - } - } else { - found = findMemberRegEx(msg, nameid).map(r => r.user); - } - if (found.length > 0 && found[0] !== null) { - const foundDupli = targetUser.findIndex(r => r === found[0]); - if (foundDupli !== -1) { - resultMsg += `**[WARNING]** Duplicate for user **${targetUser[foundDupli].tag}** with keyword: **${usermention.trim()}**\n`; - } else { - targetUser.push(found[0]); - if (found.length > 1) { - resultMsg += `**[WARNING]** Multiple users found for: **${usermention.trim()}**\n`; - } - } - } else { - resultMsg += `Can't find user: **${usermention.trim()}**\n`; - } - } else { - if (!settingUp && mentions[0].length === 0) { - return trySend(this.client, msg, "Args: `<[user_[mention|ID|name]]> -- [reason] -- [duration]`. Use `,` to provide multiple user.\nExample:```js\n" + `${msg.guild.commandPrefix}${this.name} 580703409934696449, @Shasha#1234, ur mom,#6969,^fuck\\s(ur)?\\s.{5}#\\d+69$--69y69mo69w420d420h420m420s -- Saying "joe"\`\`\``); - } - } + + if (!duration.invoked) duration.invoked = DateTime.fromJSDate(msg.editedAt || msg.createdAt); + if (!duration.until && MUTE.defaultDuration?.duration) duration.until = duration.invoked.plus(MUTE.defaultDuration.duration.object); + if (duration.until?.invalid) duration.until = undefined; else if (duration.until && !duration.duration) { + duration.interval = Interval.fromDateTimes(duration.invoked, duration.until); + duration.duration = fn.intervalToDuration(duration.interval); } + + if (mentions?.length > 0) { + const FR = await targetUser(msg, mentions, targetUsers, resultMsg); + targetUsers = FR.targetUser; + resultMsg = FR.resultMsg; + } else return trySend(this.client, msg, "Args: `<[user_[mention|ID|name]]> -- [reason] -- [duration]`. Use `,` to provide multiple user. `--s` to view settings.\nExample:```js\n" + `${msg.guild.commandPrefix + this.name} 580703409934696449, @Shasha#1234, ur mom,#6969,^yuck\\s(ur)?\\s.{5}#\\d+69$--69y69mo69w420d420h420m420s -- Saying "joe"\`\`\``); + let infractionToDoc; - if (targetUser.length > 0) { - let targetMember = [], - notInServer = []; - for (const user of targetUser) { - const member = msg.guild.member(user); - if (member) { - const pushIt = member.toJSON(); - pushIt.rolesID = member.roles.cache.map(r => r.id); - targetMember.push(pushIt); - } else { - const pushIt = user.toJSON(); - notInServer.push(pushIt); - } - } - const infractionCase = msg.guild.infractions?.length; + if (targetUsers.length > 0) { + let infractionCase = msg.guild.DB.moderation.infractions?.length, + muted = [], cant = [], already = [], infractionN = []; + infractionToDoc = { - infraction: infractionCase ? infractionCase + 1 : 1, - by: targetUser, + infraction: infractionCase ? infractionCase++ : 1, + by: targetUsers, moderator: msg.author, punishment: "mute", - at: invokedAt, - for: timeForMessage, - until: untilDate, reason: reason, - msg: msg.toJSON(), - members: targetMember, - users: notInServer + msg: msg.toJSON() } - await col.updateOne({ document: msg.guild.id }, { $push: { "moderation.infractions": infractionToDoc } }, { upsert: true }); + + for (const EXEC of targetUsers) { + try { + const RES = await EXEC.mute(msg.guild, { duration: duration, infraction: infractionToDoc.infraction, moderator: msg.member }, reason); + if (RES.infraction) infractionN.push(RES.infraction); + muted.push(EXEC.id); + } catch (e) { + if (/Missing Permissions|someone with higher position/.test(e.message)) cant.push(EXEC.id); + else if (/already muted/.test(e.message)) already.push(EXEC.id); else trySend(msg.client, msg, e.message); continue; + } + const emb = defaultEventLogEmbed(msg.guild); + emb.setTitle("You have been muted") + .setDescription("**Reason**\n" + reason) + .addField("At", duration.invoked.toFormat(fn.DT_PRINT_FORMAT), true) + .addField("For", duration.duration?.strings.join(" ") || "Indefinite", true) + .addField("Until", duration.until?.toFormat(fn.DT_PRINT_FORMAT) || "Never", true); + EXEC.createDM().then(r => trySend(msg.client, r, emb)); + } + + if (muted.length > 0) { + infractionToDoc.executed = muted; + infractionToDoc.aborted = already; + infractionToDoc.failed = cant; + msg.guild.addInfraction(infractionToDoc); + } + const NAME = msg.guild.id + "/" + infractionToDoc.infraction, newUnmuteSchedule = { name: NAME, @@ -274,35 +156,30 @@ module.exports = class mute extends commando.Command { worker: { argv: [NAME] }, - date: untilDate + date: duration.until?.toJSDate() }; + + let emb = defaultImageEmbed(msg, null, "Infraction #" + infractionToDoc.infraction); + + if (cant.length > 0) emb.addField("Can't mute", "<@" + cant.join(">, <@") + ">\n\n**You can't mute someone with higher position than you <:nekokekLife:852865942530949160>**"); + if (already.length > 0) emb.addField("Already muted", "<@" + already.join(">, <@") + ">\n\nDuration updated for these users"); + let mutedStr = "", mutedArr = []; + if (muted.length > 0) for (const U of muted) { + const tU = "<@" + U + ">\n"; + if ((mutedStr + tU).length < 1000) mutedStr += tU; else mutedArr.push(U); + } + if (mutedArr.length > 0) mutedStr += `and ${mutedArr.length} more...`; + emb.setDescription("**Reason**\n" + reason) + .addField("Muted", mutedStr || "`[NONE]`") + .addField("At", duration.invoked.toFormat(fn.DT_PRINT_FORMAT), true) + .addField("For", duration.duration?.strings.join(" ") || "Indefinite", true) + .addField("Until", duration.until?.toFormat(fn.DT_PRINT_FORMAT) || "Never", true) + .addField("Reason", reason); + + return trySend(msg.client, msg, { content: resultMsg, embed: emb }); } - resultMsg += `Result:\`\`\`js\nUsers: ${targetUser.map(r => r?.tag).join(", ")}\nReason: ${reason}\nAt: ${invokedAt.toUTCString()}\nFor: ${timeForMessage.join(" ")}\nUntil: ${typeof untilDate !== "string" ? untilDate.toUTCString() : untilDate}\`\`\``; + return trySend(msg.client, msg, resultMsg); + resultMsg += `Result:\`\`\`js\nUsers: ${targetUsers.map(r => r?.tag).join(", ")}\nReason: ${reason}\nAt: ${duration.invoked.toFormat("DDD',' cccc',' tt")}\nFor: ${duration.duration?.strings?.join(" ")}\nUntil: ${duration.until?.toFormat("DDD',' cccc',' tt")}\`\`\``; return trySend(this.client, msg, { content: resultMsg + "```js\n" + JSON.stringify(infractionToDoc, null, 2) + "```", split: { maxLength: 2000, append: ",```", prepend: "```js\n", char: "," } }); } -}; - -/* if (config.mute.role.length === 0) { - return msg.channel.send(`Mute role isn't set! Run \`${this.client.commandPrefix}mute --role \`. If you insist i will just give them admin perms <:purifyLife:774102054046007298>`) -} -if (setArgs) { - for(let set of setArgs) { - set = set.toLowerCase(); - switch(set) { - case startsWith('role'): { - let role = set.slice('role'.length).trim(); - if (role.startsWith('<&')) { - role = role.slice(2,-1); - } - //const foundRole = - } - } - } -}*/ - //scheduler.add() -/*const yearDate = dateDur.getFullYear(); -const monthDate = dateDur.getMonth(); -const dayDate = dateDur.getDay(); -const hourDate = dateDur.getHours(); -const minuteDate = dateDur.getMinutes(); -const secondDate = dateDur.getSeconds();*/ \ No newline at end of file +}; \ No newline at end of file diff --git a/cmds/moderation/src/configureMuteRole.js b/cmds/moderation/src/configureMuteRole.js new file mode 100644 index 0000000..0c7fe6d --- /dev/null +++ b/cmds/moderation/src/configureMuteRole.js @@ -0,0 +1,50 @@ +'use strict'; + +const { Message } = require("discord.js"); +const { trySend, parseDash, defaultImageEmbed, defaultSplitMessage } = require("../../../resources/functions"); +const getColor = require("../../../resources/getColor"); + +module.exports = async (msg, arg) => { + if (!msg.member.isAdmin) return trySend(msg.client, msg, "<@" + msg.author + "> you're not an Administrator <:nekohmLife:846371737644957786>"); + if (!msg.guild.member(msg.guild.client.user).isAdmin) return trySend(msg.client, msg, "<@" + msg.author + "> i am not an Administrator <:pepewhysobLife:853237646666891274>"); + const args = parseDash(arg); + if (!args?.[1]) return trySend(msg.client, msg, "Args: `-n role_[name|mention|ID] -c color_[name|number|hex]`"); + let data = { name: "Muted" }; + for (const ARG of args) { + const U = ARG.slice(2).trim(); + if (ARG.startsWith("n ")) if (U.length > 0) data.name = U; + if (ARG.startsWith("c ")) if (U.length > 0) data.color = getColor(U); + } + if (data.name?.length > 100) return trySend(msg.client, msg, "Role name must be less than 100 characters in length!"); + let emb = defaultImageEmbed(msg, null, "Create Mute Role"); + emb.addField("Name", data.name) + .setDescription("Respond with **'yes'** if you want to proceed. A new role will be created with the following properties:") + .addField("Color", "This embed's color"); + if (data.color) emb.setColor(data.color); + await trySend(msg.client, msg, emb); + const RR = await msg.channel.awaitMessages((r) => r.author === msg.author, { max: 1, time: 30000 }); + if (RR.first()?.content.toLowerCase() === "yes") return detonate(msg, data); else return trySend(msg.client, msg, "Create Mute Role: **Cancelled**"); +} + +async function detonate(msg, data) { + const map = msg.guild.channels.cache.map(r => r); + const pleaseWait = await trySend(msg.client, msg, `Setting up for ${map.length} channel${map.length < 2 ? "" : "s"}... This message will be edited when done.`); + data.permissions = 0; + const ROLE = await msg.guild.roles.create({ data: data, reason: "Create Mute Role" }).catch(() => { }); + msg.guild.DB.moderation.settings.mute.role = ROLE.id; + let cant = []; + if (ROLE) { + for (const U of map) { + await U.updateOverwrite(ROLE, { SEND_MESSAGES: false, CONNECT: false }, "Create Mute Role").catch(() => cant.push(U.id)); + } + } else return pleaseWait.edit("Create Mute Role: Can't create role. Operation cancelled"); + if (cant.length > 0) { + const split = defaultSplitMessage, + mes = "**Can't overwrite permissions in:**\n"; + split.append = ","; + split.prepend = ""; + trySend(msg.client, msg, { content: mes + "<#" + cant.join(">,\n<#") + ">", split: split }); + } + msg.guild.setDb(msg.guild.DB); + return pleaseWait.edit(`Create Mute Role: ${ROLE} **Done**`); +} \ No newline at end of file diff --git a/cmds/moderation/src/duration.js b/cmds/moderation/src/duration.js index eb109ab..d72d1ab 100644 --- a/cmds/moderation/src/duration.js +++ b/cmds/moderation/src/duration.js @@ -1,2 +1,93 @@ 'use strict'; +const { DateTime, Settings, Interval } = require("luxon"), + DURATION_REGEXP = /[\-]?\d+(?![^ymwdhs])[ymwdhs]?o?/gi, + DT_PRINT_FORMAT = "DDD'\n'cccc',' tt"; + +Settings.defaultZone = "utc"; +/** + * + * @param {Interval} interval + * @returns {{ "object": {years: number, months: number,days: number,hours: number,minutes: number,seconds: number}, strings: string[] }} + */ +function intervalToDuration(interval) { + if (!(interval instanceof Interval)) return; + const object = interval.toDuration(["years", "months", "days", "hours", "minutes", "seconds"], { conversionAccuracy: "longterm" }).toObject(); + let strings = []; + for (const S in object) { + object[S] = Math.floor(object[S]); + if (object[S] > 0) strings.push(`${object[S]} ${object[S] === 1 ? S.slice(0, -1) : S}`); else continue; + } + if (strings.length > 0) { + if (strings.length > 1) strings[strings.length - 2] += " and"; + return { object, strings }; + } else console.log(interval, object); +}; + +/** + * @param {Date} base - Base date + * @param {string} string - To match /[\-]?\d+(?![^ymwdhs])[ymwdhs]?o?/gi + */ +function duration(base, string) { + const DURATION = { + year: base.getFullYear(), + month: base.getMonth(), + day: base.getDate(), + hour: base.getHours(), + minute: base.getMinutes(), + second: base.getSeconds() + 1 + }, + DT_INVOKED = DateTime.fromJSDate(base), + DURATION_ARGS = string.match(DURATION_REGEXP); + + let changed = false; + console.log(DURATION_ARGS, DT_INVOKED.toFormat(DT_PRINT_FORMAT)); + + for (const value of DURATION_ARGS) { + const val = parseInt(value.match(/[\-]?\d+/)[0], 10); + console.log(val); + if (!val) continue; + if (value.endsWith("h") || value.endsWith("ho")) { + DURATION.hour = DURATION.hour + val; + if (!changed) changed = true; + continue; + } + if (value.endsWith("y")) { + DURATION.year = DURATION.year + val; + if (!changed) changed = true; + continue; + } + if (value.endsWith("mo")) { + DURATION.month = DURATION.month + val; + if (!changed) changed = true; + continue; + } + if (value.endsWith("w")) { + DURATION.day = DURATION.day + 7 * val; + if (!changed) changed = true; + continue; + } + if (value.endsWith("d")) { + DURATION.day = DURATION.day + val; + if (!changed) changed = true; + continue; + } + if (value.endsWith("m") || !/[^\d\-\+]/.test(value)) { + DURATION.minute = DURATION.minute + val; + if (!changed) changed = true; + continue; + } + if (value.endsWith("s")) { + DURATION.second = DURATION.second + val; + if (!changed) changed = true; + continue; + } + } + let DT_END, DT_INTERVAL; + + if (changed) DT_END = DateTime.fromJSDate(new Date(DURATION.year, DURATION.month, DURATION.day, DURATION.hour, DURATION.minute, DURATION.second)); + if (DT_END) DT_INTERVAL = Interval.fromDateTimes(DT_INVOKED, DT_END) + return { invoked: DT_INVOKED, until: DT_END, interval: DT_INTERVAL, duration: intervalToDuration(DT_INTERVAL) } +} + +module.exports = { duration, DT_PRINT_FORMAT, intervalToDuration } \ No newline at end of file diff --git a/cmds/moderation/src/muteSetting.js b/cmds/moderation/src/muteSetting.js new file mode 100644 index 0000000..b6593b5 --- /dev/null +++ b/cmds/moderation/src/muteSetting.js @@ -0,0 +1,44 @@ +'use strict'; + +const { parseDash, defaultImageEmbed, trySend, getRole, cleanMentionID } = require("../../../resources/functions"), + fn = require("./duration"), + ARGS_TEXT = "Args:\n`-r` Role: `role_[name|mention|ID]`,\n`-d` Duration: `[duration]` - Format: `number_[y|mo|w|d|h|m|s]`"; + +module.exports = (msg, arg) => { + if (!msg.member.isAdmin) return trySend(msg.client, msg, msg.author + " you're not an Administrator <:nekohmLife:846371737644957786>"); + const args = parseDash(arg); + let setEmb = defaultImageEmbed(msg, null, "Mute Configuration"), + MUTE = msg.guild.DB.moderation.settings.mute || {}, + duration, + role, + resultMsg = ""; + console.log(args); + if (arg && !args[1]) setEmb.setDescription(ARGS_TEXT); else if (args?.[1]) { + for (const ARG of args) { + if (ARG.startsWith("r ")) { + const key = cleanMentionID(ARG.slice(2)); + if (key === "none") { + role = false; + continue; + } + if (key?.length > 0) role = getRole(msg.guild, key)?.id; + if (role === undefined) resultMsg += `No role found for: **${ARG}**\n`; else msg.guild.DB.moderation.settings.mute.role = role; + } + if (ARG.startsWith("d ")) { + const D = ARG.slice(2).trim(); + console.log(D); + if (/^[\-\+]?\d{1,16}(?![^ymwdhs])[ymwdhs]?o?/i.test(D)) { + duration = fn.duration(msg.createdAt, D); + msg.guild.DB.moderation.settings.mute.defaultDuration = duration; + } else resultMsg += "Valid duration format: `number_[y|mo|w|d|h|m|s]`. Example: `69y420w5m72s3mo`"; + } + } + MUTE = msg.guild.DB.moderation.settings.mute; + msg.guild.setDb(msg.guild.DB); + } + + setEmb + .addField("Role", MUTE.role ? "<@&" + MUTE.role + ">" : "Not set") + .addField("Duration", MUTE.defaultDuration?.duration?.strings?.join(" ") || "Not set"); + return trySend(msg.client, msg, { content: resultMsg, embed: setEmb }); +} \ No newline at end of file diff --git a/cmds/moderation/src/targetUser.js b/cmds/moderation/src/targetUser.js new file mode 100644 index 0000000..d5a8cb0 --- /dev/null +++ b/cmds/moderation/src/targetUser.js @@ -0,0 +1,45 @@ +'use strict'; + +const { Message, User } = require("discord.js"); +const { cleanMentionID, findMemberRegEx } = require("../../../resources/functions"); + +/** + * @param {Message} msg + * @param {string[]} mentions + * @param {User[]} targetUser + * @param {string} resultMsg + * @returns {Promise<{ targetUser: User[], resultMsg: string }>} + */ +module.exports = async (msg, mentions = [], targetUser = [], resultMsg) => { + if (mentions.length === 0) throw new TypeError("Mentions has no length"); + for (const usermention of mentions) { + if (usermention.length > 0) { + let found = [], + nameid = cleanMentionID(usermention); + if (/^\d{17,19}$/.test(nameid)) { + const findmem = msg.guild.member(nameid); + if (findmem) { + found.push(findmem.user); + } else { + await msg.client.users.fetch(nameid).then(fetchUser => found.push(fetchUser)).catch(() => { }); + } + } else { + found = findMemberRegEx(msg, nameid).map(r => r.user); + } + if (found.length > 0 && found[0] !== null) { + const foundDupli = targetUser.findIndex(r => r === found[0]); + if (foundDupli !== -1) { + resultMsg += `**[WARNING]** Duplicate for user **${targetUser[foundDupli].tag}** with keyword: **${usermention.trim()}**\n`; + } else { + targetUser.push(found[0]); + if (found.length > 1) { + resultMsg += `**[WARNING]** Multiple users found for: **${usermention.trim()}**\n`; + } + } + } else { + resultMsg += `Can't find user: **${usermention.trim()}**\n`; + } + } else continue; + } + return { targetUser, resultMsg }; +} \ No newline at end of file diff --git a/cmds/moderation/unmute.js b/cmds/moderation/unmute.js new file mode 100644 index 0000000..8f4184c --- /dev/null +++ b/cmds/moderation/unmute.js @@ -0,0 +1,67 @@ +'use strict'; + +const commando = require("@iceprod/discord.js-commando"); +const { parseDoubleDash, trySend, defaultImageEmbed, parseComa, defaultEventLogEmbed } = require("../../resources/functions"); +const targetUser = require("./src/targetUser"); + +module.exports = class unmute extends commando.Command { + constructor(client) { + super(client, { + name: "unmute", + memberName: "unmute", + group: "moderation", + description: "Mute.", + details: "Args: `user_[mention|name|ID] -- [reason]`", + guildOnly: true, + userPermissions: ['MANAGE_ROLES'], + clientPermissions: ['MANAGE_ROLES'] + }); + } + + async run(msg, arg) { + msg.channel.startTyping(); + if (!arg) return trySend(msg.client, msg, this.details); + const args = parseDoubleDash(arg), + mentions = parseComa(args.shift()); + let reason = "No reason provided", targetUsers = [], resultMsg = ""; + if (args?.length > 0) { + for (const ARG of args) if (!ARG || ARG === "--" || ARG.trim().length === 0) continue; else reason = ARG.trim(); + } + if (mentions?.length > 0) { + const FR = await targetUser(msg, mentions, targetUsers, resultMsg); + console.log(FR); + targetUsers = FR.targetUser; + resultMsg = FR.resultMsg; + } + let notMuted = [], + cant = [], success = []; + + for (const USER of targetUsers) { + if (!USER.DB) await USER.dbLoad(); + const L = USER.getMutedIn(msg.guild.id); + + if (!L.data) { notMuted.push(USER.id); continue } else { + await USER.unmute(msg.guild, msg.member, reason) + .then(() => { + success.push(USER.id); + const emb = defaultEventLogEmbed(msg.guild); + + emb.setTitle("You have been unmuted") + .setDescription("**Reason**\n" + reason); + + USER.createDM().then(r => trySend(msg.client, r, emb)); + }) + .catch((e) => { + console.log(e); cant.push(USER.id) + }); + } + } + + let emb = defaultImageEmbed(msg, null, "Unmute"); + if (cant.length > 0) emb.addField("Can't unmute", "<@" + cant.join(">, <@") + ">"); + if (notMuted.length > 0) emb.addField("Wasn't muted", "<@" + notMuted.join(">, <@") + ">"); + emb.setDescription("**Unmuted**\n" + (success.length > 0 ? "<@" + success.join(">, <@") + ">" : "`[NONE]`")) + .addField("Reason", reason); + return trySend(msg.client, msg, { content: resultMsg, embed: emb }); + } +} \ No newline at end of file diff --git a/cmds/owner/eval.js b/cmds/owner/eval.js index 2074e33..0ab038c 100644 --- a/cmds/owner/eval.js +++ b/cmds/owner/eval.js @@ -63,7 +63,7 @@ module.exports = class EvalCommand extends Command { this.lastResult = await eval(args.script); hrDiff = process.hrtime(hrStart); } catch (err) { - return msg.reply(`Error while evaluating: \`${err}\``); + return msg.reply(`\`\`\`\n${err.stack}\`\`\``); } // Prepare for callback time and respond diff --git a/cmds/utility/embmaker.js b/cmds/utility/embmaker.js index 4e3db03..765e25b 100644 --- a/cmds/utility/embmaker.js +++ b/cmds/utility/embmaker.js @@ -36,9 +36,7 @@ module.exports = class embmaker extends commando.Command { } async run(msg, arg) { let isAdmin = true; - if (msg.guild && !this.client.owners.includes(msg.author)) { - if (!msg.member.hasPermission("ADMINISTRATOR")) isAdmin = false; - } + if (msg.guild) isAdmin = msg.member.isAdmin; const args = parseDoubleDash(arg); let embed = new MessageEmbed(); let autName, footertext, autIcon, autUrl, footericon, content, channel, editSrc, newAttach = [], reportMessage = ""; diff --git a/cmds/utility/perms.js b/cmds/utility/perms.js index df5fa50..98649db 100644 --- a/cmds/utility/perms.js +++ b/cmds/utility/perms.js @@ -66,6 +66,7 @@ module.exports = class perms extends commando.Command { } const title = `Permissions for: \`${member.user.tag}\``; mes += `**Default:**\`\`\`js\n`; + if (msg.member.isAdmin) mes += "'ADMINISTRATOR', "; if (res.length > 0) { mes += `${res.join(", ")}\`\`\``; } else { diff --git a/config_copy.json b/config_copy.json new file mode 100644 index 0000000..7c8e75d --- /dev/null +++ b/config_copy.json @@ -0,0 +1,30 @@ +{ + "invite": "https://discord.com/oauth2/authorize?client_id=788006279837909032&scope=bot&permissions=8", + "token": "", + "errLogChannel": "822877910138224660", + "randomColors": [ + 12357519, + 16711935, + 128, + 32896, + 15277667, + "00ff00", + "ff0000", + "ff94f2", + "f1e40f", + "ff8c00", + "a0522d", + 3447003, + "0fffff", + "803c9d", + "faa775", + "000000", + 16777214 + ], + "defaultErrorLogChannel": "822877910138224660", + "mongoServer": "mongodb://localhost:27017", + "chatChannel": "837178237322919966", + "guildLog": "840154722434154496", + "shardChannel": "851361670533218324", + "home": "772073587792281600" +} \ No newline at end of file diff --git a/database/mongo.js b/database/mongo.js index 9935cea..5b36a2b 100644 --- a/database/mongo.js +++ b/database/mongo.js @@ -2,7 +2,7 @@ const { mongoServer } = require("../config.json"); const { MongoClient, Db } = require("mongodb"); -const dbClient = new MongoClient(mongoServer, { +const dbClient = new MongoClient(process.env.MONGO_HOST || mongoServer, { useUnifiedTopology: true }); diff --git a/package.json b/package.json index f9c8bd5..21b51d9 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,15 @@ { "dependencies": { - "@iceprod/discord.js-commando": "^0.14.3", + "@iceprod/discord.js-commando": "github:iceproductions/Commando#next", "axios": "^0.21.1", "bree": "^6.2.0", "bufferutil": "^4.0.3", "cabin": "^9.0.4", - "discord.js": "^12.5.2", - "discord.js-commando": "^0.12.3", + "discord.js": "^12.5.3", "erlpack": "github:discord/erlpack", "fs-extra": "^9.1.0", "lodash": "^4.17.21", - "moment": "^2.29.1", + "luxon": "^2.0.1", "mongodb": "^3.6.6", "nekos-best.js": "^2.0.2", "node": "^15.12.0", @@ -19,5 +18,8 @@ "sqlite3": "^5.0.2", "utf-8-validate": "^5.0.4", "zlib-sync": "^0.1.7" + }, + "devDependencies": { + "@types/luxon": "^1.27.1" } } diff --git a/resources/debug.js b/resources/debug.js index 3956b6c..9b49960 100644 --- a/resources/debug.js +++ b/resources/debug.js @@ -11,11 +11,12 @@ function timestampAt(client) { /** * - * @param {String} string + * @param {object} object * @returns {import("discord.js").MessageOptions} */ -function makeJSONMessage(string) { - return { content: '```js\n' + JSON.stringify(string, (k, v) => v ?? undefined, 2) + '```', split: { maxLength: 2000, char: ",", append: ',```', prepend: '```js\n' } }; +function makeJSONMessage(object) { + console.log(typeof object, object); + return { content: '```js\n' + JSON.stringify(object, (k, v) => v ?? undefined, 2) + '```', split: { maxLength: 2000, char: ",", append: ',```', prepend: '```js\n' } }; } module.exports = { timestampAt, makeJSONMessage } \ No newline at end of file diff --git a/resources/eventsLogger/guildBanAdd.js b/resources/eventsLogger/guildBanAdd.js index 83c4011..08554ca 100644 --- a/resources/eventsLogger/guildBanAdd.js +++ b/resources/eventsLogger/guildBanAdd.js @@ -4,13 +4,13 @@ const { getChannel, defaultEventLogEmbed, trySend } = require("../functions"); const getColor = require("../getColor"); module.exports = async (GUILD, USER) => { - if (GUILD.eventChannels?.ban) { + if (GUILD.DB.settings.eventChannels?.ban) { if (USER.partial) USER = await USER.fetch(); - const log = getChannel(GUILD, GUILD.eventChannels.ban); + const log = getChannel(GUILD, GUILD.DB.settings.eventChannels.ban); if (!log) return; const emb = defaultEventLogEmbed(GUILD); const rea = (await GUILD.fetchBan(USER)).reason; - emb.setDescription(rea ?? "No reason provided.") + emb.setDescription(rea ?? "No reason provided") .setTitle(`\`${USER.tag}\` banned`) .setColor(getColor("red")) .setThumbnail(USER.displayAvatarURL({ size: 4096, format: "png", dynamic: true })) diff --git a/resources/eventsLogger/guildMemberAdd.js b/resources/eventsLogger/guildMemberAdd.js index a3bc074..5809e8d 100644 --- a/resources/eventsLogger/guildMemberAdd.js +++ b/resources/eventsLogger/guildMemberAdd.js @@ -10,8 +10,8 @@ const getColor = require("../getColor"); * @returns */ module.exports = (member) => { - if (member.guild.eventChannels?.join) { - const log = getChannel(member, member.guild.eventChannels.join); + if (member.guild.DB.settings.eventChannels?.join) { + const log = getChannel(member, member.guild.DB.settings.eventChannels.join); if (!log) return; const emb = defaultEventLogEmbed(member.guild); emb diff --git a/resources/eventsLogger/guildMemberRemove.js b/resources/eventsLogger/guildMemberRemove.js index c4c2d44..2d25241 100644 --- a/resources/eventsLogger/guildMemberRemove.js +++ b/resources/eventsLogger/guildMemberRemove.js @@ -10,8 +10,8 @@ const getColor = require("../getColor"); * @returns */ module.exports = (member) => { - if (member.guild.eventChannels?.leave) { - const log = getChannel(member, member.guild.eventChannels.leave); + if (member.guild.DB.settings.eventChannels?.leave) { + const log = getChannel(member, member.guild.DB.settings.eventChannels.leave); if (!log) return; const days = Math.floor(new Date(new Date().valueOf() + member.client.matchTimestamp - member.joinedAt.valueOf()).valueOf() / 86400000), emb = defaultEventLogEmbed(member.guild); diff --git a/resources/eventsLogger/guildMemberUpdate.js b/resources/eventsLogger/guildMemberUpdate.js index c426cfe..3a9dd7a 100644 --- a/resources/eventsLogger/guildMemberUpdate.js +++ b/resources/eventsLogger/guildMemberUpdate.js @@ -11,19 +11,19 @@ const getColor = require("../getColor"); * @returns */ module.exports = (memberold, membernew) => { - if (!membernew.guild.eventChannels?.memberRole && !membernew.guild.eventChannels?.member) { - if (membernew.user.cachedAvatarURL != membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) { - membernew.user.cachedAvatarURL = membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true }); + if (!membernew.guild.DB.settings.eventChannels?.memberRole && !membernew.guild.DB.settings.eventChannels?.member) { + if (membernew.user.DB.cachedAvatarURL != membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) { + membernew.user.DB.cachedAvatarURL = membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true }); }; - return; + return membernew.user.setDb(membernew.user.DB); } let log; const emb = defaultEventLogEmbed(membernew.guild); emb.setTitle("Profile `" + memberold.user.tag + "` updated") - .setThumbnail(membernew.user.cachedAvatarURL ?? memberold.toJSON().displayAvatarURL) + .setThumbnail(membernew.user.DB.cachedAvatarURL ?? memberold.toJSON().displayAvatarURL) .setColor(getColor("blue")); - if (membernew.guild.eventChannels?.memberRole) { - log = getChannel(membernew, membernew.guild.eventChannels.memberRole); + if (membernew.guild.DB.settings.eventChannels?.memberRole) { + log = getChannel(membernew, membernew.guild.DB.settings.eventChannels.memberRole); if (membernew.roles.cache.size > memberold.roles.cache.size) { emb.addField("Role added", ("<@&" + membernew.roles.cache.difference(memberold.roles.cache).sort((a, b) => b.position - a.position).map(r => r.id).join(">, <@&") + ">").slice(0, 2048)) .setDescription("**Old roles**\n" + (memberold.roles.cache.size > 1 ? "<@&" + memberold.roles.cache.sort((a, b) => b.position - a.position).map(r => r.id).slice(0, -1).join(">, <@&") + ">" : "`[NONE]`")); @@ -33,20 +33,18 @@ module.exports = (memberold, membernew) => { .setDescription("**Current roles**\n" + (membernew.roles.cache.size > 1 ? "<@&" + membernew.roles.cache.sort((a, b) => b.position - a.position).map(r => r.id).slice(0, -1).join(">, <@&") + ">" : "`[NONE]`")); } } - if (membernew.guild.eventChannels?.member && membernew.roles.cache.size === memberold.roles.cache.size) { - log = getChannel(membernew, membernew.guild.eventChannels.member); + if (membernew.guild.DB.settings.eventChannels?.member && membernew.roles.cache.size === memberold.roles.cache.size) { + log = getChannel(membernew, membernew.guild.DB.settings.eventChannels.member); if (membernew.displayName != memberold.displayName) { emb.addField("Nickname", "Changed from `" + memberold.displayName + "` to `" + membernew.displayName + "`"); } - if (membernew.user.cachedAvatarURL != membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) { + if (membernew.user.DB.cachedAvatarURL != membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) { emb .setImage(membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) .addField("Avatar", (emb.thumbnail ? "This embed's thumbnail is the user's old avatar.\n" : "") + "The image below is the user's new avatar."); } } - if (membernew.user.cachedAvatarURL != membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) { - membernew.user.cachedAvatarURL = membernew.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true }); - }; + membernew.user.refreshDb({ cachedAvatarURL: membernew.user.displayAvatarURL({ format: "png", size: "4096", dynamic: true }) }); if (!emb.fields || emb.fields.length === 0) return; return trySend(membernew.client, log, emb); } diff --git a/resources/eventsLogger/messageDelete.js b/resources/eventsLogger/messageDelete.js index c216aa7..3846414 100644 --- a/resources/eventsLogger/messageDelete.js +++ b/resources/eventsLogger/messageDelete.js @@ -11,11 +11,11 @@ const getColor = require("../getColor"); */ module.exports = async (msg) => { if (msg.partial) return; - const ignored = msg.guild.eventChannels.mesDel?.ignore?.includes(msg.channel.id) ?? false; + const ignored = msg.guild.DB.settings.eventChannels.mesDel?.ignore.includes(msg.channel.id) ?? false; let check = false; - if (msg.channel.id === msg.guild.eventChannels?.mesDel?.channel && msg.author ? msg.author !== msg.client.user : false && ignored === false) check = true; - if (msg.guild.eventChannels?.mesDel?.channel !== msg.channel.id && ignored === false || check) { - const log = getChannel(msg, msg.guild.eventChannels.mesDel?.channel); + if (msg.channel.id === msg.guild.DB.settings.eventChannels.mesDel?.channel && msg.author ? msg.author !== msg.client.user : false && ignored === false) check = true; + if (msg.guild.DB.settings.eventChannels.mesDel?.channel !== msg.channel.id && ignored === false || check) { + const log = getChannel(msg, msg.guild.DB.settings.eventChannels.mesDel?.channel); if (!log || !msg.author) return; const emb = defaultEventLogEmbed(msg.guild); emb.setColor(getColor("yellow")) diff --git a/resources/eventsLogger/messageUpdate.js b/resources/eventsLogger/messageUpdate.js index f1ace32..5b1c2ad 100644 --- a/resources/eventsLogger/messageUpdate.js +++ b/resources/eventsLogger/messageUpdate.js @@ -14,11 +14,11 @@ module.exports = async (msgold, msgnew) => { if (msgnew.partial) msgnew = await msgnew.fetch(); if (msgnew.partial) return; if (msgnew.content === msgold.content) return; - const ignored = msgnew.guild.eventChannels.mesEd?.ignore?.includes(msgnew.channel.id) || false; + const ignored = msgnew.guild.DB.settings.eventChannels.mesEd?.ignore?.includes(msgnew.channel.id) || false; let check = false; - if (msgnew.channel.id === msgnew.guild.eventChannels?.mesEd?.channel && msgnew.author ? msgnew.author !== msgnew.client.user : false && ignored === false) check = true; - if (msgnew.guild.eventChannels?.mesEd?.channel !== msgnew.channel.id && ignored === false || check) { - const log = getChannel(msgnew, msgnew.guild.eventChannels.mesEd?.channel); + if (msgnew.channel.id === msgnew.guild.DB.settings.eventChannels.mesEd?.channel && msgnew.author ? msgnew.author !== msgnew.client.user : false && ignored === false) check = true; + if (msgnew.guild.DB.settings.eventChannels.mesEd?.channel !== msgnew.channel.id && ignored === false || check) { + const log = getChannel(msgnew, msgnew.guild.DB.settings.eventChannels.mesEd?.channel); if (!log || !msgnew.author) return; const emb = defaultEventLogEmbed(msgnew.guild); emb diff --git a/resources/functions.js b/resources/functions.js index 2c03ad4..589de8b 100644 --- a/resources/functions.js +++ b/resources/functions.js @@ -12,9 +12,9 @@ const { CommandoMessage, CommandoClient } = require('@iceprod/discord.js-command * @param {Error} theError - Catched error (error) * @param {CommandoMessage} msg - Message object (msg) * @param {CommandoClient} client - This client (this.client) - * @param {Boolean} sendTheError - Add error content to notify message (true | false) - * @param {String} errorMessage - Error message ("You don't have enough permission to use that command!") - * @param {Boolean} notify - Send error to user who ran the command + * @param {boolean} sendTheError - Add error content to notify message (true | false) + * @param {string} errorMessage - Error message ("You don't have enough permission to use that command!") + * @param {boolean} notify - Send error to user who ran the command */ async function errLog(theError, msg, client, sendTheError, errorMessage, notify) { if (!client && msg) client = msg.client; @@ -22,7 +22,8 @@ async function errLog(theError, msg, client, sendTheError, errorMessage, notify) let [ret, logThis, inLogChannel, sendErr] = [undefined, '', '', '']; if (msg instanceof Message) { // client.emit("commandError", msg.command, theError, msg); - logThis = `\`${msg.command?.name}\` (${msg.id}) ${msg.url} in ${msg.guild ? `**${msg.channel.name}** (${msg.channel.id}) of **${msg.guild.name}** (${msg.guild.id})` : `**DM**`} ran by **${msg.author.tag}** (${msg.author.id}) \n\n`; + logThis = `\`${msg.command?.name}\` (${msg.id}) ${msg.url} in ${msg.guild ? `**${msg.channel.name}**` + + ` (${msg.channel.id}) of **${msg.guild.name}** (${msg.guild.id})` : `**DM**`} ran by **${msg.author.tag}** (${msg.author.id}) \n\n`; if (errorMessage) { if (errorMessage.length > 0) { sendErr = sendErr + errorMessage + '\n'; @@ -30,13 +31,23 @@ async function errLog(theError, msg, client, sendTheError, errorMessage, notify) } } if (sendTheError) sendErr = sendErr + '```js\n' + theError.stack + '```'; - if (notify) ret = await msg.channel.send(sendErr.trim(), { split: true }).catch(noPerm(msg)); + if (notify) ret = await msg.channel.send(sendErr.trim(), { + split: { + maxLength: 2000, char: "\n", + append: '```', prepend: '```js\n' + } + }).catch(noPerm(msg).sfskdufsdgsd); } if (client) { inLogChannel = inLogChannel + '```js\n' + theError.stack + '```'; try { const sendAt = client.channels.cache.get(defaultErrorLogChannel); - sendAt.send(logThis + inLogChannel.trim() + timestampAt(client), { split: { maxLength: 2000, char: "\n", append: '```', prepend: '```js\n' } }); + sendAt.send(logThis + inLogChannel.trim() + timestampAt(client), { + split: { + maxLength: 2000, char: "\n", + append: '```', prepend: '```js\n' + } + }); } catch { return console.error("Can't log to error channel or not configured.", timestampAt() + ":", theError); } @@ -47,8 +58,8 @@ async function errLog(theError, msg, client, sendTheError, errorMessage, notify) /** * Get message object from the message channel or provided channel * @param {Message} msg - Message object (msg) - * @param {String} MainID - Message ID | Channel_[mention|ID] | Message link - * @param {String} SecondID - Message ID + * @param {string} MainID - Message ID | Channel_[mention|ID] | Message link + * @param {string} SecondID - Message ID * @returns {Promise} Message object | undefined */ async function getChannelMessage(msg, MainID, SecondID) { @@ -82,7 +93,7 @@ function execCB(error, stdout, stderr) { /** * Command usage logger * @param {CommandoMessage} msg - * @param {String} addition + * @param {string} addition */ async function ranLog(msg, addition) { if (typeof addition != "string") return console.log(`[RANLOG] Not a string:`, addition); @@ -95,8 +106,8 @@ async function ranLog(msg, addition) { if (addition && addition.length > 0) embed.setDescription(addition.slice(0, ifCode && addSplit[0]?.[0].length > 0 ? 2044 : 2048) + (ifCode && addSplit[0]?.[0].length > 0 ? "```" : "")); if (addSplit[0]?.[0].length > 0) for (const add of addSplit) embed.addField("​", "```js\n" + add.join(",") + (embed.fields.length < (addSplit.length - 1) ? ",```" : "")); embed.setFooter(timestampAt(msg.client), msg.guild?.iconURL({ format: "png", size: 128, dynamic: true })); - if (msg.guild) embed.addField("Guild", `\`${msg.guild?.name}\`\n(${msg.guild?.id})`, true); - embed.addField("Channel", (msg.guild ? `<#${msg.channel.id}>\n\`${msg.channel?.name}\`` : `**DM**\n\`${msg.channel.recipient.tag}\``) + `\n(${msg.channel.id})`, true) + if (msg.guild) embed.addField("Guild", `\`${msg.guild.name}\`\n(${msg.guild.id})`, true); + embed.addField("Channel", (msg.guild ? `<#${msg.channel.id}>\n\`${msg.channel.name}\`` : `**DM**\n\`${msg.channel.recipient.tag}\``) + `\n(${msg.channel.id})`, true) .addField("User", `<@!${msg.author.id}>`, true); trySend(msg.client, channel, { embed: embed }); } @@ -105,10 +116,10 @@ async function ranLog(msg, addition) { * Notify when more than one member found when looking in the member list * @param {Message} msg - Message object * @param {GuildMember[]} arr - Test array - * @param {String} key - Keyword - * @param {Number} max - Max length - * @param {Boolean} withID - Include user_ID - * @returns {String} + * @param {string} key - Keyword + * @param {number} max - Max length + * @param {boolean} withID - Include user_ID + * @returns {string} */ function multipleMembersFound(msg, arr, key, max = 4, withID) { if (msg && arr.length > 1) { @@ -136,7 +147,7 @@ function multipleMembersFound(msg, arr, key, max = 4, withID) { /** * Get member object with RegExp * @param {Message | GuildMember | Guild} base Object of the guild being searched - * @param {String} key Keyword + * @param {string} key Keyword * @returns {GuildMember[]} Member object found */ function findMemberRegEx(base, key) { @@ -161,7 +172,7 @@ async function noPerm(msg) { * @param {CommandoClient} client - (this.client) * @param {Message | String | TextChannel | DMChannel} msgOrChannel Message object | channel_ID * @param {MessageOptions} content - ({content:content,optionblabla}) - * @param {Boolean} checkAd - Check source for Discord invite link (true) + * @param {boolean} checkAd - Check source for Discord invite link (true) * @returns {Promise} Sent message object */ async function trySend(client, msgOrChannel, content, checkAd = true) { @@ -206,7 +217,7 @@ async function tryDelete(msg) { /** * React message * @param {Message} msg - Message to react (msg) - * @param {String} reaction - Emote ("name:ID") + * @param {string} reaction - Emote ("name:ID") */ async function tryReact(msg, reaction) { if (!msg || !reaction || reaction.length === 0) return; @@ -219,7 +230,7 @@ async function tryReact(msg, reaction) { /** * Check message's content for ads - * @param {String} content - Content to check + * @param {string} content - Content to check */ function adCheck(content) { if (content.length > 5) { @@ -232,14 +243,14 @@ function adCheck(content) { /** * Make default image embed * @param {Message | GuildMember} msg - * @param {String} image + * @param {string} image * @param {GuildMember | User} author - * @param {String} title - * @param {String} footerQuote + * @param {string} title + * @param {string} footerQuote * @returns {MessageEmbed} */ function defaultImageEmbed(msg, image, title, footerQuote) { - if (!footerQuote) footerQuote = (msg.guild ?? msg.author).defaultEmbed?.footerQuote || ""; + if (!footerQuote) footerQuote = (msg.guild ?? msg.author).DB.defaultEmbed?.footerQuote || ""; const emb = new MessageEmbed() .setImage(image) .setColor(msg.guild ? getColor(msg.member?.displayColor) : randomColors[Math.floor(Math.random() * randomColors.length)]) @@ -250,8 +261,8 @@ function defaultImageEmbed(msg, image, title, footerQuote) { /** * Return clean ID of provided key - * @param {String} key - Mention | Channel Name | Username | Rolename - * @returns {String} Clean ID + * @param {string} key - Mention | Channel Name | Username | Rolename + * @returns {string} Clean ID */ function cleanMentionID(key) { if (!key || (typeof key !== "string")) return; @@ -266,7 +277,7 @@ function cleanMentionID(key) { /** * Get channel object wit RegExp * @param {Message | GuildMember | Guild} msg Object of the guild being searched - * @param {String} name Keyword + * @param {string} name Keyword * @param {ChannelType[]} exclude Exclude channel type * @returns {GuildChannel[]} Channels object found */ @@ -285,7 +296,7 @@ function findChannelRegEx(msg, name, exclude) { /** * Get role object with RegExp * @param {Message | GuildMember | Guild} msg Object of the guild being searched - * @param {String} name Keyword + * @param {string} name Keyword * @returns {Role[]} Roles object found */ function findRoleRegEx(msg, name) { @@ -298,10 +309,10 @@ function findRoleRegEx(msg, name) { * Notify when more than one channel found when looking in the channel list * @param {Message} msg - Message object * @param {GuildChannel[]} arr - Test array - * @param {String} key - Keyword - * @param {Number} max - Max length - * @param {Boolean} withID - Include channel_ID - * @returns {String} + * @param {string} key - Keyword + * @param {number} max - Max length + * @param {boolean} withID - Include channel_ID + * @returns {string} */ function multipleChannelsFound(msg, arr, key, max = 4, withID) { if (msg && arr.length > 1) { @@ -329,10 +340,10 @@ function multipleChannelsFound(msg, arr, key, max = 4, withID) { * Notify when more than one role found when looking in the role list * @param {Message} msg - Message object * @param {Role[]} arr - Test array - * @param {String} key - Keyword - * @param {Number} max - Max length - * @param {Boolean} withID - Include role_ID - * @returns {String} + * @param {string} key - Keyword + * @param {number} max - Max length + * @param {boolean} withID - Include role_ID + * @returns {string} */ function multipleRolesFound(msg, arr, key, max = 4, withID) { if (msg && arr.length > 1) { @@ -359,7 +370,7 @@ function multipleRolesFound(msg, arr, key, max = 4, withID) { /** * Standard * @param {Message | Guild} msg - Message object - * @param {String} key - Channel ID | Mention | Name + * @param {string} key - Channel ID | Mention | Name * @param {ChannelType[]} exclude - Exclude channel type * @returns {GuildChannel | Channel} Channel object */ @@ -380,7 +391,7 @@ function getChannel(msg, key, exclude) { /** * Get guild member using name || tag || ID * @param {Guild} guild - * @param {String} key - name || tag || ID + * @param {string} key - name || tag || ID * @returns {GuildMember[]} */ function getMember(guild, key) { @@ -398,8 +409,8 @@ function getMember(guild, key) { /** * Compare 2 different timestamp - * @param {Number} compare - Number to compare - * @returns {Number} Result + * @param {number} compare - Number to compare + * @returns {number} Result */ function getUTCComparison(compare) { return compare - new Date().valueOf(); @@ -412,17 +423,20 @@ function getUTCComparison(compare) { */ function defaultEventLogEmbed(guild) { if (!guild) return; + const C = guild.member(guild.client.user); return new MessageEmbed() + .setColor(getColor(C.displayColor)) .setAuthor(guild.name) - .setFooter((guild.defaultEmbed?.footerQuote ? guild.defaultEmbed.footerQuote : ""), guild.iconURL({ format: "png", size: 128, dynamic: true })) + .setFooter((guild.DB?.settings?.defaultEmbed?.footerQuote ? + guild.DB.settings.defaultEmbed.footerQuote : ""), guild.iconURL({ format: "png", size: 128, dynamic: true })) .setTimestamp(new Date()); } /** * Split on Length * @param {Array} arr - * @param {Number} maxLength - Max character length per split - * @param {String} joiner + * @param {number} maxLength - Max character length per split + * @param {string} joiner * @returns {Array} */ function splitOnLength(arr, maxLength, joiner = "\n") { @@ -442,29 +456,32 @@ function splitOnLength(arr, maxLength, joiner = "\n") { /** * Parse string (split ",") - * @param {String} content + * @param {string} content * @returns {String[]} */ function parseComa(content) { - return content.trim().split(/(? setTimeout(() => r(), ms)) } +const defaultSplitMessage = { maxLength: 2000, char: ",", append: ',```', prepend: '```js\n' }; module.exports = { cleanMentionID, defaultEventLogEmbed, multipleMembersFound, multipleRolesFound, multipleChannelsFound, @@ -501,5 +519,5 @@ module.exports = { trySend, tryDelete, tryReact, adCheck, defaultImageEmbed, getChannel, splitOnLength, parseComa, parseDoubleDash, getMember, - parseDash, reValidURL, getUser, getRole, wait + parseDash, reValidURL, getUser, getRole, wait, defaultSplitMessage } \ No newline at end of file diff --git a/resources/getColor.js b/resources/getColor.js index 3ce2a08..ff36bab 100644 --- a/resources/getColor.js +++ b/resources/getColor.js @@ -29,28 +29,108 @@ module.exports = function getColor(name) { return '00ff00'; case 'red': return 'ff0000'; - case 'pink': - return 'ff94f2'; case 'yellow': return 'f1e40f'; case 'orange': return 'ff8c00'; - case 'brown': - return 'a0522d'; case 'blue': return 3447003; case 'light blue': return '0fffff'; - case 'cyan': - return '0fffff'; - case 'purple': - return '803c9d'; - case 'peach': - return 'faa775'; case 'black': return '000000'; case 'white': return 16777214; + case 'cyan': + return '0fffff'; + case 'Blue': + return '00bfff'; + case 'azure': + return '007fff'; + case 'sapphire': + return '0f52ba'; + case 'ultramarine': + return '120a8f'; + case 'wheat': + return 'f5deb3'; + case 'tan': + return 'd2b48c'; + case 'rosybrown': + return 'bc8f8f'; + case 'brown': + return 'a0522d'; + case 'umber': + return '826644'; + case 'tea green': + return 'd0f0c0'; + case 'pale green': + return '98fb98'; + case 'erin': + return '00ff40'; + case 'mantis': + return '74c365'; + case 'dark green': + return '355e3b'; + case 'khaki': + return 'c3b091'; + case 'peach': + return 'faa775'; + case 'coral': + return 'ff7f50'; + case 'orange': + return 'ff8c00'; + case 'persimmon': + return 'b45e06'; + case 'mimi pink': + return 'ffdae9'; + case 'amaranth': + return 'f19cbb'; + case 'pink purple': + return 'e62aed'; + case 'red violet': + return 'c71585'; + case 'raspberry': + return 'e30b5c'; + case 'thistle': + return 'd8bfd8'; + case 'orchid': + return 'da70d6'; + case 'amethyst': + return '9966cc'; + case 'purple': + return '803c9d'; + case 'eminence': + return '6c3082'; + case 'misty roses': + return 'ffe4e1'; + case 'pink': + return 'ffc0cb'; + case 'bright pink': + return 'ff91a4'; + case 'crimson': + return 'dc143c'; + case 'dark Red': + return '8b0000'; + case 'champagne': + return 'f7e7ce'; + case 'cream': + return 'fffdd0'; + case 'gold': + return 'ffd700'; + case 'dark yellow': + return '999900'; + case 'olive': + return '808000'; + case 'silver': + return 'c0c0c0'; + case 'gray': + return 'a9a9a9'; + case 'dark gray': + return 808080; + case 'dim gray': + return 696969; + case 'midnight': + return '000001'; default: { if (/\D/.test(name)) { if (name.startsWith("#")) name = name.slice(1); diff --git a/resources/structures.js b/resources/structures.js index 2f7a97e..a48a419 100644 --- a/resources/structures.js +++ b/resources/structures.js @@ -1,8 +1,9 @@ 'use strict'; -const { Structures } = require("discord.js"), +const { Structures, Guild, GuildMember } = require("discord.js"), { database } = require("../database/mongo"), { errLog } = require("./functions"); +const { DateTime, Duration } = require("luxon"); Structures.extend("Guild", u => { return class Guild extends u { @@ -11,22 +12,39 @@ Structures.extend("Guild", u => { } async dbLoad() { - return database.collection("Guild").findOne({ document: this.id }, (e, r) => { + return database.collection("Guild").findOne({ document: this.id }).then((r, e) => { if (e) return errLog(e, null, this.client); - return this.DB = r || {}; + r = r?.DB; + if (!r) r = {}; + if (!r.settings) r.settings = {}; + if (!r.moderation) r.moderation = {}; + if (!r.settings.eventChannels) r.settings.eventChannels = {}; + if (!r.moderation.settings) r.moderation.settings = {}; + if (!r.moderation.infractions) r.moderation.infractions = []; + return this.DB = r; }); } async setDb(Db, empty = false) { if (typeof Db !== "object") throw new TypeError("Expected 'object'; Got '" + typeof Db + "'"); - if (Db === {} && !empty) throw new Error("Empty!"); - return database.collection("Guild").updateOne({ document: this.id }, { $set: Db, $setOnInsert: { document: this.id } }, - { upsert: true }, (e) => { + if (Db === {} && !empty) throw new TypeError("Empty!"); + return database.collection("Guild").updateOne({ document: this.id }, { $set: { DB: Db }, $setOnInsert: { document: this.id } }, + { upsert: true }).then((r, e) => { if (e) return errLog(e, null, this.client); return this.DB = Db; }); } + /** + * @param {object} data - Data to set + * @returns + */ + async refreshDb(data) { + if (!this.DB) await this.dbLoad(); + if (data) for (const D in data) if (this.DB[D]) this.DB[D] = data[D]; + return this.setDb(this.DB); + } + /** * Get user infractions * @param {String} get - User ID @@ -36,7 +54,7 @@ Structures.extend("Guild", u => { try { if (!this.DB) await this.dbLoad(); let found = []; - if (this.DB.moderation?.infractions?.length > 0) { + if (this.DB.moderation.infractions.length > 0) { for (const inf of this.DB.moderation.infractions) { for (const user of inf.by) { if (user.id === get) { @@ -53,7 +71,6 @@ Structures.extend("Guild", u => { async addInfraction(add) { try { if (!this.DB) await this.dbLoad(); - if (!this.DB.moderation?.infractions) this.DB.moderation.infractions = []; this.DB.moderation.infractions.push(add); return this.setDb(this.DB); } catch (e) { } @@ -93,23 +110,38 @@ Structures.extend("User", u => { } async dbLoad() { - return database.collection("User").findOne({ document: this.id }, (e, r) => { + return database.collection("User").findOne({ document: this.id }).then((r, e) => { if (e) return errLog(e, null, this.client); - if (!r.F) r.F = "F"; - return this.DB = r || {}; + r = r?.DB; + if (!r) r = {}; + if (!r.F) r.F = "<:pepewhysobLife:853237646666891274>"; + if (!r.cachedAvatarURL) r.cachedAvatarURL = this.displayAvatarURL({ format: "png", size: 4096, dynamic: true }); + if (!r.mutedIn) r.mutedIn = []; + if (!r.interactions) r.interactions = {}; + return this.DB = r; }); } async setDb(Db, empty = false) { if (typeof Db !== "object") throw new TypeError("Expected 'object'; Got '" + typeof Db + "'"); - if (Db === {} && !empty) throw new Error("Empty!"); - return database.collection("User").updateOne({ document: this.id }, { $set: Db, $setOnInsert: { document: this.id } }, - { upsert: true }, (e) => { + if (Db === {} && !empty) throw new TypeError("Empty!"); + return database.collection("User").updateOne({ document: this.id }, { $set: { DB: Db }, $setOnInsert: { document: this.id } }, + { upsert: true }).then((r, e) => { if (e) return errLog(e, null, this.client); return this.DB = Db; }); } + /** + * @param {object} data - Data to set + * @returns + */ + async refreshDb(data) { + if (!this.DB) await this.dbLoad(); + if (data) for (const D in data) if (this.DB[D]) this.DB[D] = data[D]; + return this.setDb(this.DB); + } + async setF(string) { if (!this.DB) await this.dbLoad(); this.DB.F = string; @@ -133,6 +165,106 @@ Structures.extend("User", u => { this.DB.defaultEmbed = set; return this.setDb(this.DB); } + + /** + * @param {string} guildID + * @param {{state: boolean, duration: object, infraction: number}} state + * @returns {number} + */ + pushMutedIn(guildID, { state: state = true, duration: duration, infraction: infraction }) { + const push = { + guildID: guildID, + state: state, + duration: duration, + infraction: infraction + } + return this.DB.mutedIn.push(push); + } + + removeMutedIn(guildID) { + return this.DB.mutedIn = this.DB.mutedIn.filter((r) => r.guildID !== guildID); + } + + /** + * @param {string} guildID + * @returns {{data: {state: boolean, duration: duration, infraction: number, guildID: string}, index: number, count: number}} + */ + getMutedIn(guildID) { + let index = -1; + const hmm = this.DB.mutedIn.filter((r, i) => { + if (r.guildID === guildID) { + index = i; + return true; + } else return false; + }), + data = hmm?.[0], + count = hmm?.length || 0; + const D = { data, index, count }; + console.log(D); + return D; + } + + /** + * @param {string} guildID + * @param {{state: boolean, duration: object, infraction: number}} state + * @returns {{state: boolean, duration: object, infraction: number, guildID: string}} + */ + updateMutedIn(guildID, { state: state = true, duration: duration, infraction: infraction }) { + const I = this.getMutedIn(guildID)?.index; + if (I === -1) return false; + const push = { + guildID: guildID, + state: state, + duration: duration, + infraction: infraction + }; + this.DB.mutedIn[I] = push; + return this.DB.mutedIn[I]; + } + + /** + * @param {Guild} guild + * @param {string} reason + * @param {{duration: object, saveTakenRoles: boolean, infraction: number, moderator: GuildMember}} data + */ + async mute(guild, data, reason) { + if (!guild || !(guild instanceof Guild)) throw new TypeError("Guild is " + typeof guild); + if (!data?.infraction) throw new Error("Missing infraction id"); + if (!this.DB) await this.dbLoad(); + if (!guild.DB) await guild.dbLoad(); + const MEM = guild.member(this); + if (MEM) { + if (data.moderator.roles.highest.position < MEM.roles.highest.position) throw new Error("You can't mute someone with higher position than you <:nekokekLife:852865942530949160>"); + return MEM.mute(data, reason) + } else { + const MC = this.getMutedIn(guild.id); + if (MC?.index > -1) { + if (data.duration && MC.data) { + const ret = this.updateMutedIn(guild.id, { state: true, duration: data.duration, infraction: MC.data.infraction }); + this.setDb(this.DB); + return ret; + }; + throw new Error("This member is already muted. Provide `[duration]` to set new duration."); + } + const ret = this.pushMutedIn(guild.id, { state: true, duration: data.duration, infraction: data.infraction }); + this.setDb(this.DB); + return ret; + }; + } + + async unmute(guild, moderator, reason) { + if (!guild || !(guild instanceof Guild)) throw new TypeError("Guild is " + typeof guild); + if (!this.DB) await this.dbLoad(); + const MEM = guild.member(this); + if (MEM) { + if (moderator.roles.highest.position < MEM.roles.highest.position) throw new Error("You can't mute someone with higher position than you <:nekokekLife:852865942530949160>"); + return MEM.unmute(reason) + } else { + const ret = this.removeMutedIn(guild.id); + this.setDb(this.DB); + return ret; + } + } } }); @@ -188,68 +320,108 @@ Structures.extend("GuildMember", u => { } async dbLoad() { - return database.collection("GuildMember").findOne({ document: this.id }, (e, r) => { + return database.collection("GuildMember").findOne({ document: this.id }).then((r, e) => { if (e) return errLog(e, null, this.client); - return this.DB = r || {}; + r = r?.DB; + if (!r) r = {}; + return this.DB = r; }); } async setDb(Db, empty = false) { if (typeof Db !== "object") throw new TypeError("Expected 'object'; Got '" + typeof Db + "'"); - if (Db === {} && !empty) throw new Error("Empty!"); - return database.collection("GuildMember").updateOne({ document: this.id }, { $set: Db, $setOnInsert: { document: this.id } }, - { upsert: true }, (e) => { + if (Db === {} && !empty) throw new TypeError("Empty!"); + return database.collection("GuildMember").updateOne({ document: this.id }, { $set: { DB: Db }, $setOnInsert: { document: this.id } }, + { upsert: true }).then((r, e) => { if (e) return errLog(e, null, this.client); return this.DB = Db; }); } + /** + * @param {object} data - Data to set + * @returns + */ + async refreshDb(data) { + if (!this.DB) await this.dbLoad(); + if (data) for (const D in data) if (this.DB[D]) this.DB[D] = data[D]; + return this.setDb(this.DB); + } + async infractions() { return this.guild.getInfractions(this.id); } /** - * @param {Date | number} until - * @returns + * @param {{duration: object, saveTakenRoles: boolean, infraction: number}} data + * @param {string} reason + * @returns */ - async mute(until) { + async mute(data, reason) { if (!this.DB) await this.dbLoad(); + if (!this.user.DB) await this.user.dbLoad(); if (!this.guild.DB) await this.guild.dbLoad(); - this.DB.takenRoles = this.roles.cache.map(r => r.id); + if (!data) throw new Error("Missing infraction id"); + + const MC = this.user.getMutedIn(this.guild.id); + if (!data.infraction && !MC?.data) throw new Error("Missing infraction id"); + + if (MC?.index > -1) { + if (data.duration && MC.data) { + const ret = this.user.updateMutedIn(this.guild.id, { state: true, duration: data.duration, infraction: MC.data.infraction }); + this.user.setDb(this.user.DB); + return ret; + } + throw new Error("This member is already muted. Provide `[duration]` to set new duration."); + } + if (data.saveTakenRoles === undefined) data.saveTakenRoles = true; + const ROLES = this.roles.cache.filter((r) => !r.managed).map(r => r.id); + if (data.saveTakenRoles && ROLES?.length > 0) { + console.log("populating takenRoles M"); + this.DB.takenRoles = ROLES; + } this.DB.muteRole = this.guild.DB.moderation.settings.mute.role; - if ((typeof until) === "number") until = new Date(until); + try { - const RM = await this.roles.remove(this.DB.takenRoles); - await RM.roles.add(this.DB.muteRole); - this.DB.muted = { + if (ROLES?.length > 0) await this.roles.remove(ROLES, reason); + await this.roles.add(this.DB.muteRole, reason); + const ret = this.user.pushMutedIn(this.guild.id, { state: true, - until: until - }; - return this.setDb(this.DB); + duration: data.duration, + infraction: data.infraction + }); + this.setDb(this.DB); + this.user.setDb(this.user.DB); + return ret; } catch (e) { - if (this.DB.takenRoles?.length > 0) this.roles.add(this.DB.takenRoles).catch(() => { }); + if (this.DB.takenRoles?.length > 0) await this.roles.add(this.DB.takenRoles, reason).catch(() => { }); + if (this.DB.muteRole) await this.roles.remove(this.DB.muteRole, reason).catch(() => { }); + this.user.removeMutedIn(this.guild.id); + console.log("clear takenRoles M"); this.DB.takenRoles = []; this.DB.muteRole = undefined; - this.DB.muted = { state: false }; - const R = await this.setDb(this.DB); - if (e) throw e; - return R; + throw e; } } - async unmute() { + async unmute(reason) { if (!this.DB) await this.dbLoad(); + if (!this.user.DB) await this.user.dbLoad(); try { - const RM = await this.roles.add(this.DB.takenRoles); - await RM.roles.remove(this.DB.muteRole); + if (this.DB.takenRoles.length > 0) await this.roles.add(this.DB.takenRoles, reason); + if (this.DB.muteRole) await this.roles.remove(this.DB.muteRole, reason); + const ret = this.user.removeMutedIn(this.guild.id); + console.log("clear takenRoles UM"); this.DB.takenRoles = []; this.DB.muteRole = undefined; - this.DB.muted = { state: false }; - return this.setDb(this.DB); + this.setDb(this.DB); + this.user.setDb(this.user.DB) + return ret; } catch (e) { - if (e) throw e; - return; + throw e; } } + + get isAdmin() { if (!this.client.owners.includes(this.user)) return this.hasPermission("ADMINISTRATOR"); else return true } } }); \ No newline at end of file