diff --git a/.gitignore b/.gitignore index df9906b..8ac6dfb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ config.json -auth.json \ No newline at end of file +auth.json +test.js \ No newline at end of file diff --git a/bot.js b/bot.js index 6875b40..66d88ae 100644 --- a/bot.js +++ b/bot.js @@ -10,13 +10,22 @@ async function init() { client.config = auth; ['commands', 'aliases'].forEach(x => client[x] = new Discord.Collection()); - ['command', 'event'].forEach(x => require(`./handle/${x}`)(client)); + client.responses = {triggers: [], commands: new Discord.Collection()}; + ['command', 'event', 'response'].forEach(x => require(`./handle/${x}`)(client)); client.developers = ["330547934951112705", "673477059904929802"]; + + client.misc = { + savers: ['497598953206841375'], + activeDMs: new Discord.Collection(), + statusPings: new Discord.Collection() + }; + client.utils = {}; client.utils.logch = async () => {return client.guilds.cache.get('762707532417335296').channels.cache.get('762732961753595915');}; client.guildconfig = {}; client.guildconfig.prefixes = new Map(); + client.guildconfig.logs = new Map(); } init(); \ No newline at end of file diff --git a/commands/afk.js b/commands/afk.js index ec46965..d73f251 100644 --- a/commands/afk.js +++ b/commands/afk.js @@ -24,7 +24,11 @@ module.exports = { if (reason.length > 150) {return message.reply("That status a bit long; keep it under 150 characters.");} tu.statustype = 'afk'; tu.statusmsg = reason.trim(); + tu.statussetat = new Date(); + let tempDate = new Date(); + tu.statusclearat = tempDate.setHours(tempDate.getHours() + 12); tu.save(); + require('../util/cachestatus')(message.author.id, tempDate.setHours(tempDate.getHours() + 12)); return message.reply(`I set your ${tu.statusclearmode === 'auto' ? 'automatically' : 'manually'}-clearing AFK message to: ${reason.trim()}`); } }; \ No newline at end of file diff --git a/commands/anime.js b/commands/anime.js new file mode 100644 index 0000000..cb6bde8 --- /dev/null +++ b/commands/anime.js @@ -0,0 +1,78 @@ +const {TagFilter} = require("../util/tagfilter"); +const {Tag} = require ("../util/tag"); + +const Discord = require('discord.js'); +const UserData = require('../models/user'); +const AniData = require('../models/anime'); + +module.exports = { + name: "anime", + aliases: ['ani', 'an'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Anime") + .setDescription("View and find anime in our huge list of anime!") + .addField("Syntax", "`anime <>`"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}anime <>\``);} + let queue = false; + if (['a', 'add', 'n', 'new'].includes(args[0])) { + let tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.staff) { + await message.channel.send("Since you aren't a Natsuki Staff member, this anime will be __submitted__ for reviewal!"); + queue = true; + } + let options = new TagFilter([ + new Tag(['ask', 'question'], 'ask', 'toggle'), + new Tag(['title', 't', 'name', 'n'], 'name', 'append'), + new Tag(['japname', 'japanesename', 'jn'], 'japname', 'listAppend'), + new Tag(['description', 'desc', 'd', 'plot', 'p'], 'plot', 'append'), + new Tag(['pub', 'pubs', 'publishers', 'publisher', 'pb'], 'publishers', 'listAppend'), + new Tag(['stud', 's', 'studio', 'studs', 'studios'], 'studios', 'listAppend'), + new Tag(['began', 'airstart', 'as'], 'airStartDate', 'append'), + new Tag(['ended', 'airend', 'ae'], 'airEndDate', 'append'), + new Tag(['iscomplete', 'completed', 'ic'], 'isComplete', 'toggle'), + new Tag(['seasons', 'sns'], 'seasons', 'append'), + new Tag(['episodes', 'es'], 'episodes', 'append'), + new Tag(['genres', 'g'], 'genres', 'listAppend'), + new Tag(['tags', 'ta', 'tgs', 'tg', 'tag'], 'tags', 'listAppend'), + new Tag(['cs', 'characters', 'chars', 'chs'], 'characters', 'listAppend'), + new Tag(['streams', 'streamat', 'sa'], 'streamAt', 'listAppend'), + new Tag(['img', 'thumb', 'thumbnail', 'image']) + ]).test(args.join(' ')); + + if (Object.keys(options).length) { + var foptions = {}; + let option; for (option of Object.keys(options)) { + if (Array.isArray(options[option])) { + let s = ''; + let data; + for (data of options[option]) { + s += data; + s += options[option].indexOf(data) < (options[option].length - 1) ? ', ' : ''; + } + foptions[option] = s; + } + } + } else { + if (client.misc.activeDMs.has(message.author.id)) {return message.channel.send("I'm already asking you questions in a DM! Finish that first, then try this command again.");} + client.misc.activeDMs.set(message.author.id, 'anime-add'); + + await message.author.send("I'm going to ask you some questions about the anime's info. Just reply with the answer and use good grammar and spelling and be as accurate as possible. To cancel the process, just leave the question unanswered for a few minutes and I'll let you know that the question timed out and is not longer answerable.") + .catch(() => {return message.reply("Something went wrong there! Most likely, your DMs are closed.");}); + + } + + message.channel.send(new Discord.MessageEmbed() + .setTitle(`New Anime -> ${options.name}`) + .setDescription(`${queue ? 'Requested' : 'Added'} by ${message.guild ? message.member.displayName : message.author.username}`) + .addField('Info', `**Name:** ${options.name}\n**Japanese Name:** ${options.japname}\n\n**Publishers:** ${foptions.publishers}\n**Studios:** ${foptions.studios}`) + .addField('Length', `**# of Seasons:** ${options.seasons}\n**# of Episodes:** ${options.episodes}`) + .addField('Airing', `**Began:** ${options.airStartDate}\n**Ended:** ${options.isComplete ? options.airEndDate : 'This anime is still airing!'}`) + .addField('Other', `**Genre(s):** ${foptions.genres}\n**Tags:** ${foptions.tags}\n**Characters:** ${foptions.characters}\n**Stream this at:** ${foptions.streamAt}`) + .setColor("c375f0") + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/autorole.js b/commands/autorole.js new file mode 100644 index 0000000..d8ee895 --- /dev/null +++ b/commands/autorole.js @@ -0,0 +1,33 @@ +const Discord = require('discord.js'); +const GuildData = require('../models/guild'); + +module.exports = { + name: "autorole", + aliases: ['joinrole', 'jr'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Auto Role/Join Role") + .setDescription("Set a role to be automatically added to users when they join the server.") + .addField("Syntax", "`autorole `") + .addField('Notice', "This command can only be used by server staff members and admins."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is only available in servers.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}autorole \``);} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (['v', 'view', 'check'].includes(args[0])) {return message.channel.send(tg.joinrole.length && message.guild.roles.cache.has(tg.joinrole) ? `I am currently adding \`${message.guild.roles.cache.get(tg.joinrole).name}\` to new members.` : "At the moment, I'm not adding a role to new members.");} + if ((!tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You don't have the permissions to edit this setting.");} + if (['s', 'set', 'c', 'clear'].includes(args[0])) { + let role = message.mentions.roles.first() ? message.mentions.roles.first().id : args[1] && message.guild.roles.cache.has(args[1]) ? args[1] : ['c', 'clear'].includes(args[0]) ? '' : null; + if (role === null) {return message.reply("That doesn't seem to be a role!");} + tg.joinrole = role; + tg.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Join Role Updated") + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Role: ${tg.joinrole.length ? `<@&${tg.joinrole}>` : "None"}`) + .setColor("c375f0") + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/bio.js b/commands/bio.js new file mode 100644 index 0000000..fc8486d --- /dev/null +++ b/commands/bio.js @@ -0,0 +1,49 @@ +const Discord = require('discord.js'); +const UserData = require('../models/user'); + +module.exports = { + name: "bio", + help: new Discord.MessageEmbed() + .setTitle("Help -> Bio") + .setDescription("Set and view user bios, which are fun ways to express yourself!") + .addField("Syntax", "`bio `"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}bio \``);} + let tu = await UserData.findOne({uid: message.author.id}) ? await UserData.findOne({uid: message.author.id}) : new UserData({uid: message.author.id}); + + if (['v', 'view', 'check'].includes(args[0].toLowerCase())) { + let person = args[1] ? args[1].match(/^<@(?:!?)(?:\d+)>$/) && message.mentions.users.first() ? message.mentions.users.first().id : message.guild && message.guild.members.cache.has(args[1]) ? args[1] : message.author.id : message.author.id; + let pud = await UserData.findOne({uid: person}); + if (!pud || !pud.bio || !pud.bio.length) {return message.reply(person === message.author.id ? "You don't have a bio set!" : "That user has no bio for me to show you!");} + return message.channel.send(new Discord.MessageEmbed() + .setTitle(`Bio for ${message.guild ? message.guild.members.cache.get(person).displayName : message.author.username}`) + .setThumbnail(client.users.cache.get(person).avatarURL({size: 2048})) + .setDescription(pud.bio) + .setColor(pud.color && pud.color.length ? pud.color : 'c375f0') + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + ); + } + if (['s', 'set'].includes(args[0].toLowerCase())) { + args.shift(); + if (!args.length) {return message.reply("Please specify a bio!");} + let bio = args.join(" "); + if (bio.length > 200) {return message.reply("Please keep your bio under 200 characters!");} + tu.bio = bio; + tu.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle(`Bio Set!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(tu.bio) + .setColor(tu.color && tu.color.length ? tu.color : 'c375f0') + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + ); + } + if (['c', 'clear'].includes(args[0].toLowerCase())) { + tu.bio = ''; + tu.save(); + return message.reply("Bio cleared!"); + } + } +}; \ No newline at end of file diff --git a/commands/blacklist.js b/commands/blacklist.js new file mode 100644 index 0000000..e6e59cd --- /dev/null +++ b/commands/blacklist.js @@ -0,0 +1,88 @@ +const Discord = require('discord.js'); +const UserData = require('../models/user'); +const GuildData = require('../models/guild') + +module.exports = { + name: "blacklist", + aliases: ['bl'], + help: "Disables a user from using Natsuki ( Usage: {{p}}blacklist )", + async execute(message, msg, args, cmd, prefix, mention, client) { + + let tu = await UserData.findOne({ uid: message.author.id }); + + if (['g', 'guild'].includes(args[0].toLowerCase())) { + if(!tu || !tu.admin) { return message.channel.send('Sorry... you have to be a Natsuki Admin to do this!');} + + let guild = !args[1].match(/\d+/) ? message.guild ? message.guild : null : client.guilds.cache.has(args[1]) ? client.guilds.cache.get(args[1]) : null; + if (!guild) {return message.channel.send("You must provide a guild ID or be in a guild that you wish to blacklist!");} + + let tg = await GuildData.findOne({gid: guild.id}) || new GuildData({gid: guild.id}); + + if (args[1].match(/\d+/)) {args.shift();} + if (!args[1]) {return message.channel.send("You must specify whether to `add` or `del` a guild's blacklist!");} + + let tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.admin) {return message.reply("You must be a Natsuki Admin to blacklist!");} + if (message.guild.id === "762707532417335296") {return message.reply("You can't blacklist my support server!");} + + if (['a', 'add'].includes(args[1].toLowerCase())) { + if (tg.blacklisted) {return message.reply("That guild is already blacklisted!");} + tg.blacklisted = true; + tg.save(); + return message.channel.send("Gotcha! This server will not be able to use my commands!"); + } + + if (['r', 'rem', 'remove', 'd', 'del', 'delete'].includes(args[1].toLowerCase())) { + if (tg.blacklisted) {return message.reply("That guild isn't blacklisted in the first place!");} + tg.blacklisted = false; + tg.save(); + return message.channel.send("I have graced your merciful request; this server can once again make use of my wonderous abilities!"); + } + + return message.channel.send("Valid args: `[guildID] `"); + + } + + if (['u', 'user'].includes(args[0].toLowerCase())) { + + args.shift(); + + if (!args[1]) {return message.channel.send("You must specify whether to `add` or `del` a user's blacklist!");} + + function checkPerms(tu, bu) { + if (!tu.developer && bu.support) {message.channel.send("You can't blacklist any member of staff unless you're a developer!"); return null;} + if (!tu.admin) {message.channel.send("You must be at least admin to do that!"); return null;} + if (bu.developer) {message.channel.send("Developers cannot be blacklisted!"); return null;} + } + + if(['a', 'add'].includes(args[1].toLowerCase())) { + let blacklistUser = args[0].match(/^<@(?:!?)(?:\d+)>$/) && mention && client.users.cache.has(mention.id) ? mention.id : client.users.cache.has(args[0]) ? client.users.cache.get(args[0]).id : null; + if (!blacklistUser) {return message.reply("You must specify a user to blacklist!");} + let usersData = await UserData.findOne( { uid: blacklistUser } ) || new UserData({uid: blacklistUser}); + + if (!checkPerms(tu, usersData)) {return;} + + if (usersData.blacklisted === true) {return message.reply('they\'re already blacklisted :eyes:');} + + await UserData.findOneAndUpdate({ uid: blacklistUser }, { blacklisted: true }.catch(() => {})); + return message.channel.send(`Another one bites the dust! **${blacklistUser.user.tag}** has been blacklisted!`) + } + + if(['r', 'rem', 'remove', 'd', 'del', 'delete'].includes(args[1].toLowerCase())) { + let blacklistedUser = args[0].match(/^<@(?:!?)(?:\d+)>$/) && mention && client.users.cache.has(mention.id) ? mention.id : client.users.cache.has(args[0]) ? client.users.cache.get(args[0]).id : null; + if (!blacklistedUser) { return message.reply("You need to specify who you're letting free..." );} + let userData = await UserData.findOne( { uid: blacklistedUser } ) || new UserData({uid: blacklistedUser}); + + if (!checkPerms(tu, userData)) {return;} + + if(userData.blacklisted === false) {return message.reply('hate to break it you... they\'re not even blacklisted!');} + + await UserData.findOneAndUpdate({ uid: blacklistedUser }, { blacklisted: false }.catch(() => {})); + return message.channel.send(`Alright, there you go, I unblacklisted **${blacklistedUser.user.tag}**`) + } + + return message.channel.send("Valid args: ` `"); + + } + + }}; \ No newline at end of file diff --git a/commands/clearstatus.js b/commands/clearstatus.js index 61a3681..4692d56 100644 --- a/commands/clearstatus.js +++ b/commands/clearstatus.js @@ -1,5 +1,6 @@ const Discord = require('discord.js'); const mongooes = require('mongoose'); + const UserData = require('../models/user'); module.exports = { @@ -15,6 +16,7 @@ module.exports = { tu.statusmsg = ''; tu.statustype = ''; tu.save(); + require('../util/siftstatuses')(client, message.author.id); return message.reply("welcome back! I cleared your status."); } }; \ No newline at end of file diff --git a/commands/clearwarnings.js b/commands/clearwarnings.js new file mode 100644 index 0000000..d50dbb0 --- /dev/null +++ b/commands/clearwarnings.js @@ -0,0 +1,62 @@ +const Discord = require('discord.js'); + +const Mod = require('../models/mod'); + +module.exports = { + name: "clearwarnings", + aliases: ['clearwarn', 'cw', 'warnclear', 'wc', 'clearwarning'], + meta: { + category: "", + perms: "", + staff: false, + vip: "", + serverPerms: [], + writtenBy: "", + serverOnly: false + }, + tags: [], + help: new Discord.MessageEmbed() + .setTitle("Help -> Warn Clearing") + .setDescription("Clears the warnigns of a user") + .addField("Syntax", "`clearwarnings <@user|userID>`") + .addField("Notice", "You must be a server administrator in order to use this command."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}clearwarnings <@user|userID>\``);} + if (!message.guild) {return message.channel.send("This is a server-only command.");} + if (!message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You must be a server administrator to use this command.");} + + let user = message.mentions.members.first() && args[0].match(/^<@(?:!)(?:\d+)>$/) ? message.mentions.members.first() : message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : null; + if (!user) {return message.channel.send("Either you didn't mention a user, or I can't find that user!");} + + if (user.id === client.user.id) {return message.reply("don't worry about clearing any warnings from me... you can't give me warnings in the first place");} + if (client.users.cache.get(user.id).bot) {return message.reply("it's not like a bot would have any warnings in the first place...");} + + user = user ? user : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!Object.keys(mh.warnings).includes(user.id) || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + + let mhcases = mh.cases; + let moddedcases = []; + let cwc = 0; var wc = 0; + let warning; for (warning of mh.warnings[user.id]) { + if (mhcases[`${warning - 1}`].status !== "Cleared") { + let tcase = mhcases[`${warning - 1}`]; + tcase.status = "Cleared"; + tcase.history.push(`${new Date().toISOString()} - ${message.author.username} - Cleared the warning.`); + moddedcases.push(`${warning - 1}`); + wc++; + if (!tcase.moderators.includes(message.author.id)) {tcase.moderators.push(message.author.id);} + mhcases[`${warning - 1}`] = tcase; + } else {cwc++;} + } + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + + if (moddedcases.length) {let c; for (c of moddedcases) {mh.markModified(`cases.${c}.history`);}} + + mh.cases = mhcases; + mh.save(); + return message.reply(`Cleared ${wc} warnings from ${user.displayName}.`); + } +}; \ No newline at end of file diff --git a/commands/cry.js b/commands/cry.js new file mode 100644 index 0000000..d16a876 --- /dev/null +++ b/commands/cry.js @@ -0,0 +1,32 @@ +const Discord = require('discord.js'); +const Saves = require('../models/saves'); +const UserData = require('../models/user'); +const makeId = require('../util/makeid'); + +module.exports = { + name: "cry", + aliases: ['sob'], + help: "Tell others that you're crying with `{{p}}cry`. We're here for you!", + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'cry'}) ? await Saves.findOne({name: 'cry'}) : new Saves({name: 'cry'}); + let saves = savess.saves; + if (!args.length) {return message.channel.send(new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} is Crying!`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('8d42f5') + );} + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new cry GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/deathnote.js b/commands/deathnote.js index 6e23da1..f2a51e0 100644 --- a/commands/deathnote.js +++ b/commands/deathnote.js @@ -59,21 +59,24 @@ module.exports = { async execute(message, msg, args, cmd, prefix, mention, client) { if (!message.guild) {return message.reply("Unfortunately, this is a **guild-only** command!");} if (!args.length) {return message.channel.send(`Syntax: \`${prefix}deathnote <@member> [method of death]\``);} - if (args[0] == "kill" || args[0] == "k") {args.shift();} // if someone adds in 'kill' it'll remove it and act like it wasn't there, proceeding as normal. + if (args[0] === "kill" || args[0] === "k") {args.shift();} // if someone adds in 'kill' it'll remove it and act like it wasn't there, proceeding as normal. //if (!args[0].trim().match(/^<@(?:\!?)\d+>$/)) {return message.reply("You have to mention someone!");} - if (mention && mention.id == message.author.id) {return message.reply("Hehe I won't let you write your own name in the notebook! Just leave it somewhere for a few days and someone else will take it. Maybe they'll write your name...");} // users can't mention themselves - if (mention && mention.id == client.user.id) {return message.reply("You can't kill me! Little did you know, I'm actually a death god!");} + if (mention && mention.id === message.author.id) {return message.reply("Hehe I won't let you write your own name in the notebook! Just leave it somewhere for a few days and someone else will take it. Maybe they'll write your name...");} // users can't mention themselves + if (mention && mention.id === client.user.id) {return message.reply("You can't kill me! Little did you know, I'm actually a death god!");} //TODO if bot is mentioned maybe - let death = deaths[Math.floor(Math.random() * deaths.length)]; //kill method let reptype = responses[Object.keys(responses)[Math.floor(Math.random() * Object.keys(responses).length)]]; // report type let title = reptype.titles[Math.floor(Math.random() * reptype.titles.length)]; - let options = new TagFilter([ new Tag(['method', '-m', 'cause', '-c'], 'method', 'append'), new Tag(['victim', 'v', 'against', 'a', 'name', 'n'], 'victim', 'append') ]).test(args.join(" ")); + + let death = (!options.victim || (options.victim && !options.victim.length)) && (!options.method || (options.method && !options.method.length)) && args.length > 1 + ? args.join(" ").slice(args[0].length + 1) + : deaths[Math.floor(Math.random() * deaths.length)]; //kill method if (options.method && options.method.length) {death = options.method;} + if (death.length > 750) {return message.channel.send("I'd rather you didn't try to fill the death note with a 7-page double-spaced essay in Times New Roman containing an advanced trajectory theorem on the death of your poor target.");} if (!mention && (!options.victim || !options.victim.length)) {return message.reply("You have to write their name down in order to kill them! (In other words, please mention the user whose name you wish to write.)");} @@ -81,7 +84,7 @@ module.exports = { let vargs = options.victim.trim().split(/\s+/g); let nvargs = []; let varg; for (varg of vargs) { - if (varg.match(/^<@(?:\!?)\d+>$/)) { + if (varg.match(/^<@(?:!?)\d+>$/)) { nvargs.push(message.guild.members.cache.has(varg.slice(varg.search(/\d/), varg.search('>'))) ? message.guild.members.cache.get(varg.slice(varg.search(/\d/), varg.search('>'))).displayName : varg); } else {nvargs.push(varg);} } diff --git a/commands/dnd.js b/commands/dnd.js index fee8705..b19057f 100644 --- a/commands/dnd.js +++ b/commands/dnd.js @@ -24,7 +24,11 @@ module.exports = { if (reason.length > 150) {return message.reply("That status a bit long; keep it under 150 characters.");} tu.statustype = 'dnd'; tu.statusmsg = reason.trim(); + tu.statussetat = new Date(); + let tempDate = new Date(); + tu.statusclearat = tempDate.setHours(tempDate.getHours() + 12); tu.save(); + require('../util/cachestatus')(message.author.id, tempDate.setHours(tempDate.getHours() + 12)); return message.reply(`I set your ${tu.statusclearmode === 'auto' ? 'automatically' : 'manually'}-clearing Do not Disturb message to: ${reason.trim()}`); } }; \ No newline at end of file diff --git a/commands/eval.js b/commands/eval.js index 2c62738..0e3c8af 100644 --- a/commands/eval.js +++ b/commands/eval.js @@ -1,39 +1,42 @@ const Discord = require('discord.js'); -const util = require('util'); -const moment = require('moment'); const chalk = require('chalk'); +const {Tag} = require('../util/tag'); +const {TagFilter} = require('../util/tagfilter'); + module.exports = { name: 'eval', - aliases: ['ev', ':'], + aliases: ['ev', ':', 'e'], help: "Evaluates raw JavaScript code. *This is a __developer-only__ command.* Usage: `{{p}}eval `", execute(message, msg, args, cmd, prefix, mention, client) { try { - if (!client.developers.includes(message.author.id)) return; + if (!client.developers.includes(message.author.id)) {return message.channel.send("Sorry, but I've got trust issues, so only me devs can go commanding me around like that.");}; + if (!args.length) return message.channel.send(`Syntax: \`${prefix}eval \``); - let kieran = client.users.cache.get('673477059904929802').tag + let options = new TagFilter([new Tag(['s', 'silent', 'nr', 'noreply'], 'silent', 'toggle')]).test(args[0]); + if (options.silent) {args.shift();} - if (!args.length) return message.channel.send(`Syntax: \`${prefix}eval \``); + if (!args.length) {return message.channel.send("Silly goose, if you want me to do something, you have to tell me what!");} const result = new Promise((resolve) => resolve(eval(args.join(' ')))); return result.then((output) => { if (typeof output !== 'string') { - output = require('util').inspect(output, { depth: 0 }); + output = require('util').inspect(output, {depth: 0}); } output = output.replace(client.config.token, 'Client Token') .replace(client.config.database.password, 'Database Password') - .replace(client.config.database.cluster, 'Database Cluster') + .replace(client.config.database.cluster, 'Database Cluster'); - return message.channel.send(new Discord.MessageEmbed() + return options.silent ? null : message.channel.send(new Discord.MessageEmbed() .setTitle('Client Evaluation') .setDescription(`\`\`\`js\n${output}\n\`\`\``) .setColor('c375f0') .setFooter(`Natsuki`, client.user.avatarURL()) - .setTimestamp()) - }); + .setTimestamp()); + }).catch(error => {return message.channel.send(`Error: \`${error}\`.`);}); } catch (error) { - //let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); - //console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to run n?eval`)}`, error); + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to run n?eval`)}`, error); return message.channel.send(`Error: \`${error}\`.`); - }; + } }, }; \ No newline at end of file diff --git a/commands/hug.js b/commands/hug.js new file mode 100644 index 0000000..41dae4b --- /dev/null +++ b/commands/hug.js @@ -0,0 +1,46 @@ +const Discord = require('discord.js'); +const Saves = require('../models/saves'); +const UserData = require('../models/user'); +const makeId = require('../util/makeid'); + +module.exports = { + name: "hug", + help: "Tell others that you need a hug with `{{p}}hug`, or give one by mentioning someone to hug!", + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'hug'}) ? await Saves.findOne({name: 'hug'}) : new Saves({name: 'hug'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} needs a hug!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Show them some love with \`${prefix}hug @${message.member.displayName}\`!`) + .setColor('c375f0') + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + : "Sorry, but I'm a bot, and I can't hug you. Go into a server and ask for some hugs!" + );} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to hug!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("Sorry if you're that lonely, but you can't hug yourself!");} + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} gives ${message.guild.members.cache.get(mention.id).displayName} a hug!`, message.author.avatarURL()) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('52c7bb') + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new hug GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/kiss.js b/commands/kiss.js new file mode 100644 index 0000000..97b3339 --- /dev/null +++ b/commands/kiss.js @@ -0,0 +1,46 @@ +const Discord = require('discord.js'); +const Saves = require('../models/saves'); +const UserData = require('../models/user'); +const makeId = require('../util/makeid'); + +module.exports = { + name: "kiss", + help: "Ask for a kiss with `{{p}}kiss`, or give one by mentioning someone!", + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'kiss'}) ? await Saves.findOne({name: 'kiss'}) : new Saves({name: 'kiss'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} wants a kiss!`) + .setThumbnail(message.author.avatarURL({size: 2048})) + .setDescription(`Give them a little kiss with \`${prefix}kiss @${message.member.displayName}\`!`) + .setColor('c375f0') + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + : "I'm not really into that kind of thing. Maybe try asking in a server?" + );} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to kiss!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("A self-kiss ought to be a little hard, don't you think?");} + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} kisses ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL()) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('d428a0') + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new kiss GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/leave.js b/commands/leave.js new file mode 100644 index 0000000..c5a919e --- /dev/null +++ b/commands/leave.js @@ -0,0 +1,66 @@ +const Discord = require('discord.js'); +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); +const sendResponse = require('../util/response/sendresponse'); + +module.exports = { + name: "leave", + aliases: ['lv', 'leavemsg', 'leavemessage', 'leavechannel', 'lch', 'lmsg', 'leavech'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Leave Messages") + .setDescription("Set the channel and message for your leave messages!") + .addField("Syntax", "`leave `") + .addField("Notice", "You must be a staff or admin in your server to edit these settings.") + .addField("Responses", "Your leave message should be generated through a response using my `response` command, and then bound to the leave message by providing your response's name."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is server-only.");} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}welcome \``);} + if (['v', 'view', 'c', 'check'].includes(args[0].toLowerCase())) {} + if ((!tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You can't do that without staff or admin permissions, silly!");} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.reply("You need to specify a channel for your leave messages to be sent in!");} + let ch = message.mentions.channels.first() && args[1].match(/^<#(?:\d+)>$/) ? message.mentions.channels.first().id : message.guild.channels.cache.has(args[1]) ? message.guild.channels.cache.get(args[1]).id : null; + if (!ch) {return message.reply("I can't find that channel!");} + if (!message.guild.channels.cache.get(ch).permissionsFor(client.user.id).has("SEND_MESSAGES")) {return message.reply("I can't send messages in that channel. Try fixing the permissions or using a different channel!");} + if (!args[2]) {return message.reply(`You have to specify a response to use! You can make one with \`${prefix}response new\`.`);} + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (!tr.responses.has(args[2].toLowerCase())) {return message.reply("Silly, I can't let you know that someone left with a response that doesn't exist! Try making one or make sure you spelled the name correctly.");} + tg.lch = ch; + tg.save(); + tr.bindings.set('leave', args[2].toLowerCase()); + tr.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Leave Channel/Message Updated") + .setDescription(`This server's leave-notifying settings have been altered by ${message.author.tag}.\n\n**Channel**: <#${ch}>\n**Response Name**: \`${args[2].toLowerCase()}\``) + .setColor('c375f0') + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ) + } + + if (['t', 'test'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr || !tr.bindings.has('leave') || !tr.responses.has(tr.bindings.get('leave'))) {return message.reply("I can't test your leave message because the response doesn't exist, a leave response isn't set, or you haven't made any responses in this server.");} + await sendResponse(message.member, message.channel, 'this shit aint matter anymore lol', client, tr.responses.get(tr.bindings.get('leave'))); + } + + if (['clear'].includes(args[0].toLowerCase())) { + tg.lch = ''; + tg.save(); + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (tr) { + tr.bindings.delete('leave'); + tr.save(); + } + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Leave Channel/Message Updated") + .setDescription(`This server's leave-notifying settings have been altered by ${message.author.tag}.\n\n**Channel**: None`) + .setColor('c375f0') + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/commands/logs.js b/commands/logs.js index 042fe2e..ae33f3b 100644 --- a/commands/logs.js +++ b/commands/logs.js @@ -1,17 +1,79 @@ const Discord = require("discord.js"); +const GuildData = require('../models/guild'); +const LogData = require('../models/log'); + + +const ObjLogTypes = { + mdelete: ['md', 'mdelete', 'messagedelete', 'deletemessage', 'deletemsg', 'msgdelete'], + medit: ['me', 'medit', 'messageedit', 'editmessage', 'msgedit', 'editmsg'], + chnew: ['chn', 'chc', 'newch', 'newchannel', 'chcreate', 'channelcreate'], + //chedit: ['channeledit'], + chdelete: ['chd', 'channeldelete', 'deletechannel', 'deletech', 'chdelete'], + //vcjoin: [], + //vcleave: [], + //servervcmute: [], + //servervcdeafen: [], + //kick: [], + //ban: [], + //mute: [], + //warn: [], + //giverole: [], + //takerole: [], + //addrole: [], + //editrole: [], + //deleterole: [], + //serverjoin: [], + //serverleave: [], + //nickname: [], + //username: [], + //avatar: [] +}; const LogTypes = new Map(); + +let keys = Object.keys(ObjLogTypes); +let key; for (key of keys) {let vs = ObjLogTypes[key]; let v; for (v of vs) {LogTypes.set(v, key);}} + + module.exports = { name: "logs", aliases: ["log", "l", "modlog", "modlogs"], help: new Discord.MessageEmbed() .setTitle("Help -> Server Logs") - .setDescription("Comfigure your server's log settings.\n\nLogs will update you on what ") - .addField("Syntax", "`vip `") - .addField("Notice", "This command is **developer-only**."), + .setDescription("Configure your server's log settings.\n\nLogs will update you on events in your server that have the potential to require moderator intervention, like someone deleting a hateful message before you can see it or a misbehaving moderator kicking/banning a member when they aren't supposed to.") + .addField("Syntax", "`log [logType] [#channel]`") + .addField("Notice", "You must be an admin or have the specified staff role in order to use this command."), async execute(message, msg, args, cmd, prefix, mention, client) { if (!message.guild) {return message.reply("This command is server-only!");} - if (!args.length) {return message.channel.send(`Syntax: \`${prefix}vip \``);} - const GuildSettings = require('../models/guild'); - + let tg = await GuildData.findOne({gid: message.guild.id}); + if ((!message.member.permissions.has("ADMINISTRATOR")) && (!tg || !tg.staffrole || !tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole))) {return message.reply("You must be an administrator or have the specified staff role in this server in order to edit or view log settings.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}log [logType] [#channel]\``);} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + if (args.length < 3) {return message.channel.send(`You must specify the log type and the channel to send the log to. Use \`${prefix}log list\` to see a list of valid log types.`);} + if (!LogTypes.has(args[1].toLowerCase())) {return message.channel.send("That's not a valid log type. Use \`${prefix}log list\` to see a list of valid log types.");} + let lt = LogTypes.get(args[1].toLowerCase()); + let ch = args[2].match(/<\#(?:\d+)>/m) && message.guild.channels.cache.has(message.mentions.channels.first().id) ? message.mentions.channels.first() : message.guild.channels.cache.has(args[2]) ? message.guild.channels.cache.get(args[2]) : null; + if (!ch) {return message.channel.send("I can't find that channel! Make sure that you've mentioned one, or that the ID you provided is correct, and that I can see it.");} + if (!ch.permissionsFor(client.user.id).has("SEND_MESSAGES")) {return message.reply("I don't have permissions to send messages in that channel. Please give me access and try again.");} + let tl = await LogData.findOne({gid: message.guild.id}) || new LogData({gid: message.guild.id}); + tl[lt] = ch.id; + tl.save(); + if (!client.guildconfig.logs.has(message.guild.id)) {client.guildconfig.logs.set(message.guild.id, new Map());} + client.guildconfig.logs.get(message.guild.id).set(lt, ch.id); + return message.channel.send("Log settings updated!"); + } + + if (['l', 'list'].includes(args[0].toLowerCase())) { + return message.channel.send("Valid log types:\n\n-`msgdelete` - Shows the content of a message that was deleted, in any channel.\n-`msgedit` - Shows both the old and new versions of a message when it is edited."); + } + + if (['v', 'view'].includes(args[0].toLowerCase())) { + if (client.guildconfig.logs.has(message.guild.id) && client.guildconfig.logs.get(message.guild.id).size) {return message.channel.send(`This server's logs: \n\n${function bonk(){let s = ''; Array.from(client.guildconfig.logs.get(message.guild.id).keys()).forEach(v => s+=`\`${v}\`: <#${client.guildconfig.logs.get(message.guild.id).get(v)}>, `); return s;}().slice(0, -2)}`);} + else {return message.channel.send("Your server doesn't have any logs set up at the moment, or they aren't cached. If you keep seeing this issue even after setting logs, please contact my developers!");} + } + + if (['c', 'clear'].includes(args[0].toLowerCase())) { + + } } }; \ No newline at end of file diff --git a/commands/pull.js b/commands/pull.js new file mode 100644 index 0000000..7daab80 --- /dev/null +++ b/commands/pull.js @@ -0,0 +1,30 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); +const UserData = require('../models/user'); +const cp = require('child_process'); + +module.exports = { + name: "pull", + help: new Discord.MessageEmbed() + .setTitle("Help -> VCS Pull") + .setDescription("Pulls new commits from VCS") + .addField("Syntax", "`refresh`") + .addField("Notice", "This command is only available to Natsuki developers."), + async execute(message, msg, args, cmd, prefix, mention, client) { + const tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.developer) {return message.channel.send("You must be a Natsuki developer in order to do this!");} + + console.log(`\n${chalk.yellow('[WARN]')} >> ${chalk.gray('VCS:')} ${chalk.white('Initiating remote->local VCS sync/refresh!')}`); + + cp.exec("git pull origin master", function(error, stdout, stderr) { + if (stderr || error) { + let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); + console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to pull from VCS`)}`, stderr || error); + } else { + console.log(`\n${chalk.gray('[INFO]')} >> ${chalk.hex('ff4fd0')(`VCS Pull successful`)}\n`); + } + return message.channel.send(`Done with ${stderr || error ? 'an error' : 'no errors'}!`); + }); + } +}; \ No newline at end of file diff --git a/commands/reload.js b/commands/reload.js new file mode 100644 index 0000000..a76eb8e --- /dev/null +++ b/commands/reload.js @@ -0,0 +1,64 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); +const UserData = require("../models/user"); + +module.exports = { + name: "reload", + aliases: ['relog', 'rel', 'refresh'], + help: new Discord.MessageEmbed() + .setTitle("Help -> System Reloading") + .setDescription("Reloads the system extensions by refreshing all command and event files into client without terminating the node process. *Hi I'm Wubzy and this makes no sense to anyone but discord.js devs because we're nerds*") + .addField("Syntax", "`refresh [log]`. Adding 'log' will log to the console as though the bot were in startup.") + .addField("Notice", "This command is only available to Natsuki developers."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) { + const tu = await UserData.findOne({uid: message.author.id}); + if (!tu || !tu.developer) {return message.channel.send("You must be a Natsuki developer in order to do this!");} + + var commands = fs.readdirSync('./commands').filter(file => file.endsWith('.js')); + console.log(`\n${chalk.yellow('[WARN]')} >> ${chalk.gray('Reload:')} ${chalk.white('All commands and events are being reloaded!')}`); + console.log(`${chalk.gray('[INFO]')} >> ${chalk.hex('ff4fd0')(`Developer ${message.author.username} initiated the system refresh`)}\n`); + + ['commands', 'aliases'].forEach(x => client[x] = new Discord.Collection()); + for (let commandf of commands) { + if (Object.keys(require.cache).includes(require.resolve(`./${commandf}`))) {delete require.cache[require.resolve(`./${commandf}`)];} + var command = require(`./${commandf}`); + client.commands.set(command.name, command); + if (command.aliases) {command.aliases.forEach(a => client.aliases.set(a, command.name));} + } + console.log(`${chalk.gray('[LOG]')} >> ${chalk.blue('Loaded all Commands')}`); + + let eventFilter = fs.readdirSync('./events/').filter(x => x.endsWith('.js')); + for (let file of eventFilter) { + let evtName = file.split('.')[0]; + if (Object.keys(require.cache).includes(require.resolve('../events/' + file))) {delete require.cache[require.resolve('../events/' + file)];} + let evt = require('../events/' + file); + client.removeAllListeners(evtName); + client.on(evtName, evt.bind(null, client)); + } + console.log(`${chalk.gray('[LOG]')} >> ${chalk.blue('Loaded all Events')}`); + + var responses = fs.readdirSync('./responses').filter(file => file.endsWith('.js')); + client.responses.triggers = []; + for (let responsef of responses) { + if (Object.keys(require.cache).includes(require.resolve(`../responses/${responsef}`))) {delete require.cache[require.resolve(`../responses/${responsef}`)];} + var response = require(`../responses/${responsef}`); + client.responses.triggers.push([response.name, response.condition]); + client.responses.commands.set(response.name, response); + } + console.log(`${chalk.gray('[LOG]')} >> ${chalk.blue('Loaded all Responses')}`); + + console.log(`\n${chalk.gray('[INFO]')} >> ${chalk.hex('ff4fd0')(`Client refresh successful`)}\n`); + + return message.channel.send("Done!") + } + if (['l', 'log', 'ns', 'nosilent', 'notsilent'].includes(args[0].toLowerCase())) { + ['commands', 'aliases'].forEach(x => client[x] = new Discord.Collection()); + client.responses = {triggers: [], commands: new Discord.Collection()}; + ['command', 'event', 'response'].forEach(x => require(`./${x}`)(client)); + return message.channel.send("Done!"); + } + else {return message.channel.send("Oi! 'log' is the only valid arg to use. Use no args if you want a cleaner console output instead.");} + } +}; \ No newline at end of file diff --git a/commands/response.js b/commands/response.js new file mode 100644 index 0000000..e285dd9 --- /dev/null +++ b/commands/response.js @@ -0,0 +1,78 @@ +const Discord = require('discord.js'); +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); + +const sendResponse = require('../util/response/sendresponse'); +const parseResponse = require('../util/response/parseresponse'); +const saveResponse = require('../util/response/saveresponse'); +const getResponse = require('../util/response/getresponse'); + +module.exports = { + name: "response", + aliases: ['r', 'resp'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Responses") + .setDescription("Configure your server's saved responses. These are reusable and editable, and can be placed in things like welcome messages and used for announcements.") + .addField("Syntax", "`response `") + .addField("Notice", "You must have your server's staff role or be an admin to use this command."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("You must be in a server to use this command.");} + let tg = await GuildData.findOne({gid: message.guild.id}); + if (!tg && !['q', 'quick'].includes(args[0].toLowerCase()) && (tg.staffrole.length && !message.member.roles.cache.has(tg.staffrole)) && message.member.permissions.has("ADMINISTRATOR")) {return message.reply("you need to be staff or admin in this server in order to edit those settings.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}response \``);} + + if (args.length < 1) {return message.reply("You have to tell me what I'm supposed to find or save!");} + + if (['q', 'quick'].includes(args[0].toLowerCase())) {return await sendResponse(message.member, message.channel, 'quick', client, await parseResponse(message, client, args));} + if (['n', 'new', 's', 'save'].includes(args[0].toLowerCase())) {return await saveResponse(await parseResponse(message, client, args), message);} + if (['t', 'test', 'send'].includes(args[0].toLowerCase())) {return await sendResponse(message.member, message.channel, 'quick', client, await getResponse(message, args[1]));} + if (['r', 'remove', 'd', 'delete', 'del'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr) {return message.reply("This server has no responses for me to delete.");} + if (!tr.responses.has(args[1].toLowerCase())) {return message.reply("I can't find that response.");} + tr.responses.delete(args[1].toLowerCase()); + let hadBinding = false; + let bm = ''; + tr.bindings.forEach((v, k) => {if (v === args[1].toLowerCase()) { + tr.bindings.delete(v); + hadBinding = true; + bm += `This response was bound to \`${k}\`, so that has also been removed.\n`; + }}); + tr.save(); + return message.channel.send(`I removed the response \`${args[1].toLowerCase()}\`.${hadBinding ? `\n\n${bm}` : ''}`); + } + if (['list', 'l'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr && !tr.responses.size) {return message.reply("This server has no responses for me to show you.");} + let s = "This server's response names: "; let resps = Array.from(tr.responses.keys()); + let resp; for (resp of resps) {s += `\`${resp}\`${resps.indexOf(resp) !== resps.length - 1 ? ', ' : ''}`;} + return message.channel.send(s); + } + if (['view', 'v'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr) {return message.reply("I'd give you information on a response, but this server doesn't have any.");} + if (!tr.responses.has(args[1].toLowerCase())) {return message.reply("I can't find that response.");} + let hasBinding = false; + let bm = ''; + tr.bindings.forEach((v, k) => {if (v === args[1].toLowerCase()) {hasBinding = true; bm += !bm.length ? `\`${k}\`` : `, \`${k}\``}}); + let infoEmbed = new Discord.MessageEmbed() + .setTitle("Response Info") + .setDescription(`Requested by ${message.member.displayName}`) + .addField("Name/ID", args[1].toLowerCase(), true) + .addField("Type", tr.responses.get(args[1].toLowerCase()).embed ? "Embed" : "Message", true) + .setColor('c375f0') + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp(); + if (hasBinding) {infoEmbed.addField("Server Bindings", bm);} + return message.channel.send(infoEmbed); + } + if (['edit', 'e', 'm', 'modify'].includes(args[0].toLowerCase())) { + let options = await getResponse(message, args[1]); + if (!options) {return;} + + } + + + return message.channel.send(`Syntax: \`${prefix}response \``); + } +}; \ No newline at end of file diff --git a/commands/secretsanta.js b/commands/secretsanta.js new file mode 100644 index 0000000..aeb5147 --- /dev/null +++ b/commands/secretsanta.js @@ -0,0 +1,176 @@ +const Discord = require('discord.js'); +const SS = require('../models/secretsanta'); + +const ask = require('../util/ask'); + +module.exports = { + name: "secretsanta", + aliases: ['ss'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Secret Santa") + .setDescription("Create a secret santa for all of your friends or for your server! Whether you celebrate the holidays or not, this can still be loads of fun!") + .addField("Syntax", "``"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} + if (['create', 'new', 'c', 'n', 's'].includes(args[0].toLowerCase())) { + function clearDM() {client.misc.activeDMs.delete(message.author.id);} + if (client.misc.activeDMs.has(message.author.id)) {return message.reply("I'm already asking you questions in DM for a separate command! Finish that command before using this one.");} + client.misc.activeDMs.set(message.author.id, 'secretsanta-make'); + let mesg = await message.author.send("I'll be asking you a few questions here about how you want your Secret Santa to go! You can simply ignore the messages for a few minutes to cancel the process.").catch(() => {message.reply("Please open your DMs so I can ask you some questions!");}); + let dmch = mesg.channel; + + let conf = await ask(mesg, "This secret santa will be tied to your account, and you will be considered the host. Is this okay?", 60000, true); if (!conf) {return clearDM();} + if (['n', 'no'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Oh, alrighty! Have the person who wants to be the host execute this same command.");} + if (!['yes', 'ye', 'y', 'sure'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let start = await ask(mesg, "When will you begin the secret santa? (You'll start it manually, so don't worry about formatting.", 60000, true); if (!start) {return clearDM();} + if (start.length > 150) {clearDM(); return dmch.send("Heya there, just a few words, please! I don't wanna have to read out an essay about when it's starting to all the people that want to hear about your secret santa!");} + + let end = await ask(mesg, "When will you end the secret santa? (You'll also end it manually.)", 60000, true); if (!end) {return clearDM();} + if (end.length > 150) {clearDM(); return dmch.send("Heya there, just a few words, please! I don't wanna have to read out an essay about when it's ending to all the people that want to hear about your secret santa!");} + + let spend = await ask(mesg, "What is your maximum and minimum spending? This is useful so that everyone gets an equal gift or gifts. This will be shown to the people that buy their gifts.", 360000, true); if (!spend) {return clearDM();} + if (spend.length > 500) {clearDM(); return dmch.send("Mate, this is not a dissertation! Let's keep it under 500 characters, please!");} + + let anon = await ask(mesg, "Will you be keeping this secret santa totally anonymous, or will you let the gift recipients know who their gifters are when they are opened? Type \"yes\" if you will be keeping it anonymous, and \"no\" otherwise.", 360000, true); if (!anon) {return clearDM();} + if (['n', 'no'].includes(anon.trim().toLowerCase())) {anon = false;} + else if (['yes', 'ye', 'y', 'sure'].includes(anon.trim().toLowerCase())) {anon = true;} + else {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let info = await ask(mesg, "What information would you like me to ask joining members to provide when they join your secret santa? (Whatever you type will be shown directly to them when.)", 360000, true); if (!info) {return clearDM();} + if (info.length > 750) {clearDM(); return dmch.send("Let's keep it under 750 characters, please.");} + + let notes = await ask(mesg, "Are there any other notes you'd like to add? If not, just write N/A or something of that nature.", 360000, true); if (!ask) {return clearDM();} + if (notes.length > 500) {clearDM(); return dmch.send("Let's keep it under 500 characters, please.");} + + let fconf = await ask(mesg, "Would you like to continue and create the secret santa? By doing so, you agree that:\n-Natsuki and its developers are not responsible for anything financially-related to your secret santa\n-Anyone with your join code can join the secret santa\n-You are responsible for notifying your members of any changes or updates.\n-I am not responsible for any eggnog that may or may not be stolen from you by Wubzy. *for legal reasons this is a joke*\n\n-The answers you have submitted are what you want to use, as they cannot be changed.", 120000, true); if (!fconf) {return clearDM();} + if (['n', 'no'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Oh, yikes. Is it about the eggnog? Sorry about that hehe...");} + if (!['yes', 'ye', 'y', 'sure'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let id; + while (true) { + id = require('../util/makeid')(6); + let test = await SS.findOne({ssid: id}); + if (!test) {break;} + } + + let tss = new SS({ + ssid: id, + owner: message.author.id, + start: start, + end: end, + anon: anon, + spend: spend, + info: info, + notes: notes, + members: [], + started: false + }); + tss.save(); + + clearDM(); + return dmch.send(new Discord.MessageEmbed() + .setTitle("Secret Santa Created!") + .setDescription("Your Secret Santa has been completed! Have your members join by using `n?secretsanta join ` where the ID is the ID displayed below. You can start your secret santa when you have at least 3 members with `n?secretsanta start `. If someone joins that you don't want in your secret santa, use `n?secretsanta kick <@member|userID>`. If you want to also participate, just join the same way as everyone else.") + .setThumbnail(message.author.avatarURL({size: 1024})) + .addField("ID", `\`${id}\``, true) + .addField("Owner", message.author.username, true) + .setColor("01bd2f") + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ); + } + + if (['j', 'join'].includes(args[0].toLowerCase())) { + if (!args[1] || args[1].length !== 6) {return message.channel.send("You must provide a 6-digit join code! Ask the host to copy it and send it to you.");} + + let tss = await SS.findOne({ssid: args[1]}); + if (!tss) {return message.channel.send("A secret santa with that join code does not exist!");} + if (tss.started) {return message.channel.send("That secret santa has already started.");} + let min = false; let m; for (m of tss.members) {if (m.id === message.author.id) {min = true;}} + if (tss.members && min) {return message.channel.send("You're already in that secret santa!");} + + function clearDM() {client.misc.activeDMs.delete(message.author.id);} + if (client.misc.activeDMs.has(message.author.id)) {return message.reply("I'm already asking you questions in DM for a separate command! Finish that command before using this one.");} + client.misc.activeDMs.set(message.author.id, 'secretsanta-join'); + + let mesg = await message.author.send("I'll be asking you a few questions here about you and what you want! You can simply ignore the messages for a few minutes to cancel the process.").catch(() => {message.reply("Please open your DMs so I can ask you some questions!");}); + let dmch = mesg.channel; + + let o = await client.users.fetch(tss.owner); + + await dmch.send(new Discord.MessageEmbed() + .setTitle("This Secret Santa!") + .setDescription("This is the one you're trying to join!") + .addField("Start", tss.start) + .addField("End", tss.end) + .addField("Spending", tss.spend) + .addField("Notes", tss.notes) + .addField("Anonymous Gifters", tss.anon ? "Yes" : "No") + .addField("ID", `\`${tss.ssid}\``, true) + .addField("Owner", o.username, true) + .addField("Members", tss.members ? tss.members.length : 0, true) + .setColor("01bd2f") + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ); + + let name = await ask(mesg, "What is your name? This can be seen by everyone in the secret santa.", 60000, true); if (!name) {return clearDM();} + if (name.length > 50) {clearDM(); return dmch.send("Maybe just the *first* name? I doubt it's over 50 characters.");} + + await dmch.send("This is the information the host has asked you to provide:"); + let info = await ask(mesg, tss.info, 600000, true); if (!info) {return clearDM();} + if (info.length > 750) {clearDM(); return dmch.send("Let's keep that under 750 characters. No need to put your entire Christmas list on there :smirk:");} + + let conf = await ask(mesg, "Before we finish, do you agree to the following things:\n-I, Natsuki, and my developers, are not responsible for anything financially-related to your Secret Santa\n-You should contact the host if you have questions\n-These answers you gave are final and will be seen by the person who draws you.\n-You *need* to have your DMs open so that I can reach you when drawing time comes!", 120000, true); + if (['n', 'no'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Alrighty! I've discarded your responses :P");} + if (!['yes', 'ye', 'y', 'sure'].includes(conf.trim().toLowerCase())) {clearDM(); return dmch.send("Please specify yes or no you weeb!");} + + let tssmembers = tss.members ? tss.members : {}; + tssmembers.push({id: message.author.id, name: name, info: info}); + tss.members = tssmembers; + tss.save(); + + clearDM(); + + await o.send(`${message.author.username} has joined your Secret Santa! (Code: \`${tss.ssid}\` in case you have more than one)`); + return dmch.send("All done! You've now joined."); + } + + if (['start'].includes(args[0])) { + if (!args[1] || args[1].length !== 6) {return message.channel.send("You must specify the join code/ID to your Secret Santa!");} + let tss = await SS.findOne({ssid: args[1]}); + if (!tss) {return message.channel.send("That Secret Santa doesn't exist; your code is invalid!");} + if (tss.owner !== message.author.id) {return message.channel.send("You must be the host to do that!");} + if (tss.started) {return message.channel.send("Your Secret Santa is already started!");} + if (tss.members.length < 3) {return message.channel.send("You need to have at least 3 members in order to start.");} + + let dm = []; let rm; + let m; for (m of tss.members) {dm.push({name: m.id, assignedTo: null});} + for (m of dm) { + while (true) { + let rm = tss.members[Math.floor(Math.random() * tss.members.length)]; + let exists = false; + let cdm; for (cdm of dm) {if (!exists) {exists = cdm.assignedTo === rm.id;}} + if (!exists && rm.id !== m.name) {dm[dm.indexOf(m)] = {name: m.name, assignedTo: rm.id}; break;} + } + } + tss.assignments = dm; + tss.started = true; + tss.save(); + + for (m of tss.members) { + let mem = await client.users.fetch(m.id); + let assignmentt; let a; for (a of tss.assignments) {if (a.name === m.id) {assignmentt = a.assignedTo; break;}} + let assignment; let tm; for (tm of tss.members) {if (tm.id === assignmentt) {assignment = tm; break;}} + + await mem.send(`The secret santa has been started!\nYou've been assigned to **${assignment.name}**. They've written the following about what they want:\n\`${assignment.info}\`\n\nThe host has said this about when you should when you should have your gifts purchased and when you'll do gift exchange:\n\`${tss.end}\`\n\nThe following info is written on how much to spend on gifts: \n\`${tss.spend}\``) + .catch(async () => { + let host = await client.users.fetch(tss.owner); + await host.send(`There was a problem sending ${mem.name} their info. Please tell that member that they have been assigned to \`${assignment.name}\` and that they want \`${assignment.info}\`.`); + }); + } + message.channel.send("**The secret santa has been started!** Everyone should have their assignments."); + } + } +}; \ No newline at end of file diff --git a/commands/sip.js b/commands/sip.js new file mode 100644 index 0000000..e5dd740 --- /dev/null +++ b/commands/sip.js @@ -0,0 +1,31 @@ +const Discord = require('discord.js'); +const Saves = require('../models/saves'); +const UserData = require('../models/user'); +const makeId = require('../util/makeid'); + +module.exports = { + name: "sip", + help: "Take a sip and watch the shenanigans unfold using `{{p}}sip`.", + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'sip'}) ? await Saves.findOne({name: 'sip'}) : new Saves({name: 'sip'}); + let saves = savess.saves; + if (!args.length) {return message.channel.send(new Discord.MessageEmbed() + .setTitle(`${message.guild ? message.member.displayName : message.author.username} takes a sip...`) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('69310d') + );} + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new sip GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/slap.js b/commands/slap.js new file mode 100644 index 0000000..a94a622 --- /dev/null +++ b/commands/slap.js @@ -0,0 +1,38 @@ +const Discord = require('discord.js'); +const Saves = require('../models/saves'); +const UserData = require('../models/user'); +const makeId = require('../util/makeid'); + +module.exports = { + name: "slap", + help: "Use `{{p}}slap @person` to have me personally deliver your anger to them with a nice s l a p.", + async execute(message, msg, args, cmd, prefix, mention, client) { + let savess = await Saves.findOne({name: 'slap'}) ? await Saves.findOne({name: 'slap'}) : new Saves({name: 'slap'}); + let saves = savess.saves; + if (!args.length) { + return message.channel.send(message.guild ? "Please mention someone to slap!" : "Oi! You don't get to waltz into my DM just to slap me!");} + if (mention && args[0].match(/^<@(?:!?)(?:\d+)>$/)) { + if (!message.guild) {return message.reply("Please make sure you're in a server so you can mention someone other than me to slap!");} + if (!message.guild.members.cache.has(mention.id)) {return message.reply("That user is not in this server!");} + if (message.author.id === mention.id) {return message.reply("Wait wouldn't slapping yourself be a form of self-harm? ToS is that you??");} + return message.channel.send(new Discord.MessageEmbed() + .setAuthor(`${message.guild ? message.member.displayName : message.author.username} slaps ${message.guild.members.cache.get(mention.id).displayName}`, message.author.avatarURL()) + .setImage(String(Array.from(saves.values())[Math.floor(Math.random() * saves.size)])) + .setColor('d93846') + ); + } + if (['s', 'save', 'n', 'new', 'a', 'add'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.channel.send('oi there cunt, give me a link of an image to add!');} + let tu = await UserData.findOne({uid: message.author.id}); + if ((!tu || !tu.developer) && !client.misc.savers.includes(message.author.id)) {return message.reply("You must be a Natsuki Developer in order to add new slap GIFs.");} + let e = true; + let id; + while (e === true) {id = makeId(6); if (!saves.has(id)) {e = false;}} + args.shift(); + saves.set(id, args.join(" ").trim()); + savess.saves = saves; + savess.save(); + return message.channel.send("Save added!"); + } + } +}; \ No newline at end of file diff --git a/commands/staffrole.js b/commands/staffrole.js index 9596626..4b2e494 100644 --- a/commands/staffrole.js +++ b/commands/staffrole.js @@ -10,7 +10,7 @@ module.exports = { if (!message.guild) {return message.reply("This is a guild-only command!");} if (!args.length) {return message.channel.send(`Syntax: \`${prefix}staffrole <@role|roleID|clear|view>\``);} if (!message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You must be an admin in this server in order to change this setting!");} - + let tguild = await GuildSettings.findOne({gid: message.guild.id}) ? await GuildSettings.findOne({gid: message.guild.id}) : new GuildSettings({gid: message.guild.id}); @@ -18,7 +18,7 @@ module.exports = { if (['view', 'v'].includes(args[0].trim().toLocaleLowerCase())) {return message.reply( tguild.staffrole.length ? message.guild.roles.cache.has(tguild.staffrole) - ? `\`people with the ${message.guild.roles.cache.get(tguild.staffrole).name}\` role can edit my setting here.` + ? `people with the \`${message.guild.roles.cache.get(tguild.staffrole).name}\` role can edit my setting here.` : `I have a role stored for this server, but it doesn't seem to exist anymore, so only admins can edit my settings right now.` : 'only admins may edit settings in this server.' );} diff --git a/commands/starboard.js b/commands/starboard.js new file mode 100644 index 0000000..217154d --- /dev/null +++ b/commands/starboard.js @@ -0,0 +1,59 @@ +const Discord = require('discord.js'); +const GuildData = require('../models/guild'); +const ask = require('../util/ask'); + +module.exports = { + name: "starboard", + aliases: ['sb'], + help: new Discord.MessageEmbed() + .setTitle("Help -> StarBoard") + .setDescription("Setup and view information on this server's starboard! This allows messages to be sent to a dedicated channel when they receive a set number of star messages.") + .addField("Syntax", "`starboard `") + .addField("Notice", "You must have the staff-role or be an admin in order to set up or toggle the starboard"), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("You must be in a server in order to use this command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}starboard \``);} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (['v', 'view', 'c', 'check'].includes(args[0])) {return message.reply(tg.starchannel.length ? tg.starsenabled ? `I'm watching for stars in <#${tg.starchannel}>. Messages will be Starred when they receive ${tg.starreq} :star: reactions.` : "StarBoard has been set up for this server, but isn't enabled at this time." : "StarBoard has not yet been set up in this server. You can do so with `" + prefix + "starboard setup`.");} + if ((tg.staffrole.length && !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.channel.send("You don't have permissions to edit starboard settings in this server!");} + + if (['setup', 's', 'config', 'c'].includes(args[0])) { + if (tg.starchannel.length) {message.channel.send("You already have a starboard set up in your server! This means that by continuing the setup, you'll be overwriting your previous settings.");} + + let ch = await ask(message, 'What channel would you like the starboard to be in? (Time: 30s)', 30000); if (!ch) {return;} + if (ch.match(/^<#(?:\d+)>$/) && message.guild.channels.cache.has(ch.slice(ch.search(/\d/), ch.search('>')))) {tg.starchannel = ch.slice(ch.search(/\d/), ch.search('>'));} + else if (message.guild.channels.cache.has(ch)) {tg.starchannel = ch;} + else {return message.reply("Please specify a channel that actually exists! Try again.");} + + let starNum = await ask(message, 'How many stars should be reacted to a message in order for it to be starred? Please just send a number. (Time: 30s)', 30000); if (!starNum) {return;} + if (isNaN(Number(starNum))) {return message.reply("That isn't a number! Please try again.");} + starNum = Number(starNum); + if (starNum < 3) {return message.reply("You need to have at least 3 stars. Try again");} + if (starNum > 100) {return message.reply("You can't require more than 100 stars! Try again.");} + tg.starreq = starNum; + + tg.starsenabled = true; + tg.save(); + + return message.channel.send(`Got it! I will now be watching for messages with at least ${starNum} reactions, and I'll send them to <#${tg.starchannel}>!`); + } + + else if (['e', 'enable'].includes(args[0]) || (['t', 'toggle'].includes(args[0]) && !tg.starsenabled)) { + if (tg.starsenabled) {return message.reply("StarBoard is already enabled in this server!");} + if (!tg.starchannel.length) {return message.reply(`Please setup StarBoard first! \`${prefix}starboard setup\`.`);} + tg.starsenabled = true; + tg.save(); + return message.channel.send(`I've re-enabled your StarBoard. It's kept the same settings as you had when you disabled it!`); + } + + else if (['d', 'disable'].includes(args[0]) || (['t', 'toggle'].includes(args[0]) && tg.starsenabled)) { + if (!tg.starsenabled) {return message.reply("StarBoard is already disabled in this server!");} + if (!tg.starchannel.length) {return message.reply(`Please setup StarBoard first! \`${prefix}starboard setup\`.`);} + tg.starsenabled = false; + tg.save(); + return message.channel.send(`I've disabled your StarBoard. Your StarBoard configuration will be kept for if/when you re-enable StarBoard.`); + } + + else {return message.reply(`Invalid arg! Syntax: \`${prefix}starboard \``);} + } +}; \ No newline at end of file diff --git a/commands/userinfo.js b/commands/userinfo.js index 2dcf690..9ce5d9b 100644 --- a/commands/userinfo.js +++ b/commands/userinfo.js @@ -11,6 +11,7 @@ module.exports = { let person = message.guild ? mention ? message.guild.members.cache.get(mention.id) : args[0] ? message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : message.member : message.member : message.author; let name = message.guild ? person.displayName : person.username; let tu = await UserData.findOne({uid: person.id}); + if (!tu) {return message.channel.send("I don't have any data on that user yet.");} let infoembed= new Discord.MessageEmbed() .setTitle(`User Info for ${name}`) .setDescription(`Requested by ${message.guild ? message.member.displayName : message.author.username}`) diff --git a/commands/warn.js b/commands/warn.js new file mode 100644 index 0000000..3b76ece --- /dev/null +++ b/commands/warn.js @@ -0,0 +1,134 @@ +const Discord = require('discord.js'); + +const Mod = require('../models/mod'); + +const {TagFilter} = require('../util/tagfilter'); +const {Tag} = require('../util/tag'); + +module.exports = { + name: "warn", + help: new Discord.MessageEmbed() + .setTitle("Help -> Warnings") + .setDescription("") + .addField("Syntax", "`warn <@member> `") + .addField("Notice", "You must be a server administrator in order to use this command."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.channel.send("This is a server-only command.");} + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}warn <@member> \``);} + if (!message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You must be a server administrator to use this command.");} + if (args.length < 2 && !['check', 'c', 'list', 'l', 'clear', 'e', 'empty'].includes(args[0].toLowerCase())) {return message.channel.send("You must provide a reason for warning the user, or `check` or `clear`.");} + + let user = message.mentions.members.first() && args[0].match(/^<@(?:!)(?:\d+)>$/) ? message.mentions.members.first() : message.guild.members.cache.has(args[0]) ? message.guild.members.cache.get(args[0]) : null; + if (!user && args.length > 1) {return message.channel.send("Either you didn't mention a user, or I can't find that user!");} + if (args.length > 1) {args.shift();} + + if (['check', 'c', 'list', 'l'].includes(args[0].toLowerCase())) { + user = user ? user : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!mh.warnings[user.id] || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + //console.log(mh.cases, mh.warnings); + let ws = ''; + let cwc = 0; + let warning; for (warning of mh.warnings[user.id]) { + let tcase = mh.cases[warning - 1]; + if (tcase.status !== "Cleared") { + ws += `\`Case #${warning}\` - Issued by <@${tcase.moderators[0]}>\n${tcase.reason}\n\n`; + } else {cwc++;} + } + if (cwc > 0) {ws += '*Plus ' + cwc + ' other warnings that have been cleared.*';} + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + return message.channel.send(new Discord.MessageEmbed() + .setTitle("User Warnings") + .setThumbnail(client.users.cache.get(user.id).avatarURL({size: 1024})) + .setDescription(`For ${user.displayName}`) + .addField("Warnings", ws) + .setColor("c375f0") + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ); + } + + else if (['clear', 'e', 'empty'].includes(args[0].toLowerCase())) { + user = user ? user : message.member; + let mh = await Mod.findOne({gid: message.guild.id}); + if (!mh || !Object.keys(mh.warnings).length) {return message.reply("There are no warnings available in this server.");} + + if (!Object.keys(mh.warnings).includes(user.id) || !mh.warnings[user.id].length) {return message.reply(`${user.id === message.author.id ? 'You have' : 'That user has'} never been warned in this server.`);} + + let mhcases = mh.cases; + let moddedcases = []; + let cwc = 0; var wc = 0; + let warning; for (warning of mh.warnings[user.id]) { + if (mhcases[`${warning - 1}`].status !== "Cleared") { + let tcase = mhcases[`${warning - 1}`]; + tcase.status = "Cleared"; + tcase.history.push(`${new Date().toISOString()} - ${message.author.username} - Cleared the warning.`); + moddedcases.push(`${warning - 1}`); + wc++; + if (!tcase.moderators.includes(message.author.id)) {tcase.moderators.push(message.author.id);} + mhcases[`${warning - 1}`] = tcase; + } else {cwc++;} + } + if (cwc === mh.warnings[user.id].length) {return message.reply("That user has no uncleared warnings.");} + + if (moddedcases.length) {let c; for (c of moddedcases) {mh.markModified(`cases.${c}.history`);}} + + mh.cases = mhcases; + mh.save(); + return message.reply(`Cleared ${wc} warnings from ${user.displayName}.`); + } + + else { + if (user.id === message.author.id) {return message.channel.send("You can't warn yourself!");} + if (user.id === client.user.id) {return message.channel.send("You can't warn me, silly! What do you want me to do, spank myself?");} + if (client.users.cache.get(user.id).bot) {return message.channel.send("You can't warn a bot!");} + + let options = new TagFilter([ + new Tag(['r', 'reason'], 'reason' ,'append'), + new Tag(['nd', 'nm', 'nomessage', 'nodm'], 'nodm' ,'toggle'), + new Tag(['silent', 's'], 'silent', 'toggle'), + new Tag(['note', 'n', 'notes'], 'notes', 'append') + ]).test(args.join(" ")); + + if (Object.keys(options).length && (!options.reason || !options.reason.length)) {return message.channel.send("You *must* use -r or -reason to specify your reason if you include any other tags. Please try again!");} + let reason = options.reason && options.reason.length ? options.reason : args.join(" "); + if (reason.length > 200) {return message.reply("Please keep your reason short and sweet - less than 200 characters, please!");} + if (options.notes && options.notes.length > 150) {return message.reply("Please keep your notes short and sweet - less than 150 characters, please!");} + let notes = options.notes && options.notes.length ? [options.notes] : []; + + let mh = await Mod.findOne({gid: message.guild.id}) || new Mod({gid: message.guild.id}); + + let mhcases = mh.cases; + mhcases.push({ + members: [user.id], + punishment: "Warned", + reason: reason, + status: "Closed", + moderators: [message.author.id], + notes: notes, + history: [`${new Date().toISOString()} - ${message.author.username} - Created case`, `${new Date().toISOString()} - ${message.author.username} - Warned ${client.users.cache.get(user.id).username}`], + issued: new Date().toUTCString() + }); + + let mhwarnings = mh.warnings; + let mhwarningsk = Object.keys(mhwarnings); + + if (mhwarningsk.includes(user.id)) {let tw = mhwarnings[user.id]; tw.push(mhcases.length); mhwarnings[user.id] = tw;} + else {mhwarnings[user.id] = [mhcases.length];} + + mh.warnings = mhwarnings; + mh.warnings[user.id] = mhwarnings[user.id]; + mh.cases = mhcases; + + if (!options.silent) {message.channel.send(`Case ${mh.cases.length} - Member has been warned. Reason: \`${reason}\``);} + if (!options.silent && !options.nodm) {client.users.cache.get(user.id).send(`\`${message.author.username}\` has warned you in \`${message.guild.name}\`. Reason: **${reason}**`);} + + mh.markModified(`warnings.${user.id}`); + + mh.save(); + return null; + } + } +}; \ No newline at end of file diff --git a/commands/welcome.js b/commands/welcome.js new file mode 100644 index 0000000..3ddade7 --- /dev/null +++ b/commands/welcome.js @@ -0,0 +1,66 @@ +const Discord = require('discord.js'); +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); +const sendResponse = require('../util/response/sendresponse'); + +module.exports = { + name: "welcome", + aliases: ['wel', 'welcomemsg', 'welcomemessage', 'welcomechannel', 'wch', 'wmsg', 'welcomech'], + help: new Discord.MessageEmbed() + .setTitle("Help -> Welcome Messages") + .setDescription("Set the channel and message for your welcome messages!") + .addField("Syntax", "`welcome `") + .addField("Notice", "You must be a staff or admin in your server to edit these settings.") + .addField("Responses", "Your welcome message should be generated through a response using my `response` command, and then bound to the welcome message by providing your response's name."), + async execute(message, msg, args, cmd, prefix, mention, client) { + if (!message.guild) {return message.reply("This command is server-only.");} + let tg = await GuildData.findOne({gid: message.guild.id}) ? await GuildData.findOne({gid: message.guild.id}) : new GuildData({gid: message.guild.id}); + if (!args.length) {return message.channel.send(`Syntax: \`${prefix}welcome \``);} + if (['v', 'view', 'c', 'check'].includes(args[0].toLowerCase())) {} + if ((!tg.staffrole.length || !message.member.roles.cache.has(tg.staffrole)) && !message.member.permissions.has("ADMINISTRATOR")) {return message.reply("You can't do that without staff or admin permissions, silly!");} + + if (['s', 'set'].includes(args[0].toLowerCase())) { + if (!args[1]) {return message.reply("You need to specify a channel for your welcome messages to be sent in!");} + let ch = message.mentions.channels.first() && args[1].match(/^<#(?:\d+)>$/) ? message.mentions.channels.first().id : message.guild.channels.cache.has(args[1]) ? message.guild.channels.cache.get(args[1]).id : null; + if (!ch) {return message.reply("I can't find that channel!");} + if (!message.guild.channels.cache.get(ch).permissionsFor(client.user.id).has("SEND_MESSAGES")) {return message.reply("I can't send messages in that channel. Try fixing the permissions or using a different channel!");} + if (!args[2]) {return message.reply(`You have to specify a response to use! You can make one with \`${prefix}response new\`.`);} + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (!tr.responses.has(args[2].toLowerCase())) {return message.reply("Silly, I can't welcome someone with a response that doesn't exist! Try making one or make sure you spelled the name correctly.");} + tg.wch = ch; + tg.save(); + tr.bindings.set('welcome', args[2].toLowerCase()); + tr.save(); + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Welcome Channel/Message Updated") + .setDescription(`This server's member-welcoming settings have been altered by ${message.author.tag}.\n\n**Channel**: <#${ch}>\n**Response Name**: \`${args[2].toLowerCase()}\``) + .setColor('c375f0') + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ) + } + + if (['t', 'test'].includes(args[0].toLowerCase())) { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr || !tr.bindings.has('welcome') || !tr.responses.has(tr.bindings.get('welcome'))) {return message.reply("I can't test your welcome message because the response doesn't exist, a welcome response isn't set, or you haven't made any responses in this server.");} + await sendResponse(message.member, message.channel, 'this shit aint matter anymore lol', client, tr.responses.get(tr.bindings.get('welcome'))); + } + + if (['clear'].includes(args[0].toLowerCase())) { + tg.wch = ''; + tg.save(); + let tr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (tr) { + tr.bindings.delete('welcome'); + tr.save(); + } + return message.channel.send(new Discord.MessageEmbed() + .setTitle("Welcome Channel/Message Updated") + .setDescription(`This server's member-welcoming settings have been altered by ${message.author.tag}.\n\n**Channel**: None`) + .setColor('c375f0') + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/events/guildMemberAdd.js b/events/guildMemberAdd.js new file mode 100644 index 0000000..f837d38 --- /dev/null +++ b/events/guildMemberAdd.js @@ -0,0 +1,19 @@ +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); +const sendResponse = require('../util/response/sendresponse'); + +module.exports = async (client, member) => { + let tg = await GuildData.findOne({gid: member.guild.id}); + let tr = await Responses.findOne({gid: member.guild.id}); + if (tg && tg.joinrole.length && member.guild.roles.cache.has(tg.joinrole)) { + if (member.guild.members.cache.get(client.user.id).permissions.has("MANAGE_ROLES")) {member.roles.add(tg.joinrole);} + } + if ( + tr && tr.bindings.has('welcome') && tr.responses.has(tr.bindings.get('welcome')) + && tg.wch.length && member.guild.channels.cache.has(tg.wch) + && member.guild.channels.cache.get(tg.wch).permissionsFor(client.user.id).has("SEND_MESSAGES") + && !client.users.cache.get(member.id).bot + ) { + try {member.guild.channels.cache.get(tg.wch).send(await sendResponse(member, member.guild.channels.cache.get(tg.wch), 'xdlol', client, tr.responses.get(tr.bindings.get('welcome'))));} catch {} + } +}; \ No newline at end of file diff --git a/events/guildMemberRemove.js b/events/guildMemberRemove.js new file mode 100644 index 0000000..63d8fcf --- /dev/null +++ b/events/guildMemberRemove.js @@ -0,0 +1,16 @@ +const GuildData = require('../models/guild'); +const Responses = require('../models/responses'); +const sendResponse = require('../util/response/sendresponse'); + +module.exports = async (client, member) => { + let tg = await GuildData.findOne({gid: member.guild.id}); + let tr = await Responses.findOne({gid: member.guild.id}); + if ( + tr && tr.bindings.has('leave') && tr.responses.has(tr.bindings.get('leave')) + && tg.lch.length && member.guild.channels.cache.has(tg.lch) + && member.guild.channels.cache.get(tg.lch).permissionsFor(client.user.id).has("SEND_MESSAGES") + && !client.users.cache.get(member.id).bot + ) { + try {member.guild.channels.cache.get(tg.lch).send(await sendResponse(member, member.guild.channels.cache.get(tg.lch), 'xdlol', client, tr.responses.get(tr.bindings.get('leave'))));} catch {} + } +}; \ No newline at end of file diff --git a/events/message.js b/events/message.js index dd22d9b..a0220fa 100644 --- a/events/message.js +++ b/events/message.js @@ -1,12 +1,13 @@ const Discord = require('discord.js'); -const mongoose = require('mongoose'); const chalk = require('chalk'); + const wait = require('../util/wait'); + const UserData = require('../models/user'); +const StatusCache = require('../models/statuses'); module.exports = async (client, message) => { if (message.author.bot) {return undefined;} - if (message.channel.type == 'dm') /*{var dmch = true;} else {var dmch = false};*/ {return undefined;} if (message.channel.type !== 'text' && message.channel.type !== 'dm') {return undefined;} //if (message.channel.type == "text") {if (settings[message.guild.id]) {prefix = settings[message.guild.id].prefix;};}; @@ -24,25 +25,38 @@ module.exports = async (client, message) => { : message.content.slice(3 + client.user.id.length).trim().split(/\s+/g); var cmd = args.shift().toLowerCase().trim(); + if ([`<@${client.user.id}>`, `<@!${client.user.id}>`].includes(msg)) { + return message.channel.send(new Discord.MessageEmbed() + .setTitle(["Yep, that's me!", "^^ Hiya!", "Oh, hi there!", "Sure, what's up?", "How can I help!", "Natsuki is busy, but I can take a message for you!", "Teehee that's me!", "You were looking for Natsuki Tivastl, right?", "Sure! What's up?", "Pong!"][Math.floor(Math.random() * 10)]) + .setDescription("My prefix here is `" + prefix + "`. Use `" + prefix + "help` to see what commands you can use.") + .setColor('c375f0')); + } + if (mention && message.guild) {require('../util/mention')(message, msg, args, cmd, prefix, mention, client);} let tu = await UserData.findOne({uid: message.author.id}); if (tu && tu.statusmsg.length && tu.statusclearmode === 'auto') { tu.statusmsg = ''; tu.statustype = ''; tu.save(); + const statuses = await StatusCache.findOne({f: 'lol'}); + let status; for (status of statuses.statuses) { + if (status.id === message.author.id) {delete statuses.statuses.indexOf(status);} + } + statuses.save(); message.reply('Hey there! You asked me to clear your status when you send a message next, so I went ahead and did that for you.'); } try { if (msg.startsWith(prefix) || msg.startsWith(`<@${client.user.id}>`) || msg.startsWith(`<@!${client.user.id}>`)) { let command = client.commands.get(cmd) || client.commands.get(client.aliases.get(cmd)); - if (!command) {return;} + if (!command) {let trigger; for (trigger of client.responses.triggers) {if (await trigger[1](message, msg, args, cmd, prefix, mention, client)) {await client.responses.commands.get(trigger[0]).execute(message, msg, args, cmd, prefix, mention, client); break;}} return;} message.channel.startTyping(); await wait(800); message.channel.stopTyping(); require('../util/oncommand')(message, msg, args, cmd, prefix, mention, client); - command.execute(message, msg, args, cmd, prefix, mention, client); + return command.execute(message, msg, args, cmd, prefix, mention, client); } + let trigger; for (trigger of client.responses.triggers) {if (await trigger[1](message, msg, args, cmd, prefix, mention, client)) {await client.responses.commands.get(trigger[0]).execute(message, msg, args, cmd, prefix, mention, client); break;}} } catch (e) { var date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | In ${message.guild.name}\n`)}`, e); diff --git a/events/messageDelete.js b/events/messageDelete.js new file mode 100644 index 0000000..5fc5f7a --- /dev/null +++ b/events/messageDelete.js @@ -0,0 +1,27 @@ +const Discord = require('discord.js'); + +module.exports = async (client, message) => { + if (message.channel.type != "text") {return;}; + //if (!Object.keys(snipe.delete).includes(message.guild.id)) {snipe.delete[message.guild.id] = {};}; + //snipe.delete[message.guild.id][message.channel.id] = message; + + let ts = client.guildconfig.logs.has(message.guild.id) && client.guildconfig.logs.get(message.guild.id).has('mdelete') ? client.guildconfig.logs.get(message.guild.id).get('mdelete') : null; + if (ts) {if (message.guild.channels.cache.has(ts) && message.guild.channels.cache.get(ts).permissionsFor(client.user.id).has("SEND_MESSAGES")) { + let mde = new Discord.MessageEmbed() + .setTitle('Message Deleted') + .setDescription(`Sent by <@${message.author.id}> | In <#${message.channel.id}>`) + .setThumbnail(message.author.avatarURL({size: 1024})) + .setColor('ecff8f').setFooter("Natsuki", client.user.avatarURL()).setTimestamp(); + if (message.content && message.content.length) {mde.addField("Message", "`-> `" + message.content.toString());} + if (message.attachments.size) { + if (message.attachments.first().url.includes(".png") || message.attachments.first().url.includes(".jpg") || message.attachments.first().url.includes(".gif")) {/*console.log('e');*/ try {mde.setImage(message.attachments.first().url);} catch {}} + let av = Array.from(message.attachments.values()); + as = ''; for (let a of av) { + as += `[Att. ${av.indexOf(a) + 1}](${a.url})`; + if (av.indexOf(a) + 1 < av.length) {as += ' | ';} + } + if (as.length) {mde.addField('Attachments', as);} + } + message.guild.channels.cache.get(ts).send(mde).catch(() => {}); + }} +} \ No newline at end of file diff --git a/events/messageReactionAdd.js b/events/messageReactionAdd.js new file mode 100644 index 0000000..8187c5b --- /dev/null +++ b/events/messageReactionAdd.js @@ -0,0 +1,41 @@ +const Discord = require("discord.js"); +const GuildData = require('../models/guild'); +const StarData = require('../models/starboard'); + +module.exports = async (client, reaction, user) => { + if (reaction.partial) {try {await reaction.fetch();} catch {return;}} + + if (reaction.emoji.name === "⭐") { + if (!reaction.message.guild) {return;} + let tg = await GuildData.findOne({gid: reaction.message.guild.id}); + if (!tg) {return;} + if (tg.starchannel.length && tg.starsenabled && reaction.message.guild.channels.cache.has(tg.starchannel) && reaction.message.guild.channels.cache.get(tg.starchannel).permissionsFor(client.user.id).has('SEND_MESSAGES')) { + if (reaction.message.channel.id === tg.starchannel) {return;} + let sd = await StarData.findOne({gid: reaction.message.guild.id}) ? await StarData.findOne({gid: reaction.message.guild.id}) : new StarData({gid: reaction.message.guild.id}); + + let starEmbed = new Discord.MessageEmbed() + .setTitle('Starred Message!') + .setDescription(`Sent by ${reaction.message.member.displayName} (<@${reaction.message.author.id}>) || Channel: ${reaction.message.channel.name} (<#${reaction.message.channel.id}>)\n[Jump to Message](${reaction.message.url})`) + .setThumbnail(reaction.message.author.avatarURL({size: 2048})) + .setColor('ebb931') + .setFooter("Natsuki", client.user.avatarURL()) + .setTimestamp(); + if (reaction.message.content.length) {starEmbed.addField("Message", reaction.message.content);} + starEmbed + .addField("Stars", `:star: ${reaction.count}`, true) + .addField(`${reaction.message.member.displayName.toLowerCase().endsWith('s') ? `${reaction.message.member.displayName}'` : `${reaction.message.member.displayName}'s`} StarBoard Count`, sd.starCount[reaction.message.author.id] ? sd.starCount[reaction.message.author.id] + 1 : 1, true); + if (reaction.message.attachments.size) {starEmbed.setImage(reaction.message.attachments.first().url);} + if (Object.keys(sd.stars).includes(reaction.message.id)) { + let starMessage = await reaction.message.guild.channels.cache.get(tg.starchannel).messages.fetch(sd.stars[reaction.message.id]); + if (starMessage) {await starMessage.edit(starEmbed);} + } else { + if (reaction.count < tg.starreq) {return;} + let starEmbedMessage = await reaction.message.guild.channels.cache.get(tg.starchannel).send(starEmbed); + sd.stars[reaction.message.id] = starEmbedMessage.id; + sd.starCount[reaction.message.author.id] = sd.starCount[reaction.message.author.id] ? sd.starCount[reaction.message.author.id] + 1 : 1; + sd.serverStarCount += 1; + sd.save(); + } + } + } +}; \ No newline at end of file diff --git a/events/messageUpdate.js b/events/messageUpdate.js new file mode 100644 index 0000000..89a942b --- /dev/null +++ b/events/messageUpdate.js @@ -0,0 +1,22 @@ +const Discord = require('discord.js'); + +module.exports = async (client, oldM, newM) => { + if (oldM.channel.type != "text") {return;}; + if (oldM.author.bot) {return;} + if (oldM.deleted) {return;} + //if (!Object.keys(snipe.edit).includes(oldM.guild.id)) {snipe.edit[oldM.guild.id] = {};}; + //snipe.edit[oldM.guild.id][oldM.channel.id] = {old: oldM, cur: newM}; + + let ts = client.guildconfig.logs.has(oldM.guild.id) && client.guildconfig.logs.get(oldM.guild.id).has('medit') ? client.guildconfig.logs.get(oldM.guild.id).get('medit') : null; + if (ts) {if (oldM.guild.channels.cache.has(ts) && oldM.guild.channels.cache.get(ts).permissionsFor(client.user.id).has("SEND_oldMS")) { + let embed = new Discord.MessageEmbed() + .setTitle('Message Edited') + .setDescription(`Sent by <@${oldM.author.id}> | In <#${oldM.channel.id}> | [See Message](${oldM.url})`) + .setThumbnail(oldM.author.avatarURL({size: 1024})) + .addField("Old Message", "`-> `" + oldM.content.toString()) + .addField("New Message", "`-> `" + newM.content.toString()) + .setColor('8034eb').setFooter("Natsuki", client.user.avatarURL()).setTimestamp(); + if (newM.attachments.size && ['.png', '.jpg', '.gif'].includes(newM.attachments.first().url.slice(newM.attachments.first().url.length - 4, newM.attachments.first().url.length))) {embed.setImage(newM.attachments.first().url);} + oldM.guild.channels.cache.get(ts).send(embed).catch(() => {}); + }} +} \ No newline at end of file diff --git a/events/ready.js b/events/ready.js index d76675e..ea9502d 100644 --- a/events/ready.js +++ b/events/ready.js @@ -2,7 +2,12 @@ const Discord = require('discord.js'); const chalk = require('chalk'); const moment = require('moment'); const mongoose = require('mongoose'); + const GuildSettings = require('../models/guild'); +const BotDataSchema = require('../models/bot'); +const LogData = require('../models/log'); + +const siftStatuses = require('../util/siftstatuses'); var prefix = 'n?'; @@ -11,7 +16,7 @@ module.exports = async client => { try { await mongoose.connect(`mongodb+srv://${config.database.user}:${config.database.password}@${config.database.cluster}.3jpp4.mongodb.net/test`, { useFindAndModify: false, useNewUrlParser: true, dbName: 'Natsuki-Main', useUnifiedTopology: true, useCreateIndex: true - }); + }); } catch (e) { let date = new Date; date = date.toString().slice(date.toString().search(":") - 2, date.toString().search(":") + 6); console.error(`\n${chalk.red('[ERROR]')} >> ${chalk.yellow(`At [${date}] | Occurred while trying to connect to Mongo Cluster`)}`, e); @@ -30,27 +35,44 @@ module.exports = async client => { let responses = { "PLAYING": [ - `in ${client.guilds.cache.size} servers` + `with my darling`, 'RAIN: Shadow Lords' + ,`in ${client.guilds.cache.size} servers` ], "WATCHING": [ - `for ${client.commands.size} commands` + `for ${client.commands.size} commands`, + "I'm not a bad slime, slurp!", "Lelouch rule the world!", + "a slime somehow start an empire", "a fox-maid get her tail fluffed", + "a raccoon-girl and some guy with a shield", "some chick with unusually red hair", + "Mob hit 100", "a really bad harem anime", "The Black Swordsman", + "The Misfit of Demon King Academy" ,`over ${client.guilds.cache.size} servers` ] }; const setR = () => { let type = Object.keys(responses)[Math.floor(Math.random() * Object.keys(responses).length)]; - client.user.setActivity(responses[type][Math.floor(Math.random() * responses[type].length)] + " | " + prefix + "help", {type: type});}; + if (type === "PLAYING") {client.user.setActivity(responses[type][Math.floor(Math.random() * responses[type].length)] + " | " + prefix + "help");} + else {client.user.setActivity(responses[type][Math.floor(Math.random() * responses[type].length)] + " | " + prefix + "help", {type: type});} + } setR(); + setInterval(setR, 14400000); - - const BotDataSchema = require('../models/bot'); - const setP = async () => {let tg; for (tg of Array.from(client.guilds.cache.values)) { + const setPL = async () => {let tg; for (tg of Array.from(client.guilds.cache.values)) { let tguild = await GuildSettings.findOne({gid: tg.id}); if (tguild && tguild.prefix && tguild.prefix.length) {client.guildconfig.prefixes.set(tg.id, tguild.prefix);} + let tl = await LogData.findOne({gid: tg.id}); + if (tl) { + let keys = Object.keys(tl); + let k; for (k of keys) {if (typeof tl[k] === "string" && tl[k].length) { + if (!client.guildconfig.logs.has(tg.id)) {client.guildconfig.logs.set(tg.id, new Map());} + client.guildconfig.logs.get(tg.id).set(k, tl[k]); + }} + } }}; - setP(); - setInterval(setP, 120000); + setPL(); + siftStatuses(); + + setInterval(() => {setPL(); siftStatuses(client, null);}, 120000); let botData = await BotDataSchema.findOne({finder: 'lel'}) ? await BotDataSchema.findOne({finder: 'lel'}) diff --git a/handle/command.js b/handle/command.js index 231d101..2f88f53 100644 --- a/handle/command.js +++ b/handle/command.js @@ -6,10 +6,11 @@ module.exports = client => { var commands = fs.readdirSync('./commands').filter(file => file.endsWith('.js')); console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Commands...')}\n`); for (let commandf of commands) { + if (Object.keys(require.cache).includes(require.resolve(`../commands/${commandf}`))) {delete require.cache[require.resolve(`../commands/${commandf}`)];} var command = require(`../commands/${commandf}`); client.commands.set(command.name, command); if (command.aliases) {command.aliases.forEach(a => client.aliases.set(a, command.name));} - console.log(`${chalk.gray('[LOG] ')} >> ${chalk.blueBright('Loaded Command')} ${chalk.white(command.name)}`); + console.log(`${chalk.gray('[LOG] ')} >> ${chalk.blueBright('Loaded Command')} ${chalk.white(command.name)} ${chalk.blueBright('with')} ${chalk.white(command.aliases && command.aliases.length ? command.aliases.length : 0)} ${chalk.blueBright('aliases')}`); } console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Loaded all Commands')}`); }; \ No newline at end of file diff --git a/handle/event.js b/handle/event.js index 443357b..ead4b3c 100644 --- a/handle/event.js +++ b/handle/event.js @@ -6,8 +6,10 @@ module.exports = client => { let eventFilter = fs.readdirSync('./events/').filter(x => x.endsWith('.js')); console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Events...')}\n`); for (let file of eventFilter) { - let evt = require('../events/' + file); let evtName = file.split('.')[0]; + if (Object.keys(require.cache).includes(require.resolve('../events/' + file))) {delete require.cache[require.resolve('../events/' + file)];} + let evt = require('../events/' + file); + client.removeAllListeners(evtName); client.on(evtName, evt.bind(null, client)); console.log(`${chalk.gray('[LOG] ')} >> ${chalk.blueBright('Loaded Event')} ${chalk.white(evtName)}`); } diff --git a/handle/response.js b/handle/response.js new file mode 100644 index 0000000..1ad9f94 --- /dev/null +++ b/handle/response.js @@ -0,0 +1,16 @@ +const Discord = require('discord.js'); +const fs = require('fs'); +const chalk = require('chalk'); + +module.exports = client => { + var responses = fs.readdirSync('./responses').filter(file => file.endsWith('.js')); + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Getting Responses...')}\n`); + for (let responsef of responses) { + if (Object.keys(require.cache).includes(require.resolve(`../responses/${responsef}`))) {delete require.cache[require.resolve(`../responses/${responsef}`)];} + var response = require(`../responses/${responsef}`); + client.responses.triggers.push([response.name, response.condition]); + client.responses.commands.set(response.name, response); + console.log(`${chalk.gray('[LOG] ')} >> ${chalk.blueBright('Loaded Response')} ${chalk.white(response.name)}`); + } + console.log(`\n${chalk.gray('[BOOT]')} >> ${chalk.blue('Loaded all Responses')}`); +}; \ No newline at end of file diff --git a/models/anime.js b/models/anime.js new file mode 100644 index 0000000..ab0d902 --- /dev/null +++ b/models/anime.js @@ -0,0 +1,27 @@ +const mongoose = require('mongoose'); + +const AniSchema = new mongoose.Schema({ + id: {type: String, unique: true}, + name: String, + japname: String, + plot: String, + publishers: [String], + studios: [String], + airStartDate: Date, + airEndDate: Date, + isComplete: Boolean, + seasons: Number, + episodes: Number, + genres: [String], + tags: [String], + characters: [String], + streamAt: [String], + watchers: Number, + listed: Number, + liked: Number, + rating: Number, + lastUpdate: Date, + thumbnail: String +}); + +module.exports = mongoose.model('anime', AniSchema); \ No newline at end of file diff --git a/models/guild.js b/models/guild.js index d8cd5db..480bd5e 100644 --- a/models/guild.js +++ b/models/guild.js @@ -15,7 +15,11 @@ const guildSchema = new mongoose.Schema({ levelmessage: {type: String, default: '**{{u}}** has reached level **{{l}}**! :tada:'}, levelmessages: {type: Boolean, default: false}, prefix: {type: String, default: ''}, - nostatus: {type: Boolean, default: false} + nostatus: {type: Boolean, default: false}, + starchannel: {type: String, default: ''}, + starreq: {type: Number, default: 5}, + starsenabled: {type: Boolean, default: false}, + blacklisted: {type: Boolean, default: false} }); module.exports = mongoose.model("guild", guildSchema); \ No newline at end of file diff --git a/models/log.js b/models/log.js index 7425e29..6afb660 100644 --- a/models/log.js +++ b/models/log.js @@ -2,27 +2,29 @@ const mongoose = require('mongoose'); const logSchema = new mongoose.Schema({ gid: {type: String, unique: true}, - mdelete: {type: Boolean, default: true}, - medit: {type: Boolean, default: true}, - chnew: {type: Boolean, default: true}, - chedit: {type: Boolean, default: true}, - chdelete: {type: Boolean, default: true}, - vcjoin: {type: Boolean, default: false}, - vcleave: {type: Boolean, default: false}, - servervcmute: {type: Boolean, default: true}, - servervcdeafen: {type: Boolean, default: true}, - kick: {type: Boolean, default: true}, - ban: {type: Boolean, default: true}, - mute: {type: Boolean, default: true}, - warn: {type: Boolean, default: true}, - giverole: {type: Boolean, default: true}, - takerole: {type: Boolean, default: true}, - addrole: {type: Boolean, default: true}, - editrole: {type: Boolean, default: true}, - deleterole: {type: Boolean, default: true}, - serverjoin: {type: Boolean, default: false}, - serverleave: {type: Boolean, default: false}, - nickname: {type: Boolean, default: true}, - username: {type: Boolean, default: false}, - avatar: {type: Boolean, default: false}, -}); \ No newline at end of file + mdelete: {type: String, default: ''}, + medit: {type: String, default: ''}, + chnew: {type: String, default: ''}, + chedit: {type: String, default: ''}, + chdelete: {type: String, default: ''}, + vcjoin: {type: String, default: ''}, + vcleave: {type: String, default: ''}, + servervcmute: {type: String, default: ''}, + servervcdeafen: {type: String, default: ''}, + kick: {type: String, default: ''}, + ban: {type: String, default: ''}, + mute: {type: String, default: ''}, + warn: {type: String, default: ''}, + giverole: {type: String, default: ''}, + takerole: {type: String, default: ''}, + addrole: {type: String, default: ''}, + editrole: {type: String, default: ''}, + deleterole: {type: String, default: ''}, + serverjoin: {type: String, default: ''}, + serverleave: {type: String, default: ''}, + nickname: {type: String, default: ''}, + username: {type: String, default: ''}, + avatar: {type: String, default: ''} +}); + +module.exports = mongoose.model('log', logSchema); \ No newline at end of file diff --git a/models/mod.js b/models/mod.js new file mode 100644 index 0000000..9d07e15 --- /dev/null +++ b/models/mod.js @@ -0,0 +1,27 @@ +const mongoose = require('mongoose'); + +const ModModel = new mongoose.Schema({ + gid: {type: String, unique: true}, + cases: {type: [{ + members: [String], + punishment: String, + reason: String, + status: String, + moderators: [String], + notes: [String], + history: [String], + issued: Date + }], default: []}, + rules: {type: [{ + name: String, + description: String, + punishment: String, + automod: String + }], default: []}, + auto: {type: Object, default: {}}, + warnings: {type: Object, default: {}}, + maxWarnings: {type: Number, default: 0}, + onMaxWarn: {type: String, default: 'kick'} +}); + +module.exports = mongoose.model('mod', ModModel); \ No newline at end of file diff --git a/models/responses.js b/models/responses.js new file mode 100644 index 0000000..4f83bbf --- /dev/null +++ b/models/responses.js @@ -0,0 +1,9 @@ +const mongoose = require('mongoose'); + +const ResponseSchema = new mongoose.Schema({ + gid: {type: String, unique: true}, + responses: {type: Map, default: new Map()}, + bindings: {type: Map, default: new Map()} +}); + +module.exports = mongoose.model('responses', ResponseSchema); \ No newline at end of file diff --git a/models/saves.js b/models/saves.js new file mode 100644 index 0000000..a15d1dc --- /dev/null +++ b/models/saves.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); + +const SaveSchema = new mongoose.Schema({ + name: {type: String, unique: true}, + saves: {type: Map, default: new Map()} +}); + +module.exports = mongoose.model('saves', SaveSchema); \ No newline at end of file diff --git a/models/secretsanta.js b/models/secretsanta.js new file mode 100644 index 0000000..7f591cb --- /dev/null +++ b/models/secretsanta.js @@ -0,0 +1,17 @@ +const mongoose = require('mongoose'); + +const SS = new mongoose.Schema({ + ssid: {type: String, unique: true}, + owner: String, + start: String, + end: String, + anon: Boolean, + info: String, + notes: String, + members: [{name: String, id: String, info: String}], + started: Boolean, + spend: String, + assignments: [{name: String, assignedTo: String}] +}); + +module.exports = mongoose.model('ss', SS); \ No newline at end of file diff --git a/models/starboard.js b/models/starboard.js new file mode 100644 index 0000000..df758e5 --- /dev/null +++ b/models/starboard.js @@ -0,0 +1,10 @@ +const mongoose = require('mongoose'); + +const StarSchema = new mongoose.Schema({ + gid: {type: String, unique: true}, + stars: {type: Object, default: {}}, + starCount: {type: Object, default: {}}, + serverStarCount: {type: Number, default: 0} +}); + +module.exports = mongoose.model('stars', StarSchema); \ No newline at end of file diff --git a/models/statuses.js b/models/statuses.js new file mode 100644 index 0000000..e070c1a --- /dev/null +++ b/models/statuses.js @@ -0,0 +1,8 @@ +const mongoose = require('mongoose'); + +const StatusSchema = new mongoose.Schema({ + f: String, + statuses: [{id: String, clear: Date}] +}); + +module.exports = new mongoose.model('statuses', StatusSchema); \ No newline at end of file diff --git a/models/user.js b/models/user.js index fb9122c..9b6f51c 100644 --- a/models/user.js +++ b/models/user.js @@ -8,12 +8,16 @@ const UserSchema = new mongoose.Schema({ statusclearmode: {type: String, default: 'auto'}, statusclearat: {type: Date, default: null}, statussetat: {type: Date, default: null}, + statusshowcleartime: {type: Boolean, default: true}, + statusshowsettime: {type: Boolean, default: true}, support: {type: Boolean, default: false}, staff: {type: Boolean, default: false}, admin: {type: Boolean, default: false}, developer: {type: Boolean, default: false}, blacklisted: {type: Boolean, default: false}, - donator: {type: Boolean, default: false} + donator: {type: Boolean, default: false}, + bio: {type: String, default: ''}, + color: {type: String, default: ''} }); module.exports = mongoose.model("user", UserSchema); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 93413ab..93dd4f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1428,9 +1428,9 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { "ms": "2.1.2" } @@ -3789,11 +3789,6 @@ "ip-address": "^5.8.8" } }, - "@lavaclient/types": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@lavaclient/types/-/types-1.0.2.tgz", - "integrity": "sha512-7OmlW8PD0mU4n4qD79YiT86mJDMmV0qoPLdXnepqv067gf9204p5tXRrs/cr+vGljqewELyUtUuQd74rRTZViw==" - }, "@nodelib/fs.scandir": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz", @@ -7315,22 +7310,6 @@ "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" }, - "lavaclient": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lavaclient/-/lavaclient-3.1.2.tgz", - "integrity": "sha512-kfY6/3zkWyv3fc+G/Br8aEWrRBxwhgQy8xA3cJmIAxRGey8TC4kCM0gXKbAQDqcEErSAakRln18f+uwUkOExPA==", - "requires": { - "@lavaclient/types": "1.0.2", - "ws": "^7.3.1" - }, - "dependencies": { - "ws": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.1.tgz", - "integrity": "sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==" - } - } - }, "lavalink": { "version": "2.10.2", "resolved": "https://registry.npmjs.org/lavalink/-/lavalink-2.10.2.tgz", diff --git a/package.json b/package.json index d11bfef..de3ec0a 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,6 @@ "ffmpeg-static": "^4.2.7", "gblapi.js": "^2.0.5", "heroku": "^7.42.1", - "lavaclient": "^3.1.2", "lavaqueue": "^3.1.6", "manyitems": "^1.0.2", "moment": "^2.28.0", diff --git a/responses/decide.js b/responses/decide.js new file mode 100644 index 0000000..e484b6b --- /dev/null +++ b/responses/decide.js @@ -0,0 +1,29 @@ +const Discord = require('discord.js'); + +module.exports = { + name: "decide", + meta: { + category: "", + perms: "", + staff: false, + vip: "", + serverPerms: [], + writtenBy: "", + serverOnly: false + }, + tags: [], + help: new Discord.MessageEmbed() + .setTitle("Help -> ") + .setDescription("") + .addField("Syntax", "``"), + async condition (message, msg, args, cmd, prefix, mention, client) {return msg.split(/\s+/gm).length > 3 && (msg.startsWith(`<@${client.user.id}>`) || msg.startsWith(`<@!${client.user.id}>`));}, + async execute(message, msg, args, cmd, prefix, mention, client) { + let e = message.content.split(/\s+/g); e.shift(); + let options = e.join(" ").split(/(?:(?:\s+)[oO][rR] [sS][hH][oO][uU][lL][dD] [iI](?:\s+)|,(?:\s*)[oO][rR] [sS][hH][oO][uU][lL][dD] [iI](?:\s+)|(?:\s+)[oO][rR] [dD][oO] [iI](?:\s+)|,(?:\s*)[dD][oO] [iI](?:\s+)|,(?:\s*)[sS][hH][oO][uU][lL][dD] [iI](?:\s+)|,(?:\s*)[oO][rR] [dD][oO] [iI](?:\s+)|,(?:\s*)[oO][rR](?:\s+)|(?:\s+)[oO][rR](?:\s+)|,(?:\s*))/gm); + //console.log(e, options); + if (options.length < 2) {return;} + let cleanups = ['should i ', 'do i ']; + let option; for (option of options) {let c; for (c of cleanups) {if (option.trim().toLowerCase().startsWith(c)) {options[options.indexOf(option)] = option.trim().slice(c.length);}}} + return message.channel.send(`${['You should', 'How about', 'Hmmm... I pick', 'How about you', 'I think you should'][Math.floor(Math.random() * 5)]} ${options[Math.floor(Math.random() * options.length)]}.`); + } +}; \ No newline at end of file diff --git a/template.js b/template.js index a16aa35..74475a2 100644 --- a/template.js +++ b/template.js @@ -3,6 +3,16 @@ const Discord = require('discord.js'); module.exports = { name: "", aliases: [], + meta: { + category: "", + perms: "", + staff: false, + vip: "", + serverPerms: [], + writtenBy: "", + serverOnly: false + }, + tags: [], help: new Discord.MessageEmbed() .setTitle("Help -> ") .setDescription("") @@ -19,6 +29,16 @@ const Discord = require('discord.js'); module.exports = { name: "", aliases: [], + meta: { + category: "", + perms: "", + staff: false, + vip: "", + serverPerms: [], + writtenBy: "", + serverOnly: false + }, + tags: [], help: "", async execute(message, msg, args, cmd, prefix, mention, client) { if (!args.length) {return message.channel.send(`Syntax: \`${prefix}\``);} diff --git a/test.js b/test.js new file mode 100644 index 0000000..759f5ca --- /dev/null +++ b/test.js @@ -0,0 +1,49 @@ +let tss = { + members: [ + { + name: "wubzy", + id: "4545", + info: "stuff" + }, + { + name: "slushie", + id: "3434", + info: "wow" + }, + { + name: "kyusa", + id: "6767", + info: "e" + }, + { + name: "swag", + id: "8989", + info: "xd" + }, + { + name: "doge", + id: "0101", + info: "homks" + }, + { + name: "vincent", + id: "6666", + info: "shrekt" + } + ], + assignments: [] +} + +let dm = []; let rm; +let m; for (m of tss.members) {dm.push({name: m.id, assignedTo: null});} +for (m of dm) { + while (true) { + let rm = tss.members[Math.floor(Math.random() * tss.members.length)]; + let exists = false; + let cdm; for (cdm of dm) {if (!exists) {exists = cdm.assignedTo === rm.id;}} + if (!exists && rm.id !== m.name) {dm[dm.indexOf(m)] = {name: m.name, assignedTo: rm.id}; break;} + } +} +tss.assignments = dm; + +console.log(tss); \ No newline at end of file diff --git a/util/ask.js b/util/ask.js new file mode 100644 index 0000000..7418954 --- /dev/null +++ b/util/ask.js @@ -0,0 +1,12 @@ +module.exports = async (message, toAsk, time, nf) => { + let msg = await message.channel.send(toAsk); + let filter = nf ? () => true : m => m.author.id === message.author.id; + try { + let collected = await msg.channel.awaitMessages(filter, {max: 1, errors: ['time'], time: time}); + collected = collected.first().content; + return collected; + } catch { + message.reply("This question has timed out! Please try again."); + return null; + } +}; \ No newline at end of file diff --git a/util/cachestatus.js b/util/cachestatus.js new file mode 100644 index 0000000..da88db0 --- /dev/null +++ b/util/cachestatus.js @@ -0,0 +1,11 @@ +const StatusCache = require('../models/statuses'); + +module.exports = async (id, time) => { + let statuses = await StatusCache.findOne({f: 'lol'}) || new StatusCache({f: 'lol', statuses: []}); + let exists = false; + let status; for (status of statuses.statuses) { + if (status.id === id) {statuses.statuses[statuses.statuses.indexOf(status)].clear = time; exists = true;} + } + if (!exists) {statuses.statuses.push({id: id, clear: time});} + return statuses.save(); +}; \ No newline at end of file diff --git a/util/makeid.js b/util/makeid.js new file mode 100644 index 0000000..215eaa4 --- /dev/null +++ b/util/makeid.js @@ -0,0 +1,10 @@ +module.exports = (length) => { + let result = ''; + let characters = 'abcdefghijklmnopqrstuvwxyz0123456789'; + let charactersLength = characters.length; + let i; + for (i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} \ No newline at end of file diff --git a/util/mention.js b/util/mention.js index dbae079..14ead11 100644 --- a/util/mention.js +++ b/util/mention.js @@ -1,4 +1,6 @@ -const mongooes= require('mongoose'); +const Discord = require('discord.js'); +const moment = require('moment'); + const UserData = require('../models/user'); const GuildSettings = require('../models/guild'); @@ -6,5 +8,10 @@ module.exports = async(message, msg, args, cmd, prefix, mention, client) => { let tu = await UserData.findOne({uid: mention.id}); let tg = message.guild ? await GuildSettings.findOne({gid: message.guild.id}) : null; if (tg && tg.nostatus) {return;} - if (tu) {if (tu.statusmsg.length) {return message.reply(`That user ${tu.statustype === 'dnd' ? 'wishes not to be disturbed' : 'is AFK'}. Reason: ${tu.statusmsg}`);}} + if (client.misc.statusPings.has(message.guild.id) && client.misc.statusPings.get(message.guild.id).has(mention.id) && new Date().getTime() - client.misc.statusPings.get(message.guild.id).get(mention.id).getTime() < 300000) {return;} + if (tu && tu.statusmsg.length) { + if (!client.misc.statusPings.has(message.guild.id)) {client.misc.statusPings.set(message.guild.id, new Discord.Collection());} + client.misc.statusPings.get(message.guild.id).set(mention.id, new Date()); + return message.reply(`That user ${tu.statustype === 'dnd' ? 'wishes not to be disturbed' : 'is AFK'}. Reason: \`${tu.statusmsg}\`. (This status was set ${moment(tu.statussetat.getTime()).fromNow()})`); + } }; \ No newline at end of file diff --git a/util/pagination.d.ts b/util/pagination.d.ts new file mode 100644 index 0000000..8517714 --- /dev/null +++ b/util/pagination.d.ts @@ -0,0 +1,33 @@ +import { MessageEmbed, Message, Client } from 'discord.js'; +export declare class Pagination { + title: string; + pages: Page[]; + zeroPage: Page | MessageEmbed; + pageTemplate: MessageEmbed; + message: Message; + timeout: Number; + description: string; + activationMessage: Message; + client: Client; + currentpos: number; + constructor(title: string, pages: Page[], zeroPage: Page | MessageEmbed, client: Client, message: Message, activationMessage: Message, timeout: number, description?: string, pageTemplate?: MessageEmbed); + addPage(page: Page): Pagination; + render(pos: number): Pagination; + nextPage(): Pagination; + prevPage(): Pagination; + destroy(delmsg?: boolean, fmsg?: Message): Pagination; + resetTimeout(newTimeout?: number): Pagination; + init(): Pagination; +} +export declare class Page { + items: PageItem[]; + title: string; + description: string; + constructor(title: string, items: PageItem[], description?: string); + addItem(item: PageItem): Page; +} +interface PageItem { + title: string; + text: string; +} +export {}; diff --git a/util/pagination.js b/util/pagination.js new file mode 100644 index 0000000..bbb8044 --- /dev/null +++ b/util/pagination.js @@ -0,0 +1,86 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Page = exports.Pagination = void 0; +const discord_js_1 = require("discord.js"); +class Pagination { + constructor(title, pages, zeroPage, client, message, activationMessage, timeout, description, pageTemplate) { + this.currentpos = 0; + this.title = title; + this.pages = pages; + this.zeroPage = zeroPage; + this.message = message; + this.timeout = timeout; + this.activationMessage = activationMessage; + this.client = client; + this.description = description ? description : `Requested by ${activationMessage.guild ? activationMessage.member.displayName : activationMessage.author.username}.`; + this.pageTemplate = pageTemplate + ? pageTemplate + : new discord_js_1.MessageEmbed() + .setDescription(this.description) + .addField('Navigation', `Click or tap the arrows below this message to navigate through the pages!\n\n*This menu will timeout in ${this.timeout}ms.`) + .setColor('c375f0') + .setFooter('Natsuki', this.client.user.avatarURL()) + .setTimestamp(); + } + ; + addPage(page) { + this.pages.push(page); + return this; + } + ; + render(pos) { + let page = this.pages[this.currentpos]; + let pageEmbed = new discord_js_1.MessageEmbed() + .setTitle(`${this.title} -> ${page.title}`) + .setDescription(`${this.pageTemplate.description ? this.pageTemplate.description : this.description}\n\n${page.description}`) + .setColor(this.pageTemplate.hexColor ? this.pageTemplate.hexColor : 'c375f0') + .setFooter(this.pageTemplate.footer ? `${this.pageTemplate.footer.text} | Page ${this.currentpos + 1} of ${this.pages.length}` : `Natsuki | Page ${this.currentpos + 1} of ${this.pages.length}`) + .setTimestamp(); + let item; + for (item of page.items) { + pageEmbed.addField(item.title, item.text); + } + if (this.pageTemplate.thumbnail) { + pageEmbed.setThumbnail(this.pageTemplate.thumbnail.url); + } + this.message.edit(pageEmbed); + return this; + } + ; + nextPage() { + return this.render(this.currentpos < (this.pages.length - 1) ? this.currentpos + 1 : this.currentpos); + } + ; + prevPage() { + return this.render(this.currentpos > 0 ? this.currentpos - 1 : this.currentpos); + } + ; + destroy(delmsg, fmsg) { + return this; + } + ; + resetTimeout(newTimeout) { + return this; + } + ; + init() { + return this; + } + ; +} +exports.Pagination = Pagination; +class Page { + constructor(title, items, description) { + this.items = []; + this.title = title; + this.items = items; + this.description = description; + } + ; + addItem(item) { + this.items.push(item); + return this; + } + ; +} +exports.Page = Page; diff --git a/util/response/filterresponse.js b/util/response/filterresponse.js new file mode 100644 index 0000000..3813574 --- /dev/null +++ b/util/response/filterresponse.js @@ -0,0 +1,11 @@ +module.exports = async (member, client, text) => { + text = text + .replace(/(?:{{member}}|{{m}})/gm, member.displayName) + .replace(/(?:{{membercount}}|{{mc}})/gm, `${member.guild.members.cache.size}`) + .replace(/(?:{{owner}}|{{o}})/gm, member.guild.owner.displayName) + .replace(/(?:{{ping}}|{{mp}}|{{memberping}}|{{p}})/gm, `<@${member.id}>`) + .replace(/(?:{{s}}|{{server}}|{{servername}}|{{sn}})/gm, member.guild.name) + .replace(/{{n}}/gm, '\n') + .replace(/{{nn}}/gm, '\n\n'); + return text; +}; \ No newline at end of file diff --git a/util/response/getresponse.js b/util/response/getresponse.js new file mode 100644 index 0000000..55bc7f0 --- /dev/null +++ b/util/response/getresponse.js @@ -0,0 +1,9 @@ +const Responses = require('../../models/responses'); + +module.exports = async (message, name) => { + let tr = await Responses.findOne({gid: message.guild.id}); + if (!tr) {message.reply("This server does not have any responses saved!"); return null;} + if (!tr.responses.has(name.toLowerCase())) {message.reply("I don't have that response saved here."); return null;} + message.delete(); + return tr.responses.get(name.toLowerCase()); +}; \ No newline at end of file diff --git a/util/response/parseresponse.js b/util/response/parseresponse.js new file mode 100644 index 0000000..5705614 --- /dev/null +++ b/util/response/parseresponse.js @@ -0,0 +1,62 @@ +const {Tag} = require('../tag'); +const {TagFilter} = require('../tagfilter'); + +module.exports = async (message, client, args) => { + let options = new TagFilter([ + new Tag(['em', '-embed'], 'embed', 'toggle'), + new Tag(['-msg', 'message'], 'message', 'toggle'), + + new Tag(['name', 'n'], 'name', 'append'), + new Tag(['ch', 'channel'], 'channel', 'append'), + + new Tag(['text', 'txt'], 'text', 'append'), + + new Tag(['title', 't'], 'title', 'append'), + new Tag(['description', 'desc', 'd'], 'description', 'append'), + new Tag(['fieldname', 'fn', 'newfield', 'nf'], 'fieldnames', 'listAppend'), + new Tag(['fieldtext', 'ft', 'fieldcontent', 'fc'], 'fieldtexts', 'listAppend'), + new Tag(['image', 'i'], 'image', 'append'), + new Tag(['thumbnail', 'thumb', 'th'], 'thumbnail', 'append'), + new Tag(['servericonthumbnail', 'serverthumbnail', 'sit', 'st'], 'guildthumb', 'toggle'), + new Tag(['servericonimage', 'serverimage', 'sii', 'si'], 'guildimage', 'toggle'), + new Tag(['color', 'colour', 'col', 'c'], 'color', 'append'), + ]).test(args.join(" ")); + + if (options.fieldnames && options.fieldnames.length) { + if (!options.fieldtexts || !options.fieldtexts.length || options.fieldnames.length !== options.fieldtexts.length) { + message.reply("You must have the same amount of field names as you do field texts."); return null; + } + } + if (options.embed) { + if (options.fieldnames && options.fieldnames.length > 10) {message.reply("You can't have more than 10 fields!"); return null;} + if (options.color && options.color.length && (![3, 6].includes(options.color.length))) {message.reply("Your color must be a hex value 3 or 6 digits long."); return null;} + if (options.title && options.title.length > 65) {message.reply("Your title should be less than 65 characters, please :)"); return null;} + if (options.description && options.description.length > 750) {message.reply("Your description should be less than 750 characters."); return null;} + if ((!options.title || !options.title.length) || (!options.description || !options.description.length)) {message.reply("You need have a title and a description!"); return null;} + if (options.image && options.image.length > 300) {message.reply("Your image URL is a bit too long. Try shortening the URL or hosting it somewhere like imgur."); return null;} + if (options.thumbnail && options.image.thumbnail > 300) {message.reply("Your thumbnail URL is a bit too long. Try shortening the URL or hosting it somewhere like imgur."); return null;} + if (options.fieldnames) { + let fn; let ft; + for (fn of options.fieldnames) { + if (fn.length > 65) {message.reply("One of your field names is longer than 65 characters. Please shorten it!"); return null;} + } for (ft of options.fieldtexts) { + if (ft.length > 500) {message.reply("One of your field texts is longer than 500 characters. Please shorten it!"); return null;} + } + } + if (options.guildthumb) {options.thumbnail = message.guild.iconURL({size: 2048});} + if (options.guildimage) {options.image = message.guild.iconURL({size: 2048});} + } else if (options.message) { + if (options.text && options.text.length > 750) {message.reply("Please keep your message text under 750 characters!"); return null;} + if (!options.text || !options.text.length) {return message.reply("You must specify -text for your message.");} + } else {message.reply("You must specify either '-message' or '-embed' for the format of your response."); return null;} + + if (options.channel && options.channel.length) {if (!options.channel.match(/^<#(?:\d+)>$/) && !message.guild.channels.cache.has(options.channel.slice(options.channel.search(/\d/), options.channel.search(">")))) {message.reply("You must use a valid channel in this server."); return null;}} + + if (options.name && options.name.length) { + options.name = options.name.toLowerCase(); + if (options.name.length > 10) {message.reply("The option name must be less than 10 characters."); return null;} + if (!options.name.match(/^[a-z0-9-_]+$/)) {message.reply("You can only use a-z, numbers, hyphens, and underscores."); return null;} + } + + return options; +}; \ No newline at end of file diff --git a/util/response/saveresponse.js b/util/response/saveresponse.js new file mode 100644 index 0000000..f983666 --- /dev/null +++ b/util/response/saveresponse.js @@ -0,0 +1,15 @@ +const Responses = require('../../models/responses'); + +module.exports = async (options, message) => { + try { + if (!options) {return null;} + if (!options.name || !options.name.length) {message.reply("You need to have a name in order to save a response."); return null;} + let sr = await Responses.findOne({gid: message.guild.id}) ? await Responses.findOne({gid: message.guild.id}) : new Responses({gid: message.guild.id}); + if (sr.responses.has(options.name)) {message.reply("You already have a response with that name. Use `edit` instead."); return null;} + sr.responses.set(options.name, options); + sr.save(); + message.channel.send("Response added!"); + } catch {message.reply("There seems to have been an error in saving your response. If this persists, please contact the developers or join the support sever."); return null;} + + return options; +}; \ No newline at end of file diff --git a/util/response/sendresponse.js b/util/response/sendresponse.js new file mode 100644 index 0000000..74ec7e1 --- /dev/null +++ b/util/response/sendresponse.js @@ -0,0 +1,22 @@ +const Discord = require('discord.js'); + +const filterResponse = require('./filterresponse'); + +module.exports = async(member, channel, mode, client, options) => { + if (!options) {return;} + if (options.channel && options.channel.length) {channel = channel.guild.channels.cache.get(options.channel.slice(options.channel.search(/\d/), options.channel.search('>')));} + try { + if (options.embed) { + var responseEmbed = new Discord.MessageEmbed().setTitle(options.title).setDescription(await filterResponse(member, client, options.description)); + if (options.fieldnames && options.fieldnames.length) {let i; for (i=0;i status.clear.getTime()) { + if (lookFor && status.id !== lookFor) {continue;} + let tu = await UserData.findOne({uid: status.id}); + if (tu) { + tu.statusmsg = ''; + tu.statustype = ''; + tu.save(); + let u = await client.users.fetch(status.id); + if (u) {u.send("Heya! Your status has been set for 12 hours, so I've cleared it for you.").catch(() => {});} + } + } else {ns.push(status);} + } + statusesm.statuses = ns; + return statusesm.save(); +}; \ No newline at end of file diff --git a/util/tag.d.ts b/util/tag.d.ts index 4421210..3c579ea 100644 --- a/util/tag.d.ts +++ b/util/tag.d.ts @@ -5,5 +5,5 @@ export declare class Tag { constructor(triggers: string[], tagName: string, filterType: TagFilterType); addTrigger(trigger: string): Tag; } -declare type TagFilterType = "append" | "toggle"; +declare type TagFilterType = "append" | "toggle" | "listAppend"; export {}; diff --git a/util/tagfilter.d.ts b/util/tagfilter.d.ts index 0da5548..ed480c4 100644 --- a/util/tagfilter.d.ts +++ b/util/tagfilter.d.ts @@ -6,5 +6,5 @@ export declare class TagFilter { constructor(tags: Tag[]); test(text: string): object; } -declare type TagFilterType = "append" | "toggle"; +declare type TagFilterType = "append" | "toggle" | "listAppend"; export {}; diff --git a/util/tagfilter.js b/util/tagfilter.js index ed77f12..d55ec5f 100644 --- a/util/tagfilter.js +++ b/util/tagfilter.js @@ -20,20 +20,40 @@ class TagFilter { test(text) { var filtered = {}; var reading = null; + var filterType; + var ticks = {}; let words = text.trim().split(/\s+/g); let word; for (word of words) { if (word.startsWith('-') && word.length > 1 && this.triggers.has(word.trim())) { - reading = this.filterTypes.get(this.triggers.get(word.trim())) == "toggle" ? null : word.trim(); + filterType = this.filterTypes.get(this.triggers.get(word.trim())); + reading = !['append', 'listAppend'].includes(filterType) ? null : word.trim(); if (!reading) { filtered[`${this.triggers.get(word.trim())}`] = true; } else { - filtered[`${this.triggers.get(reading)}`] = ''; + filtered[`${this.triggers.get(reading)}`] = filterType == 'append' ? '' : Array.isArray(filtered[`${this.triggers.get(reading)}`]) ? filtered[`${this.triggers.get(reading)}`] : []; + } + if (filterType == "listAppend") { + if (ticks[`${this.triggers.get(word.trim())}`] && ticks[`${this.triggers.get(word.trim())}`].length) { + filtered[`${this.triggers.get(word.trim())}`].push(ticks[`${this.triggers.get(word.trim())}`]); + } + ticks[`${this.triggers.get(word.trim())}`] = ''; } } else if (reading) { - filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`; + if (filterType == "listAppend") { + ticks[`${this.triggers.get(reading)}`] += ` ${word}`; + } + else { + filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`; + } + } + } + let tick; + for (tick of Object.keys(ticks)) { + if (ticks[tick].length) { + filtered[tick].push(ticks[tick]); } } let key; @@ -41,6 +61,12 @@ class TagFilter { if (typeof filtered[key] == 'string') { filtered[key] = filtered[key].trim(); } + else if (Array.isArray(filtered[key])) { + let subkey; + for (subkey of filtered[key]) { + filtered[key][filtered[key].indexOf(subkey)] = subkey.trim(); + } + } } return filtered; } diff --git a/util/ts/pagination.ts b/util/ts/pagination.ts new file mode 100644 index 0000000..14b1ada --- /dev/null +++ b/util/ts/pagination.ts @@ -0,0 +1,119 @@ +import {MessageEmbed, Message, Client} from 'discord.js'; + +import wait = require('../../util/wait'); + +export class Pagination { + title: string; + pages: Page[]; + zeroPage: Page | MessageEmbed; + pageTemplate: MessageEmbed; + message: Message; + timeout: Number; + description: string; + activationMessage: Message; + client: Client; + currentpos: number = 0; + + + + constructor (title: string, pages: Page[], zeroPage: Page | MessageEmbed, client: Client, message: Message, activationMessage: Message, timeout: number, description?: string, pageTemplate?: MessageEmbed) { + this.title = title; + + let tpages = []; + tpages.push(zeroPage); + let tpage: Page; for (tpage of pages) {tpages.push(tpage);} + this.pages = tpages; + + this.zeroPage = zeroPage; + this.message = message; + this.timeout = timeout; + this.activationMessage = activationMessage; + this.client = client; + + this.description = description ? description : `Requested by ${activationMessage.guild ? activationMessage.member.displayName : activationMessage.author.username}.`; + + this.pageTemplate = pageTemplate + ? pageTemplate + : new MessageEmbed() + .setDescription(this.description) + .addField('Navigation', `Click or tap the arrows below this message to navigate through the pages!\n\n*This menu will timeout in ${this.timeout}ms.`) + .setColor('c375f0') + .setFooter('Natsuki', this.client.user.avatarURL()) + .setTimestamp(); + }; + + + + public addPage(page: Page): Pagination { + this.pages.push(page); + return this; + }; + + public render(pos: number): Pagination { + let page = this.pages[this.currentpos]; + let pageEmbed: MessageEmbed = new MessageEmbed() + .setTitle(`${this.title} -> ${page.title}`) + .setDescription(`${this.pageTemplate.description ? this.pageTemplate.description : this.description}\n\n${page.description}`) + .setColor(this.pageTemplate.hexColor ? this.pageTemplate.hexColor : 'c375f0') + .setFooter(this.pageTemplate.footer ? `${this.pageTemplate.footer.text} | Page ${this.currentpos + 1} of ${this.pages.length}` : `Natsuki | Page ${this.currentpos + 1} of ${this.pages.length}`) + .setTimestamp(); + let item: PageItem; for (item of page.items) {pageEmbed.addField(item.title, item.text);} + if (this.pageTemplate.thumbnail) {pageEmbed.setThumbnail(this.pageTemplate.thumbnail.url);} + + this.message.edit(pageEmbed); + + return this; + }; + + public nextPage(): Pagination { + return this.render(this.currentpos < (this.pages.length - 1) ? this.currentpos + 1 : this.currentpos); + }; + + public prevPage(): Pagination { + return this.render(this.currentpos > 0 ? this.currentpos - 1 : this.currentpos); + }; + + public destroy(delmsg?: boolean, fmsg?: Message): Pagination { + return this; + }; + + public resetTimeout(newTimeout?: number): Pagination { + return this; + }; + + public init(): Pagination { + + + return this; + }; + +} + + + +export class Page { + items: PageItem[] = []; + title: string; + description: string; + + + + constructor(title: string, items: PageItem[], description?: string) { + this.title = title; + this.items = items; + this.description = description; + }; + + + + public addItem(item: PageItem): Page { + this.items.push(item); + return this; + }; + +} + +interface PageItem { + title: string, + text: string +} \ No newline at end of file diff --git a/util/ts/tag.ts b/util/ts/tag.ts index 86c7ce2..380d46f 100644 --- a/util/ts/tag.ts +++ b/util/ts/tag.ts @@ -22,4 +22,4 @@ export class Tag { }; } -type TagFilterType = "append" | "toggle"; \ No newline at end of file +type TagFilterType = "append" | "toggle" | "listAppend"; \ No newline at end of file diff --git a/util/ts/tagfilter.ts b/util/ts/tagfilter.ts index fcf04f7..d0fcbdf 100644 --- a/util/ts/tagfilter.ts +++ b/util/ts/tagfilter.ts @@ -21,25 +21,42 @@ export class TagFilter { public test(text: string): object { var filtered: object = {}; var reading: string = null; + var filterType: TagFilterType; + var ticks = {}; let words = text.trim().split(/\s+/g); let word: string; for (word of words) { if (word.startsWith('-') && word.length > 1 && this.triggers.has(word.trim())) { - reading = this.filterTypes.get(this.triggers.get(word.trim())) == "toggle" ? null : word.trim(); + filterType = this.filterTypes.get(this.triggers.get(word.trim())); + reading = !['append', 'listAppend'].includes(filterType) ? null : word.trim(); if (!reading) {filtered[`${this.triggers.get(word.trim())}`] = true;} - else {filtered[`${this.triggers.get(reading)}`] = '';} + else {filtered[`${this.triggers.get(reading)}`] = filterType == 'append' ? '' : Array.isArray(filtered[`${this.triggers.get(reading)}`]) ? filtered[`${this.triggers.get(reading)}`] : [];} + if (filterType == "listAppend") { + if (ticks[`${this.triggers.get(word.trim())}`] && ticks[`${this.triggers.get(word.trim())}`].length) {filtered[`${this.triggers.get(word.trim())}`].push(ticks[`${this.triggers.get(word.trim())}`]);} + ticks[`${this.triggers.get(word.trim())}`] = ''; + } } else if (reading) { - filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`; + if (filterType == "listAppend") {ticks[`${this.triggers.get(reading)}`] += ` ${word}`;} + else {filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`;} } } + + let tick: string; for (tick of Object.keys(ticks)) { + if (ticks[tick].length) {filtered[tick].push(ticks[tick]);} + } let key: string; for (key of Object.keys(filtered)) { if (typeof filtered[key] == 'string') {filtered[key] = filtered[key].trim();} + else if (Array.isArray(filtered[key])) { + let subkey: string; for (subkey of filtered[key]) { + filtered[key][filtered[key].indexOf(subkey)] = subkey.trim(); + } + } } return filtered; }; } -type TagFilterType = "append" | "toggle"; \ No newline at end of file +type TagFilterType = "append" | "toggle" | "listAppend"; \ No newline at end of file