diff --git a/Main.js b/Main.js index 9439b33..9405cf4 100644 --- a/Main.js +++ b/Main.js @@ -114,7 +114,11 @@ client.on("guildBanRemove", async (GUILD, USER) => { client.on("guildUpdate", async (oldGuild, newGuild) => { lgr.guildUpdate(oldGuild, newGuild); -}) +}); + +client.on("channelUpdate", async (oldChannel, newChannel) => { + lgr.channelUpdate(oldChannel, newChannel); +}); client.on("messageDelete", async (msg) => { if (msg.author && !msg.author.DB) await msg.author.dbLoad(); diff --git a/cmds/moderation/eventlog.js b/cmds/moderation/eventlog.js index 0cfc6fe..dd6bdf1 100644 --- a/cmds/moderation/eventlog.js +++ b/cmds/moderation/eventlog.js @@ -7,7 +7,7 @@ const ARGS_TEXT = `**Set configuration using**` + `\`--[e|d]\` Message [edit|delete],\n\` -i\` Ignore channel,\n\`--j\` Member Join,\n\`--l\` Member Leave,\n` + `\`--p\` Member Profile Update,\n\`--mr\` Member Roles Update,\n\`--b\` Ban,\n\`--u\` Unban,\n` + `\`--g\` Server Update,\n\`--r\` Role Update,\n` + - `\`--c\` Channel Update,\n\`--em\` Emote Update,\n\`--i\` Server Invites.\n` + + `\`--c\` Channel Update - **[REQUIRE \`VIEW_AUDIT_LOG\`]**,\n\`--em\` Emote Update,\n\`--i\` Server Invites.\n` + `\n**Examples:**\n\`\`\`\n--e #message-edited-log -i #admin, #staff --p #member-profile --b #ban-logs\`\`\` \`\`\`\n--rm --e --p --b\`\`\``; module.exports = class eventlog extends commando.Command { diff --git a/resources/eventsLogger/channelUpdate.js b/resources/eventsLogger/channelUpdate.js new file mode 100644 index 0000000..1bff680 --- /dev/null +++ b/resources/eventsLogger/channelUpdate.js @@ -0,0 +1,161 @@ +'use strict'; + +const { GuildChannel, Guild, GuildAuditLogsEntry } = require("discord.js"); +const { defaultEventLogEmbed, changed, trySend, wait } = require("../functions"); +const getColor = require("../getColor"); + +/** + * @param {GuildChannel} oldChannel + * @param {GuildChannel} newChannel + */ +module.exports = async (oldChannel, newChannel) => { + await wait(2000); + const dateNow = new Date(); + if (!newChannel.guild.DB) await newChannel.guild.dbLoad(); + if (!newChannel.guild.DB.eventChannels?.guild) return; + const logChannel = newChannel.guild.channels.cache.get(newChannel.guild.DB.eventChannels.guild); + if (!logChannel) return; + + let audit; + + const diff = newChannel.permissionOverwrites.difference(oldChannel.permissionOverwrites); + let overwritesUpdates = []; + + for (const [key, val] of newChannel.permissionOverwrites) { + const oldOverwrites = oldChannel.permissionOverwrites.get(key); + if (!oldOverwrites) continue; + if (oldOverwrites?.allow.bitfield !== val.allow.bitfield || + oldOverwrites?.deny.bitfield !== val.deny.bitfield) { + overwritesUpdates.push({ new: val, old: oldOverwrites }); + console.log; // BREAKPOINT + } + }; + + const emb = defaultEventLogEmbed(newChannel.guild); + let fetchAudit, fetchAR; + + if (overwritesUpdates.length) { + for (const overwrite of overwritesUpdates) { + const oldAllow = overwrite.old?.allow.serialize(), + oldDeny = overwrite.old?.deny.serialize(), + newAllow = overwrite.new.allow.serialize(), + newDeny = overwrite.new.deny.serialize(), + allowDiff = changed(oldAllow, newAllow), + denyDiff = changed(oldDeny, newDeny); + let allowBefore = [], allowAfter = [], denyBefore = [], denyAfter = []; + for (const u in allowDiff.oldObj) { + if (!allowDiff.oldObj[u]) continue; + allowBefore.push(u); + }; + for (const u in allowDiff.newObj) { + if (!allowDiff.newObj[u]) continue; + allowAfter.push(u); + }; + for (const u in denyDiff.oldObj) { + if (!denyDiff.oldObj[u]) continue; + denyBefore.push(u); + }; + for (const u in denyDiff.newObj) { + if (!denyDiff.newObj[u]) continue; + denyAfter.push(u); + }; + emb.addField(`Changed override`, `**For ${overwrite.new.type}** ${overwrite.new.type === "member" ? + `<@${overwrite.new.id}> (${overwrite.new.id})` : overwrite.new.id === newChannel.guild.id ? "@everyone" : `<@&${overwrite.new.id}> (${overwrite.new.id})` + }\n` + "**Approved before:**```js\n" + + (allowBefore.join(", ") || "NONE") + "```**Currently approved:**```js\n" + + (allowAfter.join(", ") || "NONE") + "```**Denied before:**```js\n" + + (denyBefore.join(", ") || "NONE") + "```**Currently denied:**```js\n" + + (denyAfter.join(", ") || "NONE") + "```"); + console.log; // BREAKPOINT + } + }; + + if (diff.size) { + for (const [key, val] of diff) { + const removed = oldChannel.permissionOverwrites.get(key); + const added = newChannel.permissionOverwrites.get(key); + if (!newChannel.guild.roles.cache.get((removed || added).id)) continue; + if (removed) { + if (!fetchAR) fetchAR = "R"; + const allow = removed.allow.serialize(), deny = removed.deny.serialize(); + let all = [], den = []; + for (const K in allow) { + if (!allow[K]) continue; + all.push(K); + }; + for (const K in deny) { + if (!deny[K]) continue; + den.push(K); + }; + emb.addField(`Removed override`, `**For ${removed.type}** ${removed.type === "member" ? + `<@${removed.id}> (${removed.id})` : removed.id === newChannel.guild.id ? "@everyone" : `<@&${removed.id}> (${removed.id})`}\n` + + "**Approved:**```js\n" + (all.join(", ") || "NONE") + "```" + + "**Denied:**```js\n" + (den.join(", ") || "NONE") + "```"); + console.log; // BREAKPOINT + } else if (added) { + if (!fetchAR) fetchAR = "A"; + const allow = added.allow.serialize(), deny = added.deny.serialize(); + let all = [], den = []; + for (const K in allow) { + if (!allow[K]) continue; + all.push(K); + }; + for (const K in deny) { + if (!deny[K]) continue; + den.push(K); + }; + emb.addField(`Added override`, `**For ${added.type}** ${added.type === "member" ? + `<@${added.id}> (${added.id})` : added.id === newChannel.guild.id ? "@everyone" : `<@&${added.id}> (${added.id})`}\n` + + "**Approved:**```js\n" + (all.join(", ") || "NONE") + "```" + + "**Denied:**```js\n" + (den.join(", ") || "NONE") + "```"); + console.log; // BREAKPOINT + } + } + }; + + if (newChannel.guild.me.hasPermission("VIEW_AUDIT_LOG")) { + if (!audit && overwritesUpdates.length) { + audit = await getAudit(newChannel.guild, dateNow, newChannel.id, { type: "CHANNEL_OVERWRITE_UPDATE" }); + }; + if (!audit && fetchAudit) { + audit = await getAudit(newChannel.guild, dateNow, newChannel.id, { type: "CHANNEL_UPDATE" }); + }; + if (!audit && fetchAR) { + /** + * @type {import("discord.js").GuildAuditLogsFetchOptions} + */ + let opt = {}; + switch (fetchAR) { + case "A": opt.type = "CHANNEL_OVERWRITE_CREATE"; break; + case "R": opt.type = "CHANNEL_OVERWRITE_DELETE"; break; + }; + audit = await getAudit(newChannel.guild, dateNow, newChannel.id, opt); + }; + }; + + emb.setTitle("Channel `" + newChannel.name + "` updated" + (audit?.executor ? ` by ${audit.executor.bot ? "`[BOT]` " : ""}\`${audit.executor.tag}\`` : "")) + .setColor(getColor("cyan")) + .addField("Channel", `<#${newChannel.id}>\n(${newChannel.id})`, true); + if (emb.fields.length < 2) return; + if (audit?.executor) { + emb.setAuthor(emb.author.name, audit.executor.displayAvatarURL({ size: 128, format: "png", dynamic: true })) + .addField("Administrator", `<@${audit.executor.id}>\n(${audit.executor.id})`, true); + if (audit.executor.bot) emb.addField("​", audit.reason || "No reason provided"); + } + console.log; // BREAKPOINT + return trySend(newChannel.client, logChannel, emb); +}; + +/** + * @param {Guild} guild Get audit from + * @param {Date} dateNow + * @param {string} id Target ID + * @param {import("discord.js").GuildAuditLogsFetchOptions} option + * @returns {Promise} + */ +async function getAudit(guild, dateNow, id, option, filter = (value, key, collection) => + value.target.id === id && (dateNow.valueOf() - value.createdTimestamp) < 60000) { + const col = await guild.fetchAuditLogs(option); + const fil = col.entries.filter(filter); + return fil.first(); +}; \ No newline at end of file diff --git a/resources/eventsLogger/guildBanAdd.js b/resources/eventsLogger/guildBanAdd.js index b475955..aba10ca 100644 --- a/resources/eventsLogger/guildBanAdd.js +++ b/resources/eventsLogger/guildBanAdd.js @@ -26,7 +26,7 @@ module.exports = async (GUILD, USER) => { emb.setDescription(audit?.reason || "No reason provided"); } else emb.setDescription("Unknown reason"); - emb.setTitle(`\`${USER.tag}\` banned` + (audit?.executor ? ` by \`${audit.executor.tag}\`` : "")) + emb.setTitle(`${USER.bot ? "`[BOT]` " : ""}\`${USER.tag}\` banned` + (audit?.executor ? ` by ${audit.executor.bot ? "`[BOT]` " : ""}\`${audit.executor.tag}\`` : "")) .setColor(getColor("red")) .setThumbnail(USER.displayAvatarURL({ size: 4096, format: "png", dynamic: true })) .addField("User", `<@${USER.id}>\n(${USER.id})`); diff --git a/resources/eventsLogger/guildBanRemove.js b/resources/eventsLogger/guildBanRemove.js index b577aa2..4458d73 100644 --- a/resources/eventsLogger/guildBanRemove.js +++ b/resources/eventsLogger/guildBanRemove.js @@ -26,7 +26,7 @@ module.exports = async (GUILD, USER) => { emb.setDescription(audit?.reason || "No reason provided"); } else emb.setDescription("Unknown reason"); - emb.setTitle(`\`${USER.tag}\` unbanned` + (audit?.executor ? ` by \`${audit.executor.tag}\`` : "")) + emb.setTitle(`${USER.bot ? "`[BOT]` " : ""}\`${USER.tag}\` unbanned` + (audit?.executor ? ` by ${audit.executor.bot ? "`[BOT]` " : ""}\`${audit.executor.tag}\`` : "")) .setColor(getColor("red")) .setThumbnail(USER.displayAvatarURL({ size: 4096, format: "png", dynamic: true })) .addField("User", `<@${USER.id}>\n(${USER.id})`); diff --git a/resources/eventsLogger/guildMemberAdd.js b/resources/eventsLogger/guildMemberAdd.js index fc8a51b..1b5aa60 100644 --- a/resources/eventsLogger/guildMemberAdd.js +++ b/resources/eventsLogger/guildMemberAdd.js @@ -27,7 +27,7 @@ module.exports = async (member) => { const emb = defaultEventLogEmbed(member.guild), INT2 = Interval.fromDateTimes(DateTime.fromJSDate(member.user.createdAt), DateTime.now()); emb - .setTitle("`" + member.user.tag + "` joined") + .setTitle((member.user.bot ? "`[BOT]` " : "") + "`" + member.user.tag + "` joined") .setThumbnail(member.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) .setColor(getColor("cyan")) .addField("Registered", defaultDateFormat(member.user.createdAt) + diff --git a/resources/eventsLogger/guildMemberRemove.js b/resources/eventsLogger/guildMemberRemove.js index a1869cf..080ae3d 100644 --- a/resources/eventsLogger/guildMemberRemove.js +++ b/resources/eventsLogger/guildMemberRemove.js @@ -25,7 +25,7 @@ module.exports = async (member) => { INT = Interval.fromDateTimes(LE, DateTime.now()); emb - .setTitle("`" + member.user.tag + "` left") + .setTitle((member.user.bot ? "`[BOT]` " : "") + "`" + member.user.tag + "` left") .setThumbnail(member.user.displayAvatarURL({ format: "png", size: 4096, dynamic: true })) .setColor(getColor("yellow")) .addField("Nick", "`" + member.displayName + "`") diff --git a/resources/eventsLogger/guildMemberUpdate.js b/resources/eventsLogger/guildMemberUpdate.js index 6558d42..51e2562 100644 --- a/resources/eventsLogger/guildMemberUpdate.js +++ b/resources/eventsLogger/guildMemberUpdate.js @@ -20,7 +20,7 @@ module.exports = async (memberold, membernew) => { }; return membernew.user.setDb("cachedAvatarURL", membernew.user.DB.cachedAvatarURL); } - let log, thumbMes = "", audit = {}, auditPerm, nullReason = false; + let log, thumbMes = "", audit = {}, auditPerm, nullReason = false, dateNow = new Date(); const emb = defaultEventLogEmbed(membernew.guild), oldT = memberold.toJSON().displayAvatarURL; const oldAV = membernew.user.DB.cachedAvatarURL || oldT; if (oldAV) thumbMes += "This embed's thumbnail is the user's old avatar.\n"; @@ -29,7 +29,7 @@ module.exports = async (memberold, membernew) => { if (membernew.guild.me.hasPermission("VIEW_AUDIT_LOG")) { // console.log("FETCH UPDATE LOG", membernew.user.tag); const the = (await membernew.guild.fetchAuditLogs({ limit: 1, type: "MEMBER_ROLE_UPDATE" })).entries.first(); - if (the.target.id === memberold.id) audit = the; + if (the.target.id === memberold.id && (dateNow.valueOf() - the.createdTimestamp) < 60000) audit = the; auditPerm = true; } if (membernew.roles.cache.size > memberold.roles.cache.size) { @@ -82,18 +82,24 @@ module.exports = async (memberold, membernew) => { } } // console.log(audit); - emb.setTitle("Profile `" + memberold.user.tag + "` updated" + - (audit?.executor ? ` by \`${audit.executor.tag}\`` : "")) - .setColor(getColor("blue")); + emb.setTitle("Profile " + (membernew.user.bot ? "`[BOT]` " : "") + "`" + membernew.user.tag + "` updated" + + (audit?.executor ? ` by ${audit.executor.bot ? "`[BOT]` " : ""}\`${audit.executor.tag}\`` : "")) + .setColor(getColor("blue")) + .addField("User", `<@${membernew.user.id}>\n(${membernew.user.id})`, true); if (emb.fields[0]?.name !== "Avatar") emb.setFooter(emb.footer.text || "​", NEWAV); if (!nullReason) { if (auditPerm) { emb.setDescription((audit?.reason || "No reason provided") + (emb.description ? "\n\n" + emb.description : "")); - } else emb.setDescription("Unknown reason\n\n" + (emb.description ? "\n\n" + emb.description : "")); - } + console.log; // BREAKPOINT + } else emb.setDescription("Unknown reason" + (emb.description ? "\n\n" + emb.description : "")); + }; + if (emb.length < 2) return; + if (audit.executor && audit.executor?.id !== membernew.user.id) { + emb.addField("Moderator", `<@${audit.executor.id}>\n(${audit.executor.id})`, true); + }; membernew.user.DB.cachedAvatarURL = NEWAV; membernew.user.setDb("cachedAvatarURL", membernew.user.DB.cachedAvatarURL); - if (!emb.fields || emb.fields.length === 0) return; + if (!emb.fields || emb.fields.length < 2) return; return trySend(membernew.client, log, emb); } diff --git a/resources/eventsLogger/guildUpdate.js b/resources/eventsLogger/guildUpdate.js index 57f44c9..c5f8256 100644 --- a/resources/eventsLogger/guildUpdate.js +++ b/resources/eventsLogger/guildUpdate.js @@ -16,9 +16,10 @@ module.exports = async (oldGuild, newGuild) => { if (newGuild.DB.eventChannels.guild) { const logChannel = newGuild.channels.cache.get(newGuild.DB.eventChannels.guild); if (!logChannel) return; - let audit = {}, newBanner, oldBanner, newSplash, oldSplash, newSDisc, oldSDisc; + let audit = {}, newBanner, oldBanner, newSplash, oldSplash, newSDisc, oldSDisc, auditPerm; const cached = newGuild.DB.cached; if (newGuild.me.hasPermission("VIEW_AUDIT_LOG")) { + auditPerm = true; audit = (await newGuild.fetchAuditLogs({ "limit": 1, "type": "GUILD_UPDATE" })).entries.first(); } else audit.reason = "Unknown reason"; @@ -148,7 +149,8 @@ module.exports = async (oldGuild, newGuild) => { if (audit.executor) { if (audit.executor.bot) emb.setDescription(audit.reason || "No reason provided"); - emb.setAuthor(emb.author.name, audit.executor.displayAvatarURL({ size: 128, format: "png", dynamic: true })); + emb.setAuthor(emb.author.name, audit.executor.displayAvatarURL({ size: 128, format: "png", dynamic: true })) + .addField("Administrator", `<@${audit.executor.id}>\n(${audit.executor.id})`); }; if (cached.iconURL && cached.iconURL !== newIcon) { @@ -167,7 +169,7 @@ module.exports = async (oldGuild, newGuild) => { await imageLogEmbed(logChannel, newEmb, "Splash Discovery", "This embed's thumbnail is the server's old splash discovery.\nThe image below is the server's new splash discovery.", oldSDisc, newSDisc); }; - if (!emb.fields.length) return; + if (audit.executor) if (emb.fields.length < 2) return; else if (!emb.fields.length) return; newGuild.updateCached("systemChannelID", newGuild.systemChannelID); newGuild.updateCached("iconURL", newIcon); @@ -180,7 +182,7 @@ module.exports = async (oldGuild, newGuild) => { async function imageLogEmbed(channel, embed, fieldName, fieldValue, thumbnail, image) { const newEmb = new MessageEmbed(embed); - newEmb.fields = []; + newEmb.fields = newEmb.fields.slice(newEmb.fields.length - 1); newEmb.addField(fieldName, fieldValue) .setThumbnail(thumbnail) .setImage(image); diff --git a/resources/eventsLogger/messageDelete.js b/resources/eventsLogger/messageDelete.js index ab017c7..0599483 100644 --- a/resources/eventsLogger/messageDelete.js +++ b/resources/eventsLogger/messageDelete.js @@ -34,7 +34,7 @@ module.exports = async (msg) => { console.log; // BREAKPOINT } emb.setColor(getColor("yellow")) - .setTitle((!msg.webhookID ? "Message " + msg.id : "Webhook " + msg.webhookID) + " deleted" + (audit?.executor ? ` by \`${audit.executor.tag}\`` : "")) + .setTitle((!msg.webhookID ? "Message " + msg.id : "Webhook " + msg.webhookID) + " deleted" + (audit?.executor ? ` by ${audit.executor.bot ? "`[BOT]` " : ""}\`${audit.executor.tag}\`` : "")) .setDescription(msg.content.length > 0 ? msg.content : "`[EMPTY]`") .setURL(msg.url) .setFooter(emb.footer.text || "​", msg.author.displayAvatarURL({ size: 128, format: "png", dynamic: true })); @@ -53,6 +53,7 @@ module.exports = async (msg) => { emb.addField("Author", `<@!${msg.author?.id}>\n\`${msg.author?.tag}\`\n(${msg.author?.id})`, true) .addField("Channel", `<#${msg.channel?.id}>\n\`${msg.channel?.name}\`\n(${msg.channel?.id})`, true); if (audit.executor?.bot) emb.addField("Reason", audit.reason || "No reason provided"); + if (audit.executor) emb.addField("Moderator", `<@${audit.executor.id}>\n(${audit.executor.id})`); return trySend(msg.client, log, emb); } } diff --git a/resources/functions.js b/resources/functions.js index 18ad116..d3d94a5 100644 --- a/resources/functions.js +++ b/resources/functions.js @@ -547,11 +547,29 @@ function defaultDateFormat(date) { } const defaultSplitMessage = { maxLength: 2000, char: ",", append: ',```', prepend: '```js\n' }; + +/** + * @param {object} oldObj + * @param {object} newObj + */ +function changed(oldObj, newObj) { + let diffOld = {}, diffNew = {}; + for (const key in oldObj) { + if (oldObj[key] === newObj[key]) continue; + diffOld[key] = oldObj[key]; + }; + for (const key in newObj) { + if (newObj[key] === oldObj[key]) continue; + diffNew[key] = newObj[key]; + }; + return { oldObj: diffOld, newObj: diffNew }; +} + module.exports = { cleanMentionID, defaultEventLogEmbed, multipleMembersFound, multipleRolesFound, multipleChannelsFound, findMemberRegEx, findChannelRegEx, findRoleRegEx, - getChannelMessage, errLog, + getChannelMessage, errLog, changed, execCB, ranLog, noPerm, getUTCComparison, trySend, tryDelete, tryReact, defaultDateFormat, adCheck, defaultImageEmbed, getChannel, diff --git a/resources/structures.js b/resources/structures.js index 801dcd7..7dfe948 100644 --- a/resources/structures.js +++ b/resources/structures.js @@ -383,6 +383,38 @@ Structures.extend("DMChannel", u => { } }); +Structures.extend("NewsChannel", u => { + return class NewsChannel extends u { + constructor(guild, data) { + super(guild, data); + this.lastMessagesID = []; + }; + + pushLastMessagesID() { + if (this.lastMessagesID.length === 3) { + this.lastMessagesID.shift(); + }; + return this.lastMessagesID.push(this.lastMessageID); + }; + } +}); + +Structures.extend("StoreChannel", u => { + return class StoreChannel extends u { + constructor(guild, data) { + super(guild, data); + this.lastMessagesID = []; + }; + + pushLastMessagesID() { + if (this.lastMessagesID.length === 3) { + this.lastMessagesID.shift(); + }; + return this.lastMessagesID.push(this.lastMessageID); + }; + } +}); + Structures.extend("Message", e => { return class Message extends e { constructor(client, data, channel) {