diff --git a/Makefile b/Makefile index b44d381..6bb691c 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ clean: @-rm -rf build build: - tsc -p ./tsconfig.json + -tsc -p ./tsconfig.json run: cd build && node main diff --git a/package.json b/package.json index bf1c286..31d1f91 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "dependencies": { "axios": "^0.19.2", "eris": "bsian03/eris#bsian", + "eris-pagination": "bsian03/eris-pagination#dev", "moment": "^2.24.0", "mongoose": "^5.9.9", "signale": "^1.4.0", diff --git a/src/class/Client.ts b/src/class/Client.ts index 589ff0f..29212bb 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, Util } from '.'; -import { Moderation, ModerationInterface } from '../models'; +import { Member, MemberInterface, Moderation, ModerationInterface } from '../models'; export default class Client extends eris.Client { public config: { token: string, prefix: string, guildID: string, mongoDB: string }; @@ -13,14 +13,14 @@ export default class Client extends eris.Client { public util: Util; - public db: { moderation: mongoose.Model }; + public db: { member: mongoose.Model, moderation: mongoose.Model }; // eslint-disable-next-line @typescript-eslint/no-useless-constructor constructor(token: string, options?: eris.ClientOptions) { super(token, options); this.commands = new Collection(); this.intervals = new Collection(); - this.db = { moderation: Moderation }; + this.db = { member: Member, moderation: Moderation }; } public async loadDatabase() { diff --git a/src/class/Command.ts b/src/class/Command.ts index 7fc00f5..8a57e4b 100644 --- a/src/class/Command.ts +++ b/src/class/Command.ts @@ -28,9 +28,9 @@ export default class Command { * - **0:** Everyone * - **1:** Associates+ * - **2:** Core Team+ - * - **3:** Moderators, Supervisor, Board of Directors - * - **4:** Technicians, Supervisor, Board of Directors - * - **5:** Moderators, Technicians, Supervisor, Board of Directors + * - **3:** Moderators, Supervisor, & Board of Directors + * - **4:** Technicians, Supervisor, & Board of Directors + * - **5:** Moderators, Technicians, Supervisor, & Board of Directors * - **6:** Supervisor+ * - **7:** Board of Directors */ @@ -68,8 +68,10 @@ export default class Command { case 4: return member.roles.some((r) => ['701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); case 5: - return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r)); + return member.roles.some((r) => ['455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r)); case 6: + return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r)); + case 7: return member.roles.includes('662163685439045632'); default: return false; diff --git a/src/class/Util.ts b/src/class/Util.ts index 10a0702..c0798c5 100644 --- a/src/class/Util.ts +++ b/src/class/Util.ts @@ -118,6 +118,16 @@ export default class Util { return arrayString; } + public splitFields(fields: { name: string, value: string, inline?: boolean }[]): { name: string, value: string, inline?: boolean }[][] { + let index = 0; + const array: {name: string, value: string, inline?: boolean}[][] = [[]]; + while (fields.length) { + if (array[index].length >= 25) { index += 1; array[index] = []; } + array[index].push(fields[0]); fields.shift(); + } + return array; + } + public decimalToHex(int: number): string { const hex = int.toString(16); return '#000000'.substring(0, 7 - hex.length) + hex; diff --git a/src/commands/additem.ts b/src/commands/additem.ts new file mode 100644 index 0000000..666489d --- /dev/null +++ b/src/commands/additem.ts @@ -0,0 +1,45 @@ +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class AddItem extends Command { + constructor(client: Client) { + super(client); + this.name = 'additem'; + this.description = 'Adds information to your whois embed.'; + this.usage = 'additem [code]'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args.length < 1) { + const embed = new RichEmbed(); + embed.setTitle('Whois Data Codes'); + embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts'); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + if (['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) { + const account = await this.client.db.member.findOne({ userID: message.member.id }); + if (!account) { + // eslint-disable-next-line new-cap + const newAccount = new this.client.db.member({ + userID: message.member.id, + additional: { + langs: [args[0].split('-')[1]], + }, + }); + await newAccount.save(); + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added language code ${args[0]} to profile.***`); + } + await account.updateOne({ $addToSet: { 'additional.langs': args[0].split('-')[1] } }); + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added language code ${args[0]} to profile.***`); + } + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid language code.***`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/delitem.ts b/src/commands/delitem.ts new file mode 100644 index 0000000..cf802d2 --- /dev/null +++ b/src/commands/delitem.ts @@ -0,0 +1,37 @@ +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class DelItem extends Command { + constructor(client: Client) { + super(client); + this.name = 'delitem'; + this.description = 'Removes information to your whois embed.'; + this.usage = 'delitem [code]'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (args.length < 1) { + const embed = new RichEmbed(); + embed.setTitle('Whois Data Codes'); + embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts'); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + if (['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) { + const account = await this.client.db.member.findOne({ userID: message.member.id }); + if (!account || !account?.additional.langs || account?.additional.langs.length < 1) { + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any languages to remove.***`); + } + await account.updateOne({ $pull: { 'additional.langs': args[0].split('-')[1] } }); + return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed language code ${args[0]} to profile.***`); + } + return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid language code.***`); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..0a46112 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,116 @@ +import { createPaginationEmbed } from 'eris-pagination'; +import { Message } from 'eris'; +import { Client, Command, RichEmbed } from '../class'; + +export default class Help extends Command { + constructor(client: Client) { + super(client); + this.name = 'help'; + this.description = 'Information about commands.'; + this.usage = 'help [command]'; + this.permissions = 0; + this.enabled = true; + } + + // eslint-disable-next-line consistent-return + public async run(message: Message, args: string[]) { + try { + if (args.length > 0) { + const command = this.client.commands.get(args[0].toLowerCase()); + if (!command) return this.error(message.channel, 'The command you provided doesn\'t exist.'); + const embed = new RichEmbed(); + embed.setTitle(`${this.client.config.prefix}${command.name}`); + embed.addField('Description', command.description ?? '-'); + embed.addField('Usage', command.usage ?? '-'); + if (command.aliases.length > 0) { + embed.addField('Aliases', command.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', ')); + } + let description: string = ''; + if (!command.enabled) { + description += 'This command is disabled.'; + } + if (command.guildOnly) { + description += 'This command can only be ran in a guild.'; + } + embed.setDescription(description); + switch (command.permissions) { + case 0: + break; + case 1: + embed.addField('Permissions', 'Associates+'); + break; + case 2: + embed.addField('Permissions', 'Core Team+'); + break; + case 3: + embed.addField('Permissions', 'Moderators, Supervisor, & Board of Directors'); + break; + case 4: + embed.addField('Permissions', 'Technicians, Supervisor, & Board of Directors'); + break; + case 5: + embed.addField('Permissions', 'Moderators, Technicians, Supervisor, & Board of Directors'); + break; + case 6: + embed.addField('Permissions', 'Supervisor+'); + break; + case 7: + embed.addField('Permissions', 'Board of Directors'); + break; + default: + break; + } + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + return message.channel.createMessage({ embed }); + } + const cmdList: Command[] = []; + this.client.commands.forEach((c) => cmdList.push(c)); + const commands = this.client.commands.map((c) => { + const aliases = c.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', '); + let perm: string; + switch (c.permissions) { + case 0: + break; + case 1: + perm = 'Associates+'; + break; + case 2: + perm = 'Core Team+'; + break; + case 3: + perm = 'Moderators, Supervisor, & Board of Directors'; + break; + case 4: + perm = 'Technicians, Supervisor, & Board of Directors'; + break; + case 5: + perm = 'Moderators, Technicians, Supervisor, & Board of Directors'; + break; + case 6: + perm = 'Supervisor+'; + break; + case 7: + perm = 'Board of Directors'; + break; + default: + break; + } + return { name: `${this.client.config.prefix}${c.name}`, value: `**Description:** ${c.description}\n**Aliases:** ${aliases}\n**Usage:** ${c.usage}\n**Permissions:** ${perm ?? ''}`, inline: false }; + }); + const splitCommands = this.client.util.splitFields(commands); + const cmdPages: RichEmbed[] = []; + splitCommands.forEach((splitCmd) => { + const embed = new RichEmbed(); + embed.setTimestamp(); embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setDescription(`Command list for ${this.client.user.username}`); + splitCmd.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) { + this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/info.ts b/src/commands/info.ts new file mode 100644 index 0000000..278c9f9 --- /dev/null +++ b/src/commands/info.ts @@ -0,0 +1,34 @@ +import { Message } from 'eris'; +import { totalmem } from 'os'; +import { Client, Command, RichEmbed } from '../class'; +import { version as erisVersion } from '../../node_modules/eris/package.json'; +import { version as mongooseVersion } from '../../node_modules/mongoose/package.json'; + +export default class Info extends Command { + constructor(client: Client) { + super(client); + this.name = 'info'; + this.description = 'System information.'; + this.usage = 'info'; + this.permissions = 0; + this.enabled = true; + } + + public async run(message: Message) { + try { + const embed = new RichEmbed(); + embed.setTitle('Information'); + embed.setThumbnail(this.client.user.avatarURL); + embed.addField('Language(s)', '<:TypeScript:703451285789343774> TypeScript', true); + embed.addField('Discord Library', `Eris (${erisVersion})`, true); + embed.addField('Database Library', `MongoDB w/ Mongoose ODM (${mongooseVersion})`, true); + embed.addField('Repository', 'https://gitlab.libraryofcode.org/engineering/communityrelations | Licensed under GNU Affero General Public License V3', true); + embed.addField('Memory Usage', `${Math.round(process.memoryUsage().rss / 1024 / 1024)} MB / ${Math.round(totalmem() / 1024 / 1024 / 1024)} GB`, true); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + message.channel.createMessage({ embed }); + } catch (err) { + this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/npm.ts b/src/commands/npm.ts new file mode 100644 index 0000000..031d21b --- /dev/null +++ b/src/commands/npm.ts @@ -0,0 +1,64 @@ +import { Message } from 'eris'; +import axios from 'axios'; +import { Client, Command, RichEmbed } from '../class'; + +export default class NPM extends Command { + constructor(client: Client) { + super(client); + this.name = 'npm'; + this.description = 'Get information about npm modules.'; + this.usage = 'npm '; + this.permissions = 0; + this.guildOnly = false; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[0]) return this.error(message.channel, 'You need to specify a module name.'); + + const res = await axios.get(`https://registry.npmjs.com/${args[0]}`, { validateStatus: (_) => true }); + + if (res.status === 404) return this.error(message.channel, 'Could not find the library, try something else.'); + + const { data } = res; + + const bugs: string = data.bugs?.url || ''; + const description: string = data.description || 'None'; + const version: string = data['dist-tags']?.latest || 'Unknown'; + const homepage: string = data.homepage || ''; + let license: string = 'None'; + if (typeof data.license === 'object') { + license = data.license.type; + } else if (typeof data.license === 'string') { + license = data.license; + } + let dependencies: string = 'None'; + if (version !== 'Unknown' && data.versions[version].hasOwnProperty('dependencies') && Object.keys(data.versions[version].dependencies).length > 0) { + dependencies = Object.keys(data.versions[version].dependencies).join(', '); + } + const name: string = data.name || 'None'; + const repository: string = bugs.replace('/issues', '') || ''; + const creation: string = data.time.created ? new Date(data.time.created).toLocaleString('en') : 'None'; + const modification: string = data.time.modified ? new Date(data.time.modified).toLocaleString('en') : 'None'; + + const embed = new RichEmbed(); + embed.setColor(0xCC3534); + embed.setTimestamp(); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setAuthor('NPM', 'https://i.imgur.com/ErKf5Y0.png', 'https://www.npmjs.com/'); + embed.setDescription(`[NPM](https://www.npmjs.com/package/${args[0]}) | [Homepage](${homepage}) | [Repository](${repository}) | [Bugs](${bugs})`); + embed.addField('Name', name, true); + embed.addField('Latest version', version, true); + embed.addField('License', license, true); + embed.addField('Description', description, false); + embed.addField('Dependencies', dependencies, false); + embed.addField('Creation Date', creation, true); + embed.addField('Modification Date', modification, true); + + return message.channel.createMessage({ embed }); + } catch (err) { + return this.client.util.handleError(err, message, this); + } + } +} diff --git a/src/commands/roleinfo.ts b/src/commands/roleinfo.ts index 9fc6052..a617ec3 100644 --- a/src/commands/roleinfo.ts +++ b/src/commands/roleinfo.ts @@ -15,7 +15,7 @@ export default class Roleinfo extends Command { public async run(message: Message, args: string[]) { try { - if (!args[0]) return this.error(message.channel, 'You need to specifiy a role ID or a role name.'); + if (!args[0]) return this.error(message.channel, 'You need to specify a role ID or a role name.'); let role: Role = this.client.guilds.get(this.client.config.guildID).roles.find((r: Role) => r.id === args[0]); if (!role) { // if it's a role name diff --git a/src/commands/whois.ts b/src/commands/whois.ts index ece051c..80fde1a 100644 --- a/src/commands/whois.ts +++ b/src/commands/whois.ts @@ -68,6 +68,15 @@ export default class Whois extends Command { } } embed.addField('Status', `${member.status[0].toUpperCase()}${member.status.slice(1)}`, true); + if (member.bot) { + embed.addField('Platform', 'API/WebSocket', true); + } else if (member.clientStatus?.web === 'online' || member.clientStatus?.web === 'idle' || member.clientStatus?.web === 'dnd') { + embed.addField('Platform', 'Web', true); + } else if (member.clientStatus?.desktop === 'online' || member.clientStatus?.desktop === 'idle' || member.clientStatus?.desktop === 'dnd') { + embed.addField('Platform', 'Desktop', true); + } else if (member.clientStatus?.mobile === 'online' || member.clientStatus?.mobile === 'idle' || member.clientStatus?.mobile === 'dnd') { + embed.addField('Platform', 'Mobile', true); + } embed.addField('Joined At', `${moment(new Date(member.joinedAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true); embed.addField('Created At', `${moment(new Date(member.user.createdAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true); if (member.roles.length > 0) { @@ -78,14 +87,61 @@ export default class Whois extends Command { const bit = member.permission.allow; if (this.client.guilds.get(this.client.config.guildID).ownerID === member.id) serverAcknowledgements.push('Server Owner'); if ((bit | 8) === bit) { permissions.push('Administrator'); serverAcknowledgements.push('Server Admin'); } - if ((bit | 20) === bit) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); } - if ((bit | 10) === bit) permissions.push('Manage Channels'); + if ((bit | 32) === bit) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); } + if ((bit | 16) === bit) permissions.push('Manage Channels'); if ((bit | 268435456) === bit) permissions.push('Manage Roles'); if ((bit | 8192) === bit) { permissions.push('Manage Messages'); serverAcknowledgements.push('Server Moderator'); } if ((bit | 134217728) === bit) permissions.push('Manage Nicknames'); if ((bit | 1073741824) === bit) permissions.push('Manage Emojis'); if ((bit | 4) === bit) permissions.push('Ban Members'); if ((bit | 2) === bit) permissions.push('Kick Members'); + const account = await this.client.db.member.findOne({ userID: member.id }); + if (account?.additional?.langs.length > 0) { + const langs: string[] = []; + for (const lang of account.additional.langs.sort((a, b) => a.localeCompare(b))) { + switch (lang) { + case 'asm': + langs.push('<:AssemblyLanguage:703448714248716442> Assembly Language'); + break; + case 'cfam': + langs.push('<:clang:553684262193332278> C/C++'); + break; + case 'csharp': + langs.push('<:csharp:553684277280112660> C#'); + break; + case 'go': + langs.push('<:Go:703449475405971466> Go'); + break; + case 'java': + langs.push('<:Java:703449725181100135> Java'); + break; + case 'js': + langs.push('<:JavaScriptECMA:703449987916496946> JavaScript'); + break; + case 'kt': + langs.push('<:Kotlin:703450201838321684> Kotlin'); + break; + case 'py': + langs.push('<:python:553682965482176513> Python'); + break; + case 'rb': + langs.push('<:ruby:604812470451699712> Ruby'); + break; + case 'rs': + langs.push('<:Rust:703450901960196206> Rust'); + break; + case 'swift': + langs.push('<:Swift:703451096093294672> Swift'); + break; + case 'ts': + langs.push('<:TypeScript:703451285789343774> TypeScript'); + break; + default: + break; + } + } + embed.addField('Known Languages', langs.join(', ')); + } if (permissions.length > 0) { embed.addField('Permissions', permissions.join(', ')); } diff --git a/src/configs/acknowledgements.json b/src/configs/acknowledgements.json index c7e92d3..f173976 100644 --- a/src/configs/acknowledgements.json +++ b/src/configs/acknowledgements.json @@ -6,7 +6,7 @@ "emailAddress": "matthew@staff.libraryofcode.org", "gitlab": "https://gitlab.libraryofcode.org/matthew", "github": "https://github.com/matthew119427", - "bio": "If you removed all the arteries, veins, & capillaries from a person’s body, and tied them end-to-end…the person will die. - Dr. Niel deGrasse Tyson", + "bio": "so baby come light me up, and baby ill let you on it. a little bit dangerous, but baby thats how i want it. a little less conversation and a little more touch my body. cuz im so into you... ~ Ariana Grande, Into You - Dangerous Woman", "acknowledgements": ["Maintainer & Lead Developer"] }, { @@ -57,6 +57,14 @@ "github": "https://github.com/TheSkele27", "bio": "Is water wet?" }, + { + "name": "Catbirby", + "id": "131953641371205632", + "title": "Supervisor", + "emailAddress": "catbirby@staff.libraryofcode.org", + "github": "https://github.com/catbirby", + "bio": "Computer Tech/Networking Nerd/Sysadmin/Graphic Designer/Audiophile. I don't do much coding but know my way around most languages." + }, { "name": "Besero", "id": "283318906024886272", @@ -121,9 +129,20 @@ { "name": "Ryan", "id": "186679073764802560", + "title": "Associate", + "emailAddress": "wbdvryan@staff.libraryofcode.org", "gitlab": "https://gitlab.libraryofcode.org/plainRyan", + "bio": "Experiment, learn, share, repeat.", "acknowledgements": ["Contributor"] }, + { + "name": "Zloth", + "id": "382368885267234816", + "title": "Associate", + "emailAddress": "zloth@staff.libraryofcode.org", + "github": "https://github.com/gavintjhxx", + "bio": "Undergoing education, codes as a hobby" + }, { "name": "Null", "id": "323673862971588609", diff --git a/src/models/Member.ts b/src/models/Member.ts new file mode 100644 index 0000000..f8c496b --- /dev/null +++ b/src/models/Member.ts @@ -0,0 +1,17 @@ +import { Document, Schema, model } from 'mongoose'; + +export interface MemberInterface extends Document { + userID: string + additional: { + langs: ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'], + }, +} + +const Member: Schema = new Schema({ + userID: String, + additional: { + langs: Array, + }, +}); + +export default model('Member', Member); diff --git a/src/models/index.ts b/src/models/index.ts index a149275..81202ce 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1 +1,2 @@ +export { default as Member, MemberInterface } from './Member'; export { default as Moderation, ModerationInterface } from './Moderation';