diff --git a/.eslintrc.json b/.eslintrc.json index 4d64bc1..19880fc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -40,6 +40,7 @@ "no-useless-constructor": "off", "@typescript-eslint/no-useless-constructor": 2, "import/extensions": "off", - "no-param-reassign": "off" + "no-param-reassign": "off", + "no-underscore-dangle": "off" } -} \ No newline at end of file +} diff --git a/src/class/Client.ts b/src/class/Client.ts index 5753a09..f6bafa9 100644 --- a/src/class/Client.ts +++ b/src/class/Client.ts @@ -2,7 +2,7 @@ import eris from 'eris'; import mongoose from 'mongoose'; import { promises as fs } from 'fs'; import { Collection, Command, LocalStorage, Util, ServerManagement, Event } from '.'; -import { File, FileInterface, Member, MemberInterface, Moderation, ModerationInterface, PagerNumber, PagerNumberInterface, Rank, RankInterface, Redirect, RedirectInterface, Stat, StatInterface } from '../models'; +import { File, FileInterface, Member, MemberInterface, Moderation, ModerationInterface, Note, NoteInterface, PagerNumber, PagerNumberInterface, Rank, RankInterface, Redirect, RedirectInterface, Stat, StatInterface } from '../models'; import { Config } from '../../types'; // eslint-disable-line export default class Client extends eris.Client { @@ -18,14 +18,14 @@ export default class Client extends eris.Client { public serverManagement: ServerManagement; - public db: { File: mongoose.Model, Member: mongoose.Model, Moderation: mongoose.Model, PagerNumber: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, Stat: mongoose.Model, local: { muted: LocalStorage } }; + public db: { File: mongoose.Model, Member: mongoose.Model, Moderation: mongoose.Model, Note: mongoose.Model, PagerNumber: mongoose.Model, Rank: mongoose.Model, Redirect: mongoose.Model, Stat: mongoose.Model, local: { muted: LocalStorage } }; constructor(token: string, options?: eris.ClientOptions) { super(token, options); this.commands = new Collection(); this.events = new Collection(); this.intervals = new Collection(); - this.db = { File, Member, Moderation, PagerNumber, Rank, Redirect, Stat, local: { muted: new LocalStorage('muted') } }; + this.db = { File, Member, Moderation, Note, PagerNumber, Rank, Redirect, Stat, local: { muted: new LocalStorage('muted') } }; } public async loadDatabase() { diff --git a/src/commands/addnote.ts b/src/commands/addnote.ts new file mode 100644 index 0000000..0e2a92b --- /dev/null +++ b/src/commands/addnote.ts @@ -0,0 +1,38 @@ +import { Message } from 'eris'; +import { Command, Client } from '../class'; + +export default class AddNote extends Command { + constructor(client: Client) { + super(client); + this.name = 'addnote'; + this.description = 'Adds a note to a member.'; + this.usage = `${this.client.config.prefix}addnote [category: comm | cs | edu]`; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0] || args.length < 1) return this.client.commands.get('help').run(message, [this.name]); + const member = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) return this.error(message.channel, 'The member you specified could not be found.'); + + const note: { userID?: string, date: Date, category?: string, text?: string } = { + userID: member.user.id, + date: new Date(), + }; + if (args[args.length - 1] !== 'edu' && args[args.length - 1] !== 'comm' && args[args.length - 1] !== 'cs') { + note.category = ''; + note.text = args.slice(1).join(' '); + } else { + note.category = args[args.length - 1]; + note.text = args.slice(0, args.length - 1).join(' '); + } + const saved = await (new this.client.db.Note(note).save()); + return this.success(message.channel, `Successfully created Note # \`${saved._id}\`.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delnote.ts b/src/commands/delnote.ts new file mode 100644 index 0000000..ad94795 --- /dev/null +++ b/src/commands/delnote.ts @@ -0,0 +1,26 @@ +import { Message } from 'eris'; +import { Command, Client } from '../class'; + +export default class DelNote extends Command { + constructor(client: Client) { + super(client); + this.name = 'delnote'; + this.description = 'Deletes a note.'; + this.usage = `${this.client.config.prefix}delnote `; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + const note = await this.client.db.Note.findOne({ _id: args[0] }).lean().exec().catch(() => {}); + if (!note) return this.error(message.channel, 'Could not locate that note.'); + await this.client.db.Note.deleteOne({ _id: note._id }); + return this.success(message.channel, `Note # \`${note._id}\` has been deleted.`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/game.ts b/src/commands/game.ts index 3c6d7a8..d2dead0 100644 --- a/src/commands/game.ts +++ b/src/commands/game.ts @@ -32,7 +32,7 @@ export default class Game extends Command { return this.error(message.channel, 'Member not found.'); } } - if (member.activities.length <= 0) return this.error(message.channel, 'Cannot find a game for this member.'); + if (!member.activities || member.activities.length <= 0) return this.error(message.channel, 'Cannot find a game for this member.'); const embed = new RichEmbed(); let mainStatus: Activity; if (member.activities[0].type === ActivityType.CUSTOM_STATUS) { diff --git a/src/commands/index.ts b/src/commands/index.ts index 3b77616..92b76fc 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,8 +1,10 @@ export { default as additem } from './additem'; +export { default as addnote } from './addnote'; export { default as addrank } from './addrank'; export { default as addredirect } from './addredirect'; export { default as ban } from './ban'; export { default as delitem } from './delitem'; +export { default as delnote } from './delnote'; export { default as delrank } from './delrank'; export { default as delredirect } from './delredirect'; export { default as djs } from './djs'; @@ -14,6 +16,7 @@ export { default as kick } from './kick'; export { default as listredirects } from './listredirects'; export { default as members } from './members'; export { default as mute } from './mute'; +export { default as notes } from './notes'; export { default as npm } from './npm'; export { default as page } from './page'; export { default as ping } from './ping'; diff --git a/src/commands/notes.ts b/src/commands/notes.ts new file mode 100644 index 0000000..98b8c31 --- /dev/null +++ b/src/commands/notes.ts @@ -0,0 +1,85 @@ +import { Message, Member, User } from 'eris'; +import { createPaginationEmbed } from 'eris-pagination'; +import { Command, Client, RichEmbed } from '../class'; + +export default class Notes extends Command { + constructor(client: Client) { + super(client); + this.name = 'notes'; + this.description = 'Pulls up notes for a member, with an optional categorical filter.'; + this.usage = `${this.client.config.prefix}notes [filter: comm | cs | edu]`; + this.permissions = 1; + this.guildOnly = true; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.client.commands.get('help').run(message, [this.name]); + let member: Member | User = this.client.util.resolveMember(args[0], this.mainGuild); + if (!member) member = await this.client.getRESTUser(args[0]); + if (!member) return this.error(message.channel, 'User specified could not be found.'); + + const notes = await this.client.db.Note.find({ userID: member.id }); + if (!notes || notes?.length < 1) return this.error(message.channel, 'No notes exist for this user.'); + + const noteArray: [{ name: string, value: string, inline: boolean }?] = []; + if (args[1] === 'comm' || args[1] === 'cs' || args[1] === 'edu') { + switch (args[1]) { + case 'comm': + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'comm')) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category}`} | ${note.date.toLocaleString('en-us')} ET`, + value: note.text, + inline: true, + }); + } + break; + case 'cs': + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'cs')) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category}`} | ${note.date.toLocaleString('en-us')} ET`, + value: note.text, + inline: true, + }); + } + break; + case 'edu': + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime()).filter((r) => r.category === 'edu')) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category}`}} | ${note.date.toLocaleString('en-us')} ET`, + value: note.text, + inline: true, + }); + } + break; + default: + break; + } + } else { + for (const note of notes.sort((a, b) => b.date.getTime() - a.date.getTime())) { + noteArray.push({ + name: `${note._id}${note.category === '' ? '' : `, ${note.category}`} | ${note.date.toLocaleString('en-us')} ET`, + value: note.text, + inline: true, + }); + } + } + const noteSplit = this.client.util.splitFields(noteArray); + const cmdPages: RichEmbed[] = []; + noteSplit.forEach((split) => { + const embed = new RichEmbed(); + embed.setColor('#0000FF'); + embed.setAuthor(`${member.username}#${member.discriminator}`, member.avatarURL); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + split.forEach((c) => embed.addField(c.name, c.value, c.inline)); + return cmdPages.push(embed); + }); + if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] }); + return createPaginationEmbed(message, cmdPages); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/configs/acknowledgements.json b/src/configs/acknowledgements.json index 3bd3668..5f031e4 100644 --- a/src/configs/acknowledgements.json +++ b/src/configs/acknowledgements.json @@ -17,8 +17,7 @@ "emailAddress": "joe@staff.libraryofcode.org", "gitlab": "https://gitlab.libraryofcode.org/Joe", "github": "https://github.com/sirdroolio", - "bio": "new Error(\"Proper Bio not found\");", - "acknowledgements": ["Developer"] + "bio": "new Error(\"Proper Bio not found\");" }, { "name": "Bsian", @@ -98,7 +97,8 @@ "emailAddress": "hector@staff.libraryofcode.org", "gitlab": "https://gitlab.libraryofcode.org/Hector", "github": "https://github.com/Hector6704", - "bio": "Hi there, I'm the developer of Delta, the Discord bot. I'm a free-time French JavaScript developer. I hope you'll enjoy LOC!" + "bio": "Hi there, I'm the developer of Delta, the Discord bot. I'm a free-time French JavaScript developer. I hope you'll enjoy LOC!", + "acknowledgements": ["Contributor"] }, { "name": "KhaaZ", @@ -107,7 +107,8 @@ "emailAddress": "khaaz@staff.libraryofcode.org", "gitlab": "https://gitlab.libraryofcode.org/KhaaZ", "github": "https://github.com/Khaazz", - "bio": "I baguette for a living and eat code for breakfast." + "bio": "I baguette for a living and eat code for breakfast.", + "acknowledgements": ["Assistant Maintainer"] }, { "name": "Zloth", diff --git a/src/models/Note.ts b/src/models/Note.ts new file mode 100644 index 0000000..f73f491 --- /dev/null +++ b/src/models/Note.ts @@ -0,0 +1,17 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface NoteInterface extends Document { + userID: string, + date: Date, + category: 'comm' | 'cs' | 'edu' | '', + text: string, +} + +const Note: Schema = new Schema({ + userID: String, + date: Date, + category: String, + text: String, +}); + +export default model('Note', Note); diff --git a/src/models/index.ts b/src/models/index.ts index fe3f79d..d5ee1b9 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,6 +1,7 @@ export { default as File, FileInterface } from './File'; export { default as Member, MemberInterface } from './Member'; export { default as Moderation, ModerationInterface } from './Moderation'; +export { default as Note, NoteInterface } from './Note'; export { default as PagerNumber, PagerNumberInterface, PagerNumberRaw } from './PagerNumber'; export { default as Rank, RankInterface } from './Rank'; export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect';