diff --git a/.idea/Natsuki.iml b/.idea/Natsuki.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/Natsuki.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c4ee72c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/commands/avatar.js b/commands/avatar.js new file mode 100644 index 0000000..8f474cc --- /dev/null +++ b/commands/avatar.js @@ -0,0 +1,26 @@ +const Discord = require('discord.js'); +const {Tag} = require('../util/tag'); +const {TagFilter} = require('../util/tagfilter'); + +module.exports = { + name: "avatar", + aliases: ['av', 'a', 'pfp'], + help: "Use `{{p}}avatar` to get your own profile picture, or mention someone to get theirs!", + async execute(message, msg, args, cmd, prefix, mention, client) { + let member = !args.length ? message.author : mention ? mention : client.users.cache.has(args[0]) ? client.users.cache.get(args[0]) : message.author; + let name = !args.length ? message.member ? message.member.displayName : message.author.username : mention ? mention.username : client.users.cache.has(args[0]) ? client.users.cache.get(args[0]).username : message.author.username; + let options = new TagFilter([ + new Tag(['small', 's', 'mini', 'm'], 'small', 'toggle'), + new Tag(['verysmall', 'vsmall', '-vs', 'xs'], 'vsmall', 'toggle') + ]).test(args.join(" ")); + try { + let avem = new Discord.MessageEmbed() + .setTitle(`${name.endsWith('s') ? `${name}'` : `${name}'s`} Avatar`) + .setImage(member.avatarURL({size: options.vsmall ? 128 : options.small ? 256 : 2048})) + .setColor('c375f0') + .setFooter("Natsuki", client.user.avatarURL()) + if (!options.vsmall) {avem.setTimestamp();} + return message.channel.send(avem); + } catch {return message.reply("Hmm, there seems to have been an error while I tried to show you that user's avatar.");} + } +}; \ No newline at end of file diff --git a/commands/deathnote.js b/commands/deathnote.js index c7ce72f..6e23da1 100644 --- a/commands/deathnote.js +++ b/commands/deathnote.js @@ -1,5 +1,7 @@ const Discord = require('discord.js'); const moment = require('moment'); +const {Tag} = require('../util/tag'); +const {TagFilter} = require('../util/tagfilter'); const deaths = [ "watching too much anime", "an overdose of waifus", "Hypotakunemia", "trying to self-isekai", @@ -58,20 +60,38 @@ module.exports = { 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 (!mention) {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.)");} - if (!args[0].trim().match(/^<@(?:\!?)\d+>$/)) {return message.reply("You have to mention someone!");} - if (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.id == client.user.id) {return message.reply("You can't kill me! Little did you know, I'm actually a death god!");} + //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!");} //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 pretext = before[Math.floor(Math.random() * before.length)].replace(/{p}/g, message.member.displayName); - let victim = message.mentions.members.first(); + 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(" ")); + if (options.method && options.method.length) {death = options.method;} + + 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.)");} + + if (options.victim && options.victim.length) { + let vargs = options.victim.trim().split(/\s+/g); + let nvargs = []; + let varg; for (varg of vargs) { + 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);} + } + options.victim = nvargs.join(" ").trim(); + } + let victim = options.victim && options.victim.length ? options.victim : message.mentions.members.first().displayName; let killer = message.member; + let pretext = before[Math.floor(Math.random() * before.length)].replace(/{p}/g, victim); + let note = await message.channel.send(new Discord.MessageEmbed() .setDescription(pretext) .setColor('c375f0') @@ -82,22 +102,22 @@ module.exports = { await require('../util/wait')(2500); let text = reptype.texts[Math.floor(Math.random() * reptype.texts.length)] - .replace(/{p}/g, victim.displayName) //{p} = victim - .replace(/{pa}/g, victim.displayName.toLowerCase().endsWith('s') ? `${victim.displayName}'` : `${victim.displayName}'s`) //{pa} = victim but with a formatted apostrophe like "WubzyGD's" + .replace(/{p}/g, victim) //{p} = victim + .replace(/{pa}/g, victim.toLowerCase().endsWith('s') ? `${victim}'` : `${victim}'s`) //{pa} = victim but with a formatted apostrophe like "WubzyGD's" .replace(/{c}/g, death) // {c} = death method .replace(/{w}/g, killer.displayName) // {w} = killer or writer .replace(/{ds}/g, moment().format("h:mm a")); // {ds} = date small, basically just the time. // Create and format the kill text - //TODO message before sending then edit that message i.e. "A name is being written..." then wait 5s + let finalEmbed = new Discord.MessageEmbed() + .setAuthor(title, message.author.avatarURL()) + .setDescription(text) + .setColor('c375f0') + .setFooter("Natsuki") + .setTimestamp(); - return note.edit(new Discord.MessageEmbed() - .setAuthor(title, message.author.avatarURL()) - .setThumbnail(mention.avatarURL({size: 1024})) - .setDescription(text) - .setColor('c375f0') - .setFooter("Natsuki") - .setTimestamp() - ); + if (mention) {finalEmbed.setThumbnail(mention.avatarURL({size: 1024}));} + + return note.edit(finalEmbed); } }; \ No newline at end of file diff --git a/commands/staffrole.js b/commands/staffrole.js new file mode 100644 index 0000000..7fd0acc --- /dev/null +++ b/commands/staffrole.js @@ -0,0 +1,48 @@ +const Discord = require('discord.js'); +const GuildSettings = require('../models/guild'); + +module.exports = { + name: "staffrole", + aliases: ['sr', 'setstaffrole'], + help: "Set your server's staff role, which allows users with that role to modify my settings in this server. You must be an admin in the server to change this setting.", + async execute(message, msg, args, cmd, prefix, mention, client) { + 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}); + + 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.` + : `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.' + );} + + let role = !['c', 'clear', 'n', 'none'].includes(args[0].trim().toLowerCase()) ? message.mentions.roles.size ? message.mentions.roles.first() : message.guild.roles.cache.has(args[0]) ? message.guild.roles.cache.get(args[0]) : null : 'c'; + + if (!role) {return message.reply("I couldn't find that role!");} + if (role === "c") { + tguild.staffrole = ''; + tguild.save(); + return message.reply("Got it, only admins can edit my settings in this server."); + } else { + tguild.staffrole = role.id; + tguild.save(); + let upm = message.reply("Sure thing!"); + await require('../util/wait')(1750); + return upm.edit(new Discord.MessageEmbed() + .setAuthor('Staff role updated!', message.author.avatarURL()) + .setDescription(`<@${tguild.staffrole}> can now edit my settings in this server.`) + .addField('Auditing Admin', message.member.displayName, true) + .addField('Role-Holders', `${message.guild.members.cache.filter(m => m.roles.cache.has(tguild.staffrole) && !client.users.cache.get(m.id).bot)}+ members have this role`) + .setColor('c375f0') + .setFooter('Natsuki', client.user.avatarURL()) + .setTimestamp() + ); + } + } +}; \ No newline at end of file diff --git a/events/message.js b/events/message.js index d5e7eb2..71b4e07 100644 --- a/events/message.js +++ b/events/message.js @@ -16,7 +16,7 @@ module.exports = async (client, message) => { var msg = message.content.toLowerCase(); var mention = message.mentions.users.first(); var args = msg.startsWith(prefix) - ? message.content.slice(prefix.length).trim().split(/\s+/g) + ? message.content.slice(prefix.length).trim().split(/\s+/g) : msg.startsWith('<@!') ? message.content.slice(4 + client.user.id.length).trim().split(/\s+/g) : message.content.slice(3 + client.user.id.length).trim().split(/\s+/g); diff --git a/util/tag.d.ts b/util/tag.d.ts new file mode 100644 index 0000000..4421210 --- /dev/null +++ b/util/tag.d.ts @@ -0,0 +1,9 @@ +export declare class Tag { + triggers: string[]; + tagName: string; + filterType: TagFilterType; + constructor(triggers: string[], tagName: string, filterType: TagFilterType); + addTrigger(trigger: string): Tag; +} +declare type TagFilterType = "append" | "toggle"; +export {}; diff --git a/util/tag.js b/util/tag.js new file mode 100644 index 0000000..2a012a9 --- /dev/null +++ b/util/tag.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Tag = void 0; +class Tag { + constructor(triggers, tagName, filterType) { + this.triggers = []; + let trigger; + for (trigger of triggers) { + this.triggers.push(trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}`); + } + this.tagName = tagName; + this.filterType = filterType; + } + ; + addTrigger(trigger) { + this.triggers.push(trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}`); + return this; + } + ; +} +exports.Tag = Tag; diff --git a/util/tagfilter.d.ts b/util/tagfilter.d.ts new file mode 100644 index 0000000..0da5548 --- /dev/null +++ b/util/tagfilter.d.ts @@ -0,0 +1,10 @@ +import { Tag } from "./tag"; +export declare class TagFilter { + tags: Tag[]; + triggers: Map; + filterTypes: Map; + constructor(tags: Tag[]); + test(text: string): object; +} +declare type TagFilterType = "append" | "toggle"; +export {}; diff --git a/util/tagfilter.js b/util/tagfilter.js new file mode 100644 index 0000000..ed77f12 --- /dev/null +++ b/util/tagfilter.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TagFilter = void 0; +class TagFilter { + constructor(tags) { + this.tags = tags; + this.triggers = new Map(); + this.filterTypes = new Map(); + let tag; + for (tag of this.tags) { + let trigger; + for (trigger of tag.triggers) { + this.triggers.set(trigger, tag.tagName); + } + if (!this.filterTypes.has(tag.tagName)) { + this.filterTypes.set(tag.tagName, tag.filterType); + } + } + } + test(text) { + var filtered = {}; + var reading = null; + 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(); + if (!reading) { + filtered[`${this.triggers.get(word.trim())}`] = true; + } + else { + filtered[`${this.triggers.get(reading)}`] = ''; + } + } + else if (reading) { + filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`; + } + } + let key; + for (key of Object.keys(filtered)) { + if (typeof filtered[key] == 'string') { + filtered[key] = filtered[key].trim(); + } + } + return filtered; + } + ; +} +exports.TagFilter = TagFilter; diff --git a/util/ts/tag.ts b/util/ts/tag.ts new file mode 100644 index 0000000..86c7ce2 --- /dev/null +++ b/util/ts/tag.ts @@ -0,0 +1,25 @@ +export class Tag { + triggers: string[] = []; + tagName: string; + filterType: TagFilterType; + + constructor(triggers: string[], tagName: string, filterType: TagFilterType) { + let trigger: string; for (trigger of triggers) { + this.triggers.push( + trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}` + ); + } + + this.tagName = tagName; + this.filterType = filterType; + }; + + public addTrigger(trigger: string): Tag { + this.triggers.push( + trigger.trim().startsWith("-") ? trigger.trim() : `-${trigger.trim()}` + ); + return this; + }; +} + +type TagFilterType = "append" | "toggle"; \ No newline at end of file diff --git a/util/ts/tagfilter.ts b/util/ts/tagfilter.ts new file mode 100644 index 0000000..fcf04f7 --- /dev/null +++ b/util/ts/tagfilter.ts @@ -0,0 +1,45 @@ +import {Tag} from "./tag"; + +export class TagFilter { + tags: Tag[]; + triggers: Map; + filterTypes: Map; + + constructor(tags: Tag[]) { + this.tags = tags; + this.triggers = new Map(); + this.filterTypes = new Map(); + let tag: Tag; + for (tag of this.tags) { + let trigger: string; for (trigger of tag.triggers) { + this.triggers.set(trigger, tag.tagName); + } + if (!this.filterTypes.has(tag.tagName)) {this.filterTypes.set(tag.tagName, tag.filterType);} + } + } + + public test(text: string): object { + var filtered: object = {}; + var reading: string = null; + + 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(); + if (!reading) {filtered[`${this.triggers.get(word.trim())}`] = true;} + else {filtered[`${this.triggers.get(reading)}`] = '';} + } + else if (reading) { + filtered[`${this.triggers.get(reading)}`] = `${filtered[`${this.triggers.get(reading)}`]} ${word}`; + } + } + + let key: string; for (key of Object.keys(filtered)) { + if (typeof filtered[key] == 'string') {filtered[key] = filtered[key].trim();} + } + + return filtered; + }; +} + +type TagFilterType = "append" | "toggle"; \ No newline at end of file diff --git a/util/ts/tsconfig.json b/util/ts/tsconfig.json new file mode 100644 index 0000000..990d7fa --- /dev/null +++ b/util/ts/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "commonjs", + "lib": ["es2017", "es6", "dom"], + "target": "es2017", + "declaration": true, + "outDir": "../" + }, + "include": ["**/*"], + "exclude": [] +} \ No newline at end of file