1
0
Fork 0
refactor/models
Bsian 2019-10-28 20:21:04 +00:00
parent 3942e96524
commit 246944c3ce
No known key found for this signature in database
GPG Key ID: 097FB9A291026091
22 changed files with 300 additions and 60 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@ node_modules
yarn.lock
src/config.json
package-lock.json
htmlEmail_templates

View File

@ -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();

View File

@ -28,5 +28,6 @@ export default class Command {
this.aliases = [];
this.guildOnly = true;
this.client = client;
this.permissions = {};
}
}

View File

@ -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;

View File

@ -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<void> {
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<string> {
public async messageCollector(message: Message, question: string, timeout: number, shouldDelete = false, choices: string[] = null, filter = (msg: Message): boolean|void => {}): Promise<Message> {
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); }
});
});
}

View File

@ -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) {

View File

@ -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 <support@libraryofcode.org>',
subject: 'Your account has been created',
html: `
<body>
<style>* {font-family: 'Calibri';}</style>
<h1>Library of Code | Cloud Services</h1>
<h2>Your Cloud Account has been created, welcome! Please see below for some details regarding your account and our services</h2>
<p><b>Username:</b> ${args[2]}</p>
<p><b>SSH Login:</b> <pre><code style="font-family: Courier;">ssh ${args[2]}@cloud.libraryofcode.org</code></pre>
<p><b>Email address (see below for further information):</b> ${args[2]}@cloud.libraryofcode.org</p>
<h2>Useful information</h2>
<h3>How to log in:</h3>
<ol>
<li>Open your desired terminal application - we recommend using <a target="_blank" href="https://www.gnu.org/software/bash/">Bash</a>, but you can use your computer's default</li>
<li>Type in your SSH Login as above</li>
<li>When prompted, enter your password <em>Please note that inputs will be blank, so be careful not to type in your password incorrectly</em></li>
</ol>
<p>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 <a target="_blank" href="https://whatismyip.com">public IPv4 address</a>.
<h3>Setting up your cloud email</h3>
<p>All email applications are different, so here are some information you can use to connect your email</p>
<ul>
<li><b>Server:</b> cloud.libraryofcode.org</li>
<li><b>Account username/password:</b> Normal login</li>
<li><b>Account type (incoming):</b> IMAP</li>
<li><b>Incoming port:</b> 143 (993 if you're using TLS security type)</li>
<li><b>Incoming Security Type:</b> STARTTLS (TLS if you're using port 993)</li>
<li><b>Outgoing port:</b> 587 (If that doesn't work, try 25)</li>
<li><b>Outgoing Security Type:</b> STARTTLS</li>
</ul>
<h3>Channels and Links</h3>
<ul>
<li><a target="_blank" href="https://discordapp.com/channels/446067825673633794/622856541325885453">#status</a> - You can find the status of all our services, including the cloud machine, here</li>
<li><a target="_blank" href="https://discordapp.com/channels/446067825673633794/620355063088414769">#cloud-announcements</a> - Announcements regarding the cloud machine will be here. These include planned maintenance, updates to preinstalled services etc.</li>
<li><a target="_blank" href="https://discordapp.com/channels/446067825673633794/620349128295186472">#cloud-info</a> - Important information you will need to, or should, know to a certain extent. These include our infractions system and Tier limits</li>
<li><a target="_blank" href="https://discordapp.com/channels/446067825673633794/546457788184789013">#cloud-support</a> - A support channel specifically for the cloud machine, you can use this to ask how to renew your certificates, for example</li>
<li><a target="_blank" href="https://support.libraryofcode.us">Library of Code Support Desk</a> - Our Support desk, you will find some handy info there</li>
<li><a target="_blank" href="https://www.securesign.org">SecureSign</a> - our certificates manager</li>
</ul>
<h3>Want to support us?</h3>
<p>You can support us on Patreon! Head to <a target="_blank" href="https://www.patreon.com/libraryofcode">our Patreon page</a> and feel free to donate as much or as little as you want!<br>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!</p>
<b><i>Library of Code sp-us | Support Team</i></b>
</body>
`,
});
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);
}

View File

@ -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

View File

@ -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 <support@libraryofcode.org>',
subject: 'Your account has been deleted',
html: `
<h1>Library of Code | Cloud Services</h1>
<p>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.</p>
<p><b>Reason:</b> ${reason}</p>
<p><b>Engineer:</b> ${message.author.username}</p>
<b><i>Library of Code sp-us | Support Team</i></b>
`,
});
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);
}
}
}

View File

@ -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(''));

View File

@ -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);

View File

@ -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();

View File

@ -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;
});
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);

36
src/commands/sysinfo.ts Normal file
View File

@ -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 });
}
}

View File

@ -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();

View File

@ -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;
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);

View File

@ -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);
}

View File

@ -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]}`;
}

2
src/functions/index.ts Normal file
View File

@ -0,0 +1,2 @@
export { default as checkLock } from './checkLock';
export { default as dataConversion } from './dataConversion';

View File

@ -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';

View File

@ -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,

View File

@ -1,5 +1,5 @@
export default {
success: '<:modSuccess:578750988907970567>',
loading: '<a:modloading:588607353935364106>',
error: '<:modError:578750737920688128',
error: '<:modError:578750737920688128>',
};