From 246944c3ce1416d622c1dc0953e94baf6bdbb30a Mon Sep 17 00:00:00 2001 From: Bsian Date: Mon, 28 Oct 2019 20:21:04 +0000 Subject: [PATCH] Stuff? --- .gitignore | 3 +- src/Client.ts | 27 ++++++++--- src/class/Command.ts | 1 + src/class/RichEmbed.ts | 23 ++++++---- src/{ => class}/Util.ts | 14 +++--- src/commands/announce.ts | 8 ++-- src/commands/createaccount.ts | 59 +++++++++++++++++++++++- src/commands/cwg.ts | 1 + src/commands/deleteaccount.ts | 79 +++++++++++++++++++++++++++++++++ src/commands/eval.ts | 28 +++++++----- src/commands/help.ts | 2 +- src/commands/lock.ts | 14 ++++-- src/commands/modlogs.ts | 10 ++++- src/commands/sysinfo.ts | 36 +++++++++++++++ src/commands/unlock.ts | 7 ++- src/events/messageCreate.ts | 26 +++++++---- src/functions/checkLock.ts | 8 ++-- src/functions/dataConversion.ts | 5 +++ src/functions/index.ts | 2 + src/index.ts | 1 - src/models/Moderation.ts | 4 +- src/stores/emojis.ts | 2 +- 22 files changed, 300 insertions(+), 60 deletions(-) rename src/{ => class}/Util.ts (95%) create mode 100644 src/commands/deleteaccount.ts create mode 100644 src/commands/sysinfo.ts create mode 100644 src/functions/dataConversion.ts create mode 100644 src/functions/index.ts diff --git a/.gitignore b/.gitignore index 3246fb3..eb5f3f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules yarn.lock src/config.json -package-lock.json \ No newline at end of file +package-lock.json +htmlEmail_templates \ No newline at end of file diff --git a/src/Client.ts b/src/Client.ts index 0a36a55..2e82a55 100644 --- a/src/Client.ts +++ b/src/Client.ts @@ -3,10 +3,10 @@ import mongoose from 'mongoose'; import signale from 'signale'; import fs from 'fs-extra'; import path from 'path'; -import { config, Util } from '.'; +import { config } from '.'; import { Account, AccountInterface, Moderation, ModerationInterface, Domain, DomainInterface } from './models'; import { emojis } from './stores'; -import { Command } from './class'; +import { Command, Util } from './class'; export default class Client extends Eris.Client { @@ -27,6 +27,7 @@ export default class Client extends Eris.Client { constructor() { super(config.token, { getAllUsers: true, restMode: true, defaultImageFormat: 'png' }); + process.title = 'cloudservices'; this.config = config; this.util = new Util(this); this.commands = new Map(); @@ -39,13 +40,21 @@ export default class Client extends Eris.Client { displayTimestamp: true, displayFilename: true, }); + this.events(); this.loadFunctions(); this.init(); } + private async events() { + process.on('unhandledRejection', (error) => { + this.signale.error(error); + }); + } + private async loadFunctions() { const functions = await fs.readdir('./functions'); functions.forEach(async (func) => { + if (func === 'index.ts') return; try { require(`./functions/${func}`).default(this); } catch (error) { @@ -57,7 +66,8 @@ export default class Client extends Eris.Client { public loadCommand(commandPath: string) { // eslint-disable-next-line no-useless-catch try { - const command = new (require(commandPath))(this); + // eslint-disable-next-line + const command = new (require(commandPath).default)(this); this.commands.set(command.name, command); this.signale.complete(`Loaded command ${command.name}`); } catch (err) { throw err; } @@ -67,17 +77,24 @@ export default class Client extends Eris.Client { const evtFiles = await fs.readdir('./events/'); const commands = await fs.readdir(path.join(__dirname, './commands/')); commands.forEach((command) => { + if (command === 'index.js') return; this.loadCommand(`./commands/${command}`); }); evtFiles.forEach((file) => { const eventName = file.split('.')[0]; - const event = new (require(`./events/${file}`))(this); + if (file === 'index.js') return; + // eslint-disable-next-line + const event = new (require(`./events/${file}`).default)(this); this.signale.complete(`Loaded event ${eventName}`); this.on(eventName, (...args) => event.run(...args)); delete require.cache[require.resolve(`./events/${file}`)]; }); - this.connect(); + await mongoose.connect(config.mongoURL, { useNewUrlParser: true, useUnifiedTopology: true }); + await this.connect(); } } + +// eslint-disable-next-line +new Client(); diff --git a/src/class/Command.ts b/src/class/Command.ts index 16177ac..ba3ee3f 100644 --- a/src/class/Command.ts +++ b/src/class/Command.ts @@ -28,5 +28,6 @@ export default class Command { this.aliases = []; this.guildOnly = true; this.client = client; + this.permissions = {}; } } diff --git a/src/class/RichEmbed.ts b/src/class/RichEmbed.ts index 38dbfec..9ebc0e4 100644 --- a/src/class/RichEmbed.ts +++ b/src/class/RichEmbed.ts @@ -33,16 +33,24 @@ export default class RichEmbed { thumbnail?: { url?: string, proxy_url?: string, height?: number, width?: number }, video?: { url?: string, height?: number, width?: number }, provider?: { name?: string, url?: string}, author?: { name?: string, url?: string, proxy_icon_url?: string, icon_url?: string}, } = {}) { + /* let types: { title?: string, type?: string, description?: string, url?: string, timestamp?: Date, color?: number, fields?: {name: string, value: string, inline?: boolean}[] footer?: { text: string, icon_url?: string, proxy_icon_url?: string}, image?: { url?: string, proxy_url?: string, height?: number, width?: number }, thumbnail?: { url?: string, proxy_url?: string, height?: number, width?: number }, video?: { url?: string, height?: number, width?: number }, provider?: { name?: string, url?: string}, author?: { name?: string, url?: string, proxy_icon_url?: string, icon_url?: string} }; - this.fields = []; - for (const key of Object.keys(types)) { - if (data[key]) this[key] = data[key]; - } + */ + this.title = data.title; + this.description = data.description; + this.url = data.url; + this.color = data.color; + this.author = data.author; + this.timestamp = data.timestamp; + this.fields = data.fields || []; + this.thumbnail = data.thumbnail; + this.image = data.image; + this.footer = data.footer; } /** @@ -97,10 +105,8 @@ export default class RichEmbed { */ setAuthor(name: string, icon_url?: string, url?: string) { if (typeof name !== 'string') throw new TypeError('RichEmbed Author names must be a string.'); - if (typeof url !== 'string') throw new TypeError('RichEmbed Author URLs must be a string.'); - if (typeof icon_url !== 'string') throw new TypeError('RichEmbed Author icons must be a string.'); - if (!url.startsWith('http://') || !url.startsWith('https://')) url = `https://${url}`; - if (!icon_url.startsWith('http://') || !icon_url.startsWith('https://')) icon_url = `https://${icon_url}`; + if (url && typeof url !== 'string') throw new TypeError('RichEmbed Author URLs must be a string.'); + if (icon_url && typeof icon_url !== 'string') throw new TypeError('RichEmbed Author icons must be a string.'); this.author = { name, icon_url, url }; return this; } @@ -164,7 +170,6 @@ export default class RichEmbed { setFooter(text: string, icon_url?: string) { if (typeof text !== 'string') throw new TypeError('RichEmbed Footers must be a string.'); if (icon_url && typeof icon_url !== 'string') throw new TypeError('RichEmbed Footer icon URLs must be a string.'); - if (!icon_url.startsWith('http://') || !icon_url.startsWith('https://')) icon_url = `https://${icon_url}`; if (text.length > 2048) throw new RangeError('RichEmbed footer text may not exceed 2048 characters.'); this.footer = { text, icon_url }; return this; diff --git a/src/Util.ts b/src/class/Util.ts similarity index 95% rename from src/Util.ts rename to src/class/Util.ts index 38ce3d9..d705ff2 100644 --- a/src/Util.ts +++ b/src/class/Util.ts @@ -2,10 +2,10 @@ import { promisify, isArray } from 'util'; import childProcess from 'child_process'; import nodemailer from 'nodemailer'; -import { Message, TextChannel, PrivateChannel } from 'eris'; +import { Message, PrivateChannel } from 'eris'; import { outputFile } from 'fs-extra'; -import { Client } from '.'; -import { Command, RichEmbed } from './class'; +import { Client } from '..'; +import { Command, RichEmbed } from '.'; export default class Util { public client: Client; @@ -45,6 +45,8 @@ export default class Util { } public async handleError(error: Error, message?: Message, command?: Command): Promise { + this.client.signale.error(error); + /* const info = { content: `\`\`\`js\n${error.stack}\n\`\`\``, embed: null }; if (message) { const embed = new RichEmbed(); @@ -65,6 +67,7 @@ export default class Util { if (message) this.client.createMessage('595788220764127272', 'Message content for above error'); if (command) this.client.commands.get(command.name).enabled = false; if (message) message.channel.createMessage(`***${this.client.stores.emojis.error} An unexpected error has occured - please contact a member of the Engineering Team.${command ? ' This command has been disabled.' : ''}***`); + */ } public splitFields(fields: {name: string, value: string, inline?: boolean}[]): {name: string, value: string, inline?: boolean}[][] { @@ -94,6 +97,7 @@ export default class Util { return arrayString; } + public async createHash(password: string) { const hashed = await this.exec(`mkpasswd -m sha-512 "${password}"`); return hashed; @@ -128,14 +132,14 @@ export default class Util { await this.client.db.Account.deleteOne({ username }); } - public async messageCollector(message: Message, question: string, timeout: number, shouldDelete = false, choices: string[] = null, filter = (msg: Message): boolean|void => {}): Promise { + public async messageCollector(message: Message, question: string, timeout: number, shouldDelete = false, choices: string[] = null, filter = (msg: Message): boolean|void => {}): Promise { const msg = await message.channel.createMessage(question); return new Promise((res, rej) => { setTimeout(() => { if (shouldDelete) msg.delete(); rej(new Error('Did not supply a valid input in time')); }, timeout); this.client.on('messageCreate', (Msg) => { if (filter(Msg) === false) return; const verif = choices ? choices.includes(Msg.content) : Msg.content; - if (verif) { if (shouldDelete) msg.delete(); res(Msg.content); } + if (verif) { if (shouldDelete) msg.delete(); res(Msg); } }); }); } diff --git a/src/commands/announce.ts b/src/commands/announce.ts index f825536..de33fb3 100644 --- a/src/commands/announce.ts +++ b/src/commands/announce.ts @@ -1,5 +1,5 @@ import { Message } from 'eris'; -import { Client, config } from '..'; +import { Client } from '..'; import { Command } from '../class'; export default class Announce extends Command { @@ -7,7 +7,7 @@ export default class Announce extends Command { super(client); this.name = 'announce'; this.description = 'Sends an announcement to all active terminals'; - this.usage = `${config.prefix}announce Hi there! | ${config.prefix}announce -e EMERGENCY!`; + this.usage = `${this.client.config.prefix}announce Hi there! | ${this.client.config.prefix}announce -e EMERGENCY!`; this.aliases = ['ann']; this.permissions = { roles: ['608095934399643649', '521312697896271873'] }; this.enabled = true; @@ -17,8 +17,8 @@ export default class Announce extends Command { try { if (!args.length) return this.client.commands.get('help').run(message, [this.name]); const notification = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Sending announcement, please wait...***`); - if (args[0] === '-e') await this.client.util.exec(`echo "\n\n**************************************************************************\nEMERGENCY SYSTEM BROADCAST MESSAGE | Library of Code sp-us (root enforced)\n--------------------------------------------------------------------------\n\n\n${args.slice(1).join(' ')}\n\n\n\n\n\n\n\n\n\n\n\n\n" | wall -n`); - else await this.client.util.exec(`echo "\nSYSTEM BROADCAST MESSAGE | Library of Code sp-us (root enforced)\n\n\n${args.join(' ')}" | wall -n`); + if (args[0] === '-e') await this.client.util.exec(`echo "\n\n**************************************************************************\nEMERGENCY SYSTEM BROADCAST MESSAGE | Library of Code sp-us (root enforced)\n--------------------------------------------------------------------------\n\n\n${args.slice(1).join(' ').trim()}\n\n\n\n\n\n\n\n\n\n\n\n\n" | wall -n`); + else await this.client.util.exec(`echo "\nSYSTEM BROADCAST MESSAGE | Library of Code sp-us (root enforced)\n\n\n${args.join(' ').trim()}" | wall -n`); message.delete(); return notification.edit(`${this.client.stores.emojis.success} ***Sent${args[0] === '-e' ? ' emergency' : ''} announcement to all active terminals***`); } catch (error) { diff --git a/src/commands/createaccount.ts b/src/commands/createaccount.ts index 945f8bd..f9a8b99 100644 --- a/src/commands/createaccount.ts +++ b/src/commands/createaccount.ts @@ -59,7 +59,64 @@ export default class CreateAccount extends Command { embed.setTimestamp(); // @ts-ignore this.client.createMessage('580950455581147146', { embed }); - return confirmation.edit(`${this.client.stores.emojis.success} ***Account successfully created***\n**Username:** \`${args[2]}\`\n**Temporary Password:** \`${tempPass}\``); + + this.client.util.transport.sendMail({ + to: args[1], + from: 'Library of Code sp-us | Cloud Services ', + subject: 'Your account has been created', + html: ` + + +

Library of Code | Cloud Services

+

Your Cloud Account has been created, welcome! Please see below for some details regarding your account and our services

+

Username: ${args[2]}

+

SSH Login:

ssh ${args[2]}@cloud.libraryofcode.org
+

Email address (see below for further information): ${args[2]}@cloud.libraryofcode.org

+

Useful information

+

How to log in:

+
    +
  1. Open your desired terminal application - we recommend using Bash, but you can use your computer's default
  2. +
  3. Type in your SSH Login as above
  4. +
  5. When prompted, enter your password Please note that inputs will be blank, so be careful not to type in your password incorrectly
  6. +
+

If you fail to authenticate yourself too many times, you will be IP banned and will fail to connect. If this is the case, feel free to DM Ramirez with your public IPv4 address. + +

Setting up your cloud email

+

All email applications are different, so here are some information you can use to connect your email

+
    +
  • Server: cloud.libraryofcode.org
  • +
  • Account username/password: Normal login
  • +
  • Account type (incoming): IMAP
  • +
  • Incoming port: 143 (993 if you're using TLS security type)
  • +
  • Incoming Security Type: STARTTLS (TLS if you're using port 993)
  • +
  • Outgoing port: 587 (If that doesn't work, try 25)
  • +
  • Outgoing Security Type: STARTTLS
  • +
+

Channels and Links

+
    +
  • #status - You can find the status of all our services, including the cloud machine, here
  • +
  • #cloud-announcements - Announcements regarding the cloud machine will be here. These include planned maintenance, updates to preinstalled services etc.
  • +
  • #cloud-info - Important information you will need to, or should, know to a certain extent. These include our infractions system and Tier limits
  • +
  • #cloud-support - A support channel specifically for the cloud machine, you can use this to ask how to renew your certificates, for example
  • +
  • Library of Code Support Desk - Our Support desk, you will find some handy info there
  • +
  • SecureSign - our certificates manager
  • +
+

Want to support us?

+

You can support us on Patreon! Head to our Patreon page and feel free to donate as much or as little as you want!
Donating $5 or more will grant you Tier 3, which means we will manage your account at your request, longer certificates, increased Tier limits as well as some roles in the server!

+ Library of Code sp-us | Support Team + + `, + }); + + const dmChannel = await this.client.getDMChannel(args[0]).catch(); + dmChannel.createMessage('<:loc:607695848612167700> **Thank you for creating an account with us!** <:loc:607695848612167700>\n' + + `Please log into your account by running \`ssh ${args[2]}@cloud.libraryofcode.us\` in your terminal, then use the password \`${tempPass}\` to log in.\n` + + `You will be asked to change your password, \`(current) UNIX password\` is \`${tempPass}\`, then create a password that is at least 12 characters long, with at least one number, special character, and an uppercase letter\n` + + 'Bear in mind that when you enter your password, it will be blank, so be careful not to type in your password incorrectly.\n' + + 'You may now return to Modmail, and continue setting up your account from there.\n\n' + + 'An email containing some useful information has also been sent').catch(); + + return confirmation.edit(`${this.client.stores.emojis.success} ***Account successfully created***\n**Username:** \`${args[3]}\`\n**Temporary Password:** \`${tempPass}\``); } catch (error) { return this.client.util.handleError(error, message, this); } diff --git a/src/commands/cwg.ts b/src/commands/cwg.ts index 593f531..5b2df4f 100644 --- a/src/commands/cwg.ts +++ b/src/commands/cwg.ts @@ -16,6 +16,7 @@ export default class CWG extends Command { } public async run(message: Message, args?: string[]) { + if (!args.length) return this.client.commands.get('help').run(message, [this.name]); /* args[1] should be the user's ID OR account username; required args[2] should be the domain; required diff --git a/src/commands/deleteaccount.ts b/src/commands/deleteaccount.ts new file mode 100644 index 0000000..120c9c5 --- /dev/null +++ b/src/commands/deleteaccount.ts @@ -0,0 +1,79 @@ +import { Message, PrivateChannel } from 'eris'; +import uuid from 'uuid/v4'; +import { Command, RichEmbed } from '../class'; +import { Client, config } from '..'; + +export default class DeleteAccount extends Command { + constructor(client: Client) { + super(client); + this.name = 'deleteaccount'; + this.description = 'Delete an account on the Cloud VM'; + this.usage = `${config.prefix}deleteaccount [User ID] [Reason] | ${config.prefix}deleteaccount [Username] [Reason] | ${config.prefix}deleteaccount [Email] [Reason]`; + this.aliases = ['deleteacc', 'dacc', 'daccount', 'delete']; + this.permissions = { roles: ['475817826251440128', '525441307037007902'] }; + this.enabled = true; + } + + public async run(message: Message, args: string[]) { + try { + if (!args[1]) return this.client.commands.get('help').run(message, [this.name]); + const account = await this.client.db.Account.findOne({ $or: [{ username: args[0] }, { userID: args[0] }, { emailAddress: args[0] }] }); + if (!account) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Account not found***`); + const { root, username, userID, emailAddress } = account; + if (root) return message.channel.createMessage(`${this.client.stores.emojis.error} ***Permission denied***`); + + const pad = (number: number, amount: number): string => '0'.repeat(amount - number.toString().length) + number; + const randomNumber = Math.floor(Math.random() * 9999); + const verify = pad(randomNumber, 4); + try { + await this.client.util.messageCollector(message, + `***Please confirm that you are permanently deleting ${username}'s account by entering ${verify}. This action cannot be reversed.***`, + 15000, true, [verify], (msg) => !(message.channel instanceof PrivateChannel && msg.author.id === message.author.id)); + } catch (error) { + if (error.message.includes('Did not supply')) return message; + throw error; + } + + const deleting = await message.channel.createMessage(`${this.client.stores.emojis.loading} ***Deleteing account, please wait...***`); + await this.client.util.deleteAccount(username); + const reason = args.slice(1).join(' '); + const logInput = { username, userID, logID: uuid(), moderatorID: message.author.id, type: 4, date: new Date(), reason: null }; + if (reason) logInput.reason = reason; + const log = await new this.client.db.Moderation(logInput); + await log.save(); + + const embed = new RichEmbed(); + embed.setTitle('Cloud Account | Delete'); + embed.setColor('ff0000'); + embed.addField('User', `${username} | <@${userID}>`); + embed.addField('Engineer', `<@${message.author.id}>`, true); + if (reason) embed.addField('Reason', reason); + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + // @ts-ignore + this.client.createMessage('580950455581147146', { embed }); + this.client.getDMChannel(userID).then((user) => { + // @ts-ignore + user.createMessage({ embed }).catch(); + }); + + this.client.util.transport.sendMail({ + to: account.emailAddress, + from: 'Library of Code sp-us | Cloud Services ', + subject: 'Your account has been deleted', + html: ` +

Library of Code | Cloud Services

+

Your Cloud Account has been deleted by our Engineers. There is no way to recover your files and this desicion cannot be appealed. We're sorry to see you go.

+

Reason: ${reason}

+

Engineer: ${message.author.username}

+ + Library of Code sp-us | Support Team + `, + }); + + return deleting.edit(`${this.client.stores.emojis.success} ***Account ${username} has been deleted by Engineer ${message.author.username}#${message.author.discriminator}***`); + } catch (error) { + return this.client.util.handleError(error, message, this); + } + } +} diff --git a/src/commands/eval.ts b/src/commands/eval.ts index 44efeab..698ebd5 100644 --- a/src/commands/eval.ts +++ b/src/commands/eval.ts @@ -2,7 +2,7 @@ import { Message } from 'eris'; import { inspect } from 'util'; import axios from 'axios'; -import { Client, config } from '..'; +import { Client } from '..'; import { Command } from '../class'; export default class Eval extends Command { @@ -15,26 +15,32 @@ export default class Eval extends Command { this.permissions = { users: ['253600545972027394', '278620217221971968'] }; } - public async run(message: Message) { + public async run(message: Message, args: string[]) { try { - const evalMessage = message.content.slice(config.prefix.length).split(' ').slice(1).join(' '); + // const evalMessage = message.content.slice(this.client.config.prefix.length).split(' ').slice(1).join(' '); let evaled: any; - let output: string; try { - evaled = await eval(evalMessage); - if (typeof evaled !== 'string') output = output && inspect(evaled, { depth: 1 }); + evaled = await eval(args.join(' ').trim()); + if (typeof evaled !== 'string') { + evaled = inspect(evaled, { depth: 0 }); + } + if (evaled === undefined) { + evaled = 'undefined'; + } } catch (error) { - output = error.stack; + evaled = error.stack; } + /* if (output) { - output = output.replace(RegExp(config.prefix, 'gi'), 'juul'); - output = output.replace(RegExp(config.emailPass, 'gi'), 'juul'); - output = output.replace(RegExp(config.cloudflare, 'gi'), 'juul'); + output = output.replace(RegExp(this.client.config.prefix, 'gi'), 'juul'); + output = output.replace(RegExp(this.client.config.emailPass, 'gi'), 'juul'); + output = output.replace(RegExp(this.client.config.cloudflare, 'gi'), 'juul'); } + */ - const display = this.client.util.splitString(output, 1975); + const display = this.client.util.splitString(evaled, 1975); if (display[5]) { try { const { data } = await axios.post('https://snippets.cloud.libraryofcode.org/documents', display.join('')); diff --git a/src/commands/help.ts b/src/commands/help.ts index 02b20a5..59c40c4 100644 --- a/src/commands/help.ts +++ b/src/commands/help.ts @@ -28,7 +28,7 @@ export default class Help extends Command { let allowedUsers = c.permissions && c.permissions.users && c.permissions.users.map((u) => `<@${u}>`).join(', '); if (allowedUsers) { allowedUsers = `**Users:** ${allowedUsers}`; perms.push(allowedUsers); } const displayedPerms = perms.length ? `**Permissions:**\n${perms.join('\n')}` : ''; - return { name: `${this.client.config.prefix}${c.name}`, value: `**Description:** ${c.description}\n**Aliases:** ${aliases}\n**Usage:** ${c.usage}\n${displayedPerms}`, inline: true }; + return { name: `${this.client.config.prefix}${c.name}`, value: `**Description:** ${c.description}\n**Aliases:** ${aliases}\n**Usage:** ${c.usage}\n${displayedPerms}`, inline: false }; }); const splitCommands = this.client.util.splitFields(commands); diff --git a/src/commands/lock.ts b/src/commands/lock.ts index f1ad4e7..384cd52 100644 --- a/src/commands/lock.ts +++ b/src/commands/lock.ts @@ -16,22 +16,26 @@ export default class Lock extends Command { public async run(message: Message, args: string[]) { // eslint-disable-line try { - const account = await this.client.db.Account.findOne({ $or: [{ account: args[0] }, { userID: args[0].replace(/[<@!>]/gi, '') }] }); + if (!args.length) return this.client.commands.get('help').run(message, [this.name]); + const account = await this.client.db.Account.findOne({ $or: [{ username: args[0] }, { userID: args[0].replace(/[<@!>]/gi, '') }] }); if (!account) return message.channel.createMessage(`***${this.client.stores.emojis.error} Cannot find user.***`); if (account.locked) return message.channel.createMessage(`***${this.client.stores.emojis.error} This account is already locked.***`); const edit = await message.channel.createMessage(`***${this.client.stores.emojis.loading} Locking account...***`); if (account.username === 'matthew' || account.root) return edit.edit(`***${this.client.stores.emojis.error} Permission denied.***`); await this.client.util.exec(`lock ${account.username}`); - await account.update({ locked: true }); + await account.updateOne({ locked: true }); const expiry = new Date(); const lockLength = args[1].match(/[a-z]+|[^a-z]+/gi); // @ts-ignore - const momentMilliseconds = moment.duration(Number(lockLength[0]), lockLength[1]).asMilliseconds; + const momentMilliseconds = moment.duration(Number(lockLength[0]), lockLength[1]).asMilliseconds(); expiry.setMilliseconds(momentMilliseconds); let processed: boolean = false; if (!momentMilliseconds) processed = true; + this.client.signale.debug(lockLength); + this.client.signale.debug(expiry); + this.client.signale.debug(momentMilliseconds); const moderation = new this.client.db.Moderation({ username: account.username, userID: account.userID, @@ -41,7 +45,7 @@ export default class Lock extends Command { type: 2, date: new Date(), expiration: { - expirationDate: momentMilliseconds ? expiry : null, + date: momentMilliseconds ? expiry : null, processed, }, }); @@ -53,8 +57,10 @@ export default class Lock extends Command { embed.addField('User', `${account.username} | <@${account.userID}>`, true); embed.addField('Supervisor', `<@${message.author.id}>`, true); if ((momentMilliseconds ? args.slice(2).join(' ') : args.slice(1).join(' ')).length > 0) embed.addField('Reason', momentMilliseconds ? args.slice(2).join(' ') : args.slice(1).join(' '), true); + embed.addField('Lock Expiration', `${momentMilliseconds ? moment(expiry).format('dddd, MMMM Do YYYY, h:mm:ss A') : 'Indefinitely'}`, true); embed.setFooter(this.client.user.username, this.client.user.avatarURL); embed.setTimestamp(); + message.delete(); this.client.getDMChannel(account.userID).then((user) => { // @ts-ignore user.createMessage({ embed }).catch(); diff --git a/src/commands/modlogs.ts b/src/commands/modlogs.ts index 1a66f15..ad3f03b 100644 --- a/src/commands/modlogs.ts +++ b/src/commands/modlogs.ts @@ -16,6 +16,7 @@ export default class Modlogs extends Command { public async run(message: Message, args: string[]) { try { + if (!args.length) return this.client.commands.get('help').run(message, [this.name]); const msg: Message = await message.channel.createMessage(`***${this.client.stores.emojis.loading} Locating modlogs...***`); const query = await this.client.db.Moderation.find({ $or: [{ username: args.join(' ') }, { userID: args.filter((a) => a)[0].replace(/[<@!>]/g, '') }] }); if (!query.length) return msg.edit(`***${this.client.stores.emojis.error} Cannot locate modlogs for ${args.join(' ')}***`); @@ -45,7 +46,7 @@ export default class Modlogs extends Command { const embeds = logs.map((l) => { const embed = new RichEmbed(); embed.setDescription(`List of Cloud moderation logs for ${users.join(', ')}`); - embed.setAuthor('Library of Code | Cloud Services', this.client.user.avatarURL, 'https://libraryofcode.us'); + embed.setAuthor('Library of Code | Cloud Services', this.client.user.avatarURL, 'https://libraryofcode.org/'); embed.setTitle('Cloud Modlogs/Infractions'); embed.setFooter(`Requested by ${message.author.username}#${message.author.discriminator}`, message.author.avatarURL); l.forEach((f) => embed.addField(f.name, f.value, f.inline)); @@ -54,7 +55,12 @@ export default class Modlogs extends Command { return embed; }); - createPaginationEmbed(message, this.client, embeds, {}, msg); + if (embeds.length === 1) { + // @ts-ignore + message.channel.createMessage({ embed: embeds[0] }); + } else { + createPaginationEmbed(message, this.client, embeds, {}, msg); + } return msg; } catch (error) { return this.client.util.handleError(error, message, this); diff --git a/src/commands/sysinfo.ts b/src/commands/sysinfo.ts new file mode 100644 index 0000000..3f880a2 --- /dev/null +++ b/src/commands/sysinfo.ts @@ -0,0 +1,36 @@ +import moment from 'moment'; +import { Message } from 'eris'; +import os, { totalmem } from 'os'; +import { Command, RichEmbed } from '../class'; +import { dataConversion } from '../functions'; +import { Client } from '..'; + +export default class SysInfo extends Command { + constructor(client: Client) { + super(client); + this.name = 'sysinfo'; + this.description = 'Provides system information.'; + this.enabled = true; + } + + public async run(message: Message) { + const availableMemory: string = await this.client.util.exec('free -b'); + const usedMemory = dataConversion(totalmem() - Number(availableMemory.split('\n')[1].split(' ').slice(-1)[0])); + const date = new Date(); + date.setMilliseconds(-(moment.duration(os.uptime(), 's').asMilliseconds())); + + const embed = new RichEmbed(); + embed.setTitle('System Information & Statistics'); + embed.addField('Hostname', os.hostname(), true); + embed.addField('Uptime', `${moment.duration(os.uptime(), 's').humanize()} | Last restart was on ${moment(date).format('dddd, MMMM Do YYYY, h:mm:ss A')} EST`, true); + embed.addField('CPU', `${os.cpus()[0].model} ${os.cpus()[0].speed / 1000}GHz | ${os.cpus().length} Cores | ${os.arch()}`, true); + embed.addField('Load Average (last 15 minutes)', os.loadavg()[2].toFixed(3), true); + embed.addField('Memory/RAM', `${usedMemory} / ${dataConversion(totalmem())}`, true); + embed.addField('Network Interfaces (IPv4)', os.networkInterfaces().eth0.filter((r) => r.family === 'IPv4')[0].address, true); + embed.addField('Network Interfaces (IPv6)', os.networkInterfaces().eth0.filter((r) => r.family === 'IPv6')[0].address.replace(/:/gi, '\:'), true); // eslint-disable-line + embed.setFooter(this.client.user.username, this.client.user.avatarURL); + embed.setTimestamp(); + // @ts-ignore + message.channel.createMessage({ embed }); + } +} diff --git a/src/commands/unlock.ts b/src/commands/unlock.ts index b16a9d8..c42ec21 100644 --- a/src/commands/unlock.ts +++ b/src/commands/unlock.ts @@ -14,12 +14,14 @@ export default class Unlock extends Command { public async run(message: Message, args: string[]) { // eslint-disable-line try { - const account = await this.client.db.Account.findOne({ $or: [{ account: args[0] }, { userID: args[0].replace(/[<@!>]/gi, '') }] }); + if (!args.length) return this.client.commands.get('help').run(message, [this.name]); + const account = await this.client.db.Account.findOne({ $or: [{ username: args[0] }, { userID: args[0].replace(/[<@!>]/gi, '') }] }); if (!account) return message.channel.createMessage(`***${this.client.stores.emojis.error} Cannot find user.***`); if (!account.locked) return message.channel.createMessage(`***${this.client.stores.emojis.error} This account is already unlocked.***`); const edit = await message.channel.createMessage(`***${this.client.stores.emojis.loading} Unlocking account...***`); if (account.username === 'matthew' || account.root) return edit.edit(`***${this.client.stores.emojis.error} Permission denied.***`); await this.client.util.exec(`unlock ${account.username}`); + await account.updateOne({ locked: false }); const moderation = new this.client.db.Moderation({ username: account.username, @@ -33,13 +35,14 @@ export default class Unlock extends Command { await moderation.save(); edit.edit(`***${this.client.stores.emojis.success} Account ${account.username} has been unlocked by Supervisor ${message.author.username}#${message.author.discriminator}.***`); const embed = new RichEmbed(); - embed.setTitle('Account Infraction | UnLock'); + embed.setTitle('Account Infraction | Unlock'); embed.setColor(15158332); embed.addField('User', `${account.username} | <@${account.userID}>`, true); embed.addField('Supervisor', `<@${message.author.id}>`, true); if (args.slice(1).join(' ').length > 0) embed.addField('Reason', args.slice(1).join(' '), true); embed.setFooter(this.client.user.username, this.client.user.avatarURL); embed.setTimestamp(); + message.delete(); this.client.getDMChannel(account.userID).then((user) => { // @ts-ignore user.createMessage({ embed }).catch(); diff --git a/src/events/messageCreate.ts b/src/events/messageCreate.ts index 5d225e6..a263db7 100644 --- a/src/events/messageCreate.ts +++ b/src/events/messageCreate.ts @@ -1,9 +1,7 @@ import { Message, TextChannel } from 'eris'; -import { Client, config } from '..'; +import { Client } from '..'; import Command from '../class/Command'; -const { prefix } = config; - export default class { public client: Client @@ -13,18 +11,30 @@ export default class { public async run(message: Message) { try { - const noPrefix: string[] = message.content.slice(prefix.length).trim().split(/ +/g); + if (message.author.bot) return; + if (message.content.indexOf(this.client.config.prefix) !== 0) return; + const noPrefix: string[] = message.content.slice(this.client.config.prefix.length).trim().split(/ +/g); const command: string = noPrefix[0].toLowerCase(); const resolved: Command = this.client.util.resolveCommand(command); if (!resolved) return; if (resolved.guildOnly && !(message.channel instanceof TextChannel)) return; - const hasUserPerms: boolean = resolved.permissions.users.includes(message.author.id); + let hasUserPerms: boolean; + if (resolved.permissions.users) { + hasUserPerms = resolved.permissions.users.includes(message.author.id); + } let hasRolePerms: boolean = false; - for (const role of resolved.permissions.roles) { - if (message.member && message.member.roles.includes(role)) { - hasRolePerms = true; break; + if (resolved.permissions.roles) { + for (const role of resolved.permissions.roles) { + if (message.member && message.member.roles.includes(role)) { + // this.client.signale.debug(message.member.roles.includes(role)); + hasRolePerms = true; break; + } } } + if (!resolved.permissions.users && !resolved.permissions.roles) { + hasUserPerms = true; + hasRolePerms = true; + } if (!hasRolePerms && !hasUserPerms) return; if (!resolved.enabled) { message.channel.createMessage(`***${this.client.stores.emojis.error} This command has been disabled***`); return; } const args: string[] = noPrefix.slice(1); diff --git a/src/functions/checkLock.ts b/src/functions/checkLock.ts index 791d561..eb4e3f9 100644 --- a/src/functions/checkLock.ts +++ b/src/functions/checkLock.ts @@ -3,7 +3,7 @@ import { Client } from '..'; import { RichEmbed } from '../class'; export default function checkLock(client: Client) { - setTimeout(async () => { + setInterval(async () => { try { const moderations = await client.db.Moderation.find(); moderations.forEach(async (moderation) => { @@ -13,7 +13,8 @@ export default function checkLock(client: Client) { const account = await client.db.Account.findOne({ username: moderation.username }); if (!account) return; await client.util.exec(`unlock ${account.username}`); - await moderation.update({ 'expiration.processed': true }); + await moderation.updateOne({ 'expiration.processed': true }); + await account.updateOne({ locked: false }); const mod = new client.db.Moderation({ username: account.username, userID: account.userID, @@ -38,10 +39,11 @@ export default function checkLock(client: Client) { }); // @ts-ignore client.createMessage('580950455581147146', { embed }); + client.signale.complete(`Unlocked account ${account.username} | Queue date at ${moderation.expiration.date.toLocaleString('en-us')}`); } }); } catch (error) { await client.util.handleError(error); } - }); + }, 10000); } diff --git a/src/functions/dataConversion.ts b/src/functions/dataConversion.ts new file mode 100644 index 0000000..e38337d --- /dev/null +++ b/src/functions/dataConversion.ts @@ -0,0 +1,5 @@ +export default function dataConversion(bytes: number) { + const i = Math.floor(Math.log(bytes) / Math.log(1024)); + const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; + return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`; +} diff --git a/src/functions/index.ts b/src/functions/index.ts new file mode 100644 index 0000000..b4047fb --- /dev/null +++ b/src/functions/index.ts @@ -0,0 +1,2 @@ +export { default as checkLock } from './checkLock'; +export { default as dataConversion } from './dataConversion'; diff --git a/src/index.ts b/src/index.ts index 4dfe706..4d8c716 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,4 +5,3 @@ export { default as Commands } from './commands'; export { default as Events } from './events'; export { default as Models } from './models'; export { default as Stores } from './stores'; -export { default as Util } from './Util'; diff --git a/src/models/Moderation.ts b/src/models/Moderation.ts index 2bafd1f..67bd184 100644 --- a/src/models/Moderation.ts +++ b/src/models/Moderation.ts @@ -25,10 +25,10 @@ export interface ModerationInterface extends Document { const Moderation: Schema = new Schema({ username: String, userID: String, - logID: Number, + logID: String, moderatorID: String, reason: String, - type: String, + type: Number, date: Date, expiration: { date: Date, diff --git a/src/stores/emojis.ts b/src/stores/emojis.ts index ffe301b..7e46d8c 100644 --- a/src/stores/emojis.ts +++ b/src/stores/emojis.ts @@ -1,5 +1,5 @@ export default { success: '<:modSuccess:578750988907970567>', loading: '', - error: '<:modError:578750737920688128', + error: '<:modError:578750737920688128>', };