Merge branch 'dev'

merge-requests/17/merge
Matthew 2021-03-05 23:21:38 -05:00
commit 3d01c1262f
No known key found for this signature in database
GPG Key ID: 210AF32ADE3B5C4B
24 changed files with 1211 additions and 1310 deletions

View File

@ -10,9 +10,7 @@
"es6": true,
"node": true
},
"extends": [
"airbnb-base"
],
"extends": ["airbnb-base"],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
@ -22,16 +20,14 @@
"ecmaVersion": 2020,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"plugins": ["@typescript-eslint"],
"rules": {
"linebreak-style": "off",
"no-unused-vars": "off",
"max-len": "off",
"import/no-dynamic-require": "off",
"global-require": "off",
"class-methods-use-this":"off",
"class-methods-use-this": "off",
"no-restricted-syntax": "off",
"camelcase": "off",
"indent": "warn",
@ -41,7 +37,10 @@
"@typescript-eslint/no-useless-constructor": 2,
"import/extensions": "off",
"no-param-reassign": "off",
"no-underscore-dangle": "off"
"no-underscore-dangle": "off",
"keyword-spacing": "off",
"no-multiple-empty-lines": "off",
"consistent-return": "off"
},
"ignorePatterns": "**/*.js"
}

1
.gitignore vendored
View File

@ -1,6 +1,5 @@
# Package Management & Libraries
node_modules
package-lock.json
# Configuration Files
config.yaml

View File

@ -19,14 +19,14 @@
"@types/jsonwebtoken": "^8.5.0",
"@types/mathjs": "^6.0.7",
"@types/mongoose": "^5.7.19",
"@types/node": "^14.0.1",
"@types/node": "^14.14.25",
"@types/nodemailer": "^6.4.0",
"@types/puppeteer": "^5.4.2",
"@types/puppeteer": "^5.4.3",
"@types/signale": "^1.4.1",
"@types/uuid": "^7.0.3",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"eslint": "^7.0.0",
"eslint": "^7.19.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"tslib": "^2.1.0",
@ -36,21 +36,21 @@
"@google-cloud/text-to-speech": "^3.1.2",
"ari-client": "^2.2.0",
"asterisk-manager": "^0.1.16",
"awesome-phonenumber": "^2.41.0",
"awesome-phonenumber": "^2.45.0",
"axios": "^0.19.2",
"body-parser": "^1.19.0",
"brain.js": "^2.0.0-beta.2",
"bull": "^3.18.1",
"bull": "^3.20.1",
"cheerio": "^1.0.0-rc.5",
"cron": "^1.8.2",
"eris": "^0.13.3",
"eris-pagination": "bsian03/eris-pagination",
"eris": "^0.14.0",
"eris-pagination": "github:bsian03/eris-pagination",
"express": "^4.17.1",
"helmet": "^3.22.0",
"jsonwebtoken": "^8.5.1",
"mathjs": "^7.6.0",
"moment": "^2.25.3",
"mongoose": "^5.9.13",
"mongoose": "^5.11.15",
"nodemailer": "^6.4.8",
"pluris": "^0.2.5",
"puppeteer": "^5.5.0",

View File

@ -76,7 +76,6 @@ export default class Report extends Route {
return res.status(403).json({ code: this.constants.codes.PERMISSION_DENIED, message: this.constants.messages.PERMISSION_DENIED });
}
const flags = [];
if (mem.user.publicFlags) {
if ((mem.user.publicFlags & (1 << 0)) === 1 << 0) flags.push('DISCORD_EMPLOYEE');
@ -325,7 +324,6 @@ export default class Report extends Route {
array.push(data);
}
return res.status(200).json({
code: this.constants.codes.SUCCESS,
message: {
@ -408,7 +406,6 @@ export default class Report extends Route {
else if (member.cloudServices > 10) cloudServicesScore = 10;
else cloudServicesScore = Math.round(member.cloudServices);
return res.status(200).json({
code: this.constants.codes.SUCCESS,
message: {
@ -587,11 +584,11 @@ export default class Report extends Route {
name?: string,
department?: string,
date?: Date,
}> jwt.verify(req.query.code.toString(), this.server.client.config.internalKey);
}>jwt.verify(req.query.code.toString(), this.server.client.config.internalKey);
} catch {
return res.status(401).json({ code: this.constants.codes.UNAUTHORIZED, message: this.constants.messages.UNAUTHORIZED });
}
const chan = <TextChannel> this.server.client.guilds.get(this.constants.discord.SERVER_ID).channels.get(offer.channelID);
const chan = <TextChannel>this.server.client.guilds.get(this.constants.discord.SERVER_ID).channels.get(offer.channelID);
await chan.createMessage(`__**PRE-APPROVED OFFER ACCEPTED**__\n<@${offer.staffID}>`);
const message = await chan.getMessage(offer.messageID);
const args = [];

View File

@ -1,9 +1,10 @@
/* eslint-disable no-constant-condition */
import { promises as fs, accessSync, constants, writeFileSync } from 'fs';
import { promisify } from 'util';
import { join } from 'path';
import { gzip, gzipSync, unzip } from 'zlib';
type JSONData = [{key: string, value: any}?];
type JSONData = [{ key: string, value: any }?];
/**
@ -18,7 +19,7 @@ export default class LocalStorage {
private locked: boolean = false;
constructor(dbName: string, dir = `${__dirname}/../../localstorage`) {
this.storagePath = `${dir}/${dbName}.json.gz`;
this.storagePath = join(__dirname, '../../localstorage') || dir;
this.init();
}
@ -42,7 +43,7 @@ export default class LocalStorage {
*/
static async compress(data: string): Promise<Buffer> {
const func = promisify(gzip);
const comp = <Buffer> await func(data);
const comp = <Buffer>await func(data);
return comp;
}
@ -57,7 +58,7 @@ export default class LocalStorage {
*/
static async decompress(data: Buffer): Promise<string> {
const func = promisify(unzip);
const uncomp = <Buffer> await func(data);
const uncomp = <Buffer>await func(data);
return uncomp.toString();
}
@ -91,7 +92,7 @@ export default class LocalStorage {
* await LocalStorage.get<type>('data-key');
* @param key The key for the data entry.
*/
public async getMany<T>(key: string): Promise<{key: string, value: T}[]> {
public async getMany<T>(key: string): Promise<{ key: string, value: T }[]> {
while (true) {
if (!this.locked) break;
}

View File

@ -56,6 +56,7 @@ export default class Server {
public init() {
if (this.parse) {
this.app.use(bodyParser.json());
this.app.use(bodyParser.urlencoded({ extended: true }));
}
this.app.set('trust proxy', 'loopback');
this.app.use(helmet({

View File

@ -115,7 +115,6 @@ export default class Util {
return `${(bytes / 1024 ** i).toFixed(2)} ${sizes[i]}`;
}
public async exec(command: string, _options: childProcess.ExecOptions = {}): Promise<string> {
const ex = promisify(childProcess.exec);
try {
@ -148,7 +147,7 @@ export default class Util {
* @param query Command input
* @param message Only used to check for errors
*/
public resolveCommand(query: string | string[], message?: Message): Promise<{cmd: Command, args: string[] }> {
public resolveCommand(query: string | string[], message?: Message): Promise<{ cmd: Command, args: string[] }> {
try {
let resolvedCommand: Command;
if (typeof query === 'string') query = query.split(' ');
@ -238,7 +237,7 @@ export default class Util {
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}[][] = [[]];
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();
@ -303,4 +302,9 @@ export default class Util {
}
return `${i}th`;
}
public capsFirstLetter(string?: string): string | void {
if (typeof string !== 'string') return undefined;
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
}

View File

@ -38,7 +38,7 @@ export default class Apply extends Command {
validation: (member: Member) => member.roles.includes('546457886440685578'),
func: async (client: Client, ...data: any[]) => {
const member = await client.guilds.get(client.config.guildID).getRESTMember(data[0]);
const ax = <AxiosStatic> require('axios');
const ax = <AxiosStatic>require('axios');
await ax({
method: 'get',
url: `https://api.cloud.libraryofcode.org/wh/t2?userID=${member.id}&auth=${client.config.internalKey}`,

View File

@ -23,7 +23,7 @@ export default class Billing_T3 extends Command {
emailAddress?: string,
tier?: number,
supportKey?: string,
}> (await axios.get(`https://api.cloud.libraryofcode.org/wh/info?id=${message.author.id}&authorization=${this.client.config.internalKey}`)).data;
}>(await axios.get(`https://api.cloud.libraryofcode.org/wh/info?id=${message.author.id}&authorization=${this.client.config.internalKey}`)).data;
if (!response.found) return this.error(message.channel, 'CS Account not found.');
const customer = await this.client.db.Customer.findOne({ userID: message.author.id });

View File

@ -29,12 +29,14 @@ export { default as npm } from './npm';
export { default as offer } from './offer';
export { default as page } from './page';
export { default as ping } from './ping';
export { default as profile } from './profile';
export { default as pulldata } from './pulldata';
export { default as rank } from './rank';
export { default as roleinfo } from './roleinfo';
export { default as score } from './score';
export { default as sip } from './sip';
export { default as site } from './site';
export { default as slowmode } from './slowmode';
export { default as stats } from './stats';
export { default as storemessages } from './storemessages';
export { default as sysinfo } from './sysinfo';

26
src/commands/profile.ts Normal file
View File

@ -0,0 +1,26 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
import Profile_Bio from './profile_bio';
import Profile_GitHub from './profile_github';
import Profile_Gitlab from './profile_gitlab';
export default class Profile extends Command {
constructor(client: Client) {
super(client);
this.name = 'profile';
this.description = 'Manages your profile on CR.';
this.usage = 'profile <bio/github/gitlab> <new value>\n*Provide no value in subcommand to clear data.';
this.permissions = 0;
this.enabled = true;
this.subcmds = [Profile_Bio, Profile_GitHub, Profile_Gitlab];
}
public async run(message: Message) {
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
await this.client.db.Member.create({ userID: message.author.id });
}
this.error(message.channel, `Please specify a valid option to change. Choose from \`github\`, \`bio\` and \`gitlab\`. You can view your profile with \`${this.client.config.prefix}whois\`.`);
}
}

View File

@ -0,0 +1,39 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
export default class Profile_Bio extends Command {
constructor(client: Client) {
super(client);
this.name = 'bio';
this.description = 'Updates your bio on your profile.';
this.usage = `${this.client.config.prefix}bio <new bio>`;
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
await this.client.db.Member.create({ userID: message.author.id });
}
const member = await this.client.db.Member.findOne({ userID: message.author.id });
if (!args[0]) {
await member.updateOne({
additional: {
...member.additional,
bio: null,
},
});
return message.addReaction('modSuccess:578750988907970567');
}
const bio = args.join(' ');
if (bio.length >= 256) return this.error(message.channel, 'Bio too long. It must be less than or equal to 256 characters.');
await member.updateOne({
additional: {
...member.additional,
bio,
},
});
return message.addReaction('modSuccess:578750988907970567');
}
}

View File

@ -0,0 +1,47 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
export default class Profile_GitHub extends Command {
constructor(client: Client) {
super(client);
this.name = 'github';
this.description = 'Updates your GitHub information on your profile.';
this.usage = `${this.client.config.prefix}github <GitHub profile URL>`;
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
await this.client.db.Member.create({ userID: message.author.id });
}
const member = await this.client.db.Member.findOne({ userID: message.author.id });
if (!args[0]) {
await member.updateOne({
additional: {
...member.additional,
github: null,
},
});
return message.addReaction('modSuccess:578750988907970567');
}
const urlRegex = new RegExp(
'^(https?:\\/\\/)?'
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'
+ '((\\d{1,3}\\.){3}\\d{1,3}))'
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'
+ '(\\?[;&a-z\\d%_.~+=-]*)?'
+ '(\\#[-a-z\\d_]*)?$',
'i',
);
if (!urlRegex.test(args[0]) || !args[0].startsWith('https://github.com/')) return this.error(message.channel, 'Invalid GitHub profile URL.');
await member.updateOne({
additional: {
...member.additional,
github: args[0],
},
});
return message.addReaction('modSuccess:578750988907970567');
}
}

View File

@ -0,0 +1,48 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
export default class Profile_GitLab extends Command {
constructor(client: Client) {
super(client);
this.name = 'gitlab';
this.description = 'Updates your GitLab information on your profile.';
this.usage = `${this.client.config.prefix}gitlab <GitLab profile URL>`;
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
if (!await this.client.db.Member.exists({ userID: message.author.id })) {
await this.client.db.Member.create({ userID: message.author.id });
}
if (!args[0]) return this.error(message.channel, 'No GitLab profile URL was provided.');
const member = await this.client.db.Member.findOne({ userID: message.author.id });
if (!args[0]) {
await member.updateOne({
additional: {
...member.additional,
gitlab: null,
},
});
return message.addReaction('modSuccess:578750988907970567');
}
const urlRegex = new RegExp(
'^(https?:\\/\\/)?'
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|'
+ '((\\d{1,3}\\.){3}\\d{1,3}))'
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*'
+ '(\\?[;&a-z\\d%_.~+=-]*)?'
+ '(\\#[-a-z\\d_]*)?$',
'i',
);
if (!urlRegex.test(args[0])) return this.error(message.channel, 'Invalid GitLab profile URL.');
await member.updateOne({
additional: {
...member.additional,
gitlab: args[0],
},
});
return message.addReaction('modSuccess:578750988907970567');
}
}

29
src/commands/setnick.ts Normal file
View File

@ -0,0 +1,29 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
export default class Setnick extends Command {
constructor(client: Client) {
super(client);
this.name = 'setnick';
this.description = 'Changes the nickname of a member';
this.usage = 'setnick <member> [new nickname]';
this.permissions = 2;
this.guildOnly = true;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
const member = this.client.util.resolveMember(args[0], this.mainGuild);
if (!member) return this.error(message.channel, 'Cannot find user.');
let nickname = args.slice(1).join(' ');
if (args.length === 1) nickname = null;
if (nickname?.length > 32) return this.error(message.channel, 'New nickname may not be more than 32 characters long.');
await member.edit({ nick: nickname });
return this.success(message.channel, `Updated the nickname of ${member.user.username}#${member.user.discriminator}.`);
} catch (err) {
return this.client.util.handleError(err, message, this, false);
}
}
}

34
src/commands/slowmode.ts Normal file
View File

@ -0,0 +1,34 @@
import { Message, GuildTextableChannel } from 'eris';
import moment, { unitOfTime } from 'moment';
import { Client, Command } from '../class';
export default class Slowmode extends Command {
regex: RegExp;
constructor(client: Client) {
super(client);
this.name = 'slowmode';
this.description = 'Set slowmode to a channel.';
this.usage = 'slowmode <length[unit]>';
this.permissions = 1;
this.guildOnly = true;
this.enabled = true;
this.regex = /[a-z]+|[^a-z]+/gi;
}
public async run(message: Message<GuildTextableChannel>, args: string[]) {
try {
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
const [length, unit] = args[0].match(this.regex);
if (Number.isNaN(Number(length))) return this.error(message.channel, 'Could not determine the slowmode time.');
const momentSeconds: number = Math.round(moment.duration(length, unit as unitOfTime.Base || 's').asSeconds());
if (momentSeconds > 21600 || momentSeconds < 0) return this.error(message.channel, 'Slowmode must be between 0 seconds and 6 hours.');
return message.channel.edit({ rateLimitPerUser: momentSeconds }).then((c) => message.addReaction(':success:477618704155410452'));
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

View File

@ -56,14 +56,15 @@ export default class Whois extends Command {
if (ackResolve?.extension) {
description += `☎️ ${ackResolve.extension}\n`;
}
if (ackResolve?.gitlab) {
description += `${emotes.gitlab} ${ackResolve.gitlab}\n`;
const memberProfile = await this.client.db.Member.findOne({ userID: message.author.id }).lean().exec();
if (memberProfile?.additional?.gitlab) {
description += `${emotes.gitlab} ${memberProfile?.additional.gitlab}\n`;
}
if (ackResolve?.github) {
description += `${emotes.github} ${ackResolve.github}\n`;
if (memberProfile?.additional?.github) {
description += `${emotes.github} ${memberProfile?.additional.github}\n`;
}
if (ackResolve?.bio) {
description += `${emotes.bio} *${ackResolve.bio}*\n`;
if (memberProfile?.additional?.bio) {
description += `${emotes.bio} *${memberProfile?.additional.bio}*\n`;
}
description += `\n<@${member.id}>`;
embed.setDescription(description);
@ -74,7 +75,7 @@ export default class Whois extends Command {
break;
}
}
embed.addField('Status', member.status === 'dnd' ? 'Do Not Disturb' : this.capsFirstLetter(member.status) || 'Offline', true);
embed.addField('Status', member.status === 'dnd' ? 'Do Not Disturb' : this.client.util.capsFirstLetter(member.status) || 'Offline', 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);
const score = await this.client.db.Score.findOne({ userID: member.id }).lean().exec();
@ -223,13 +224,4 @@ export default class Whois extends Command {
return this.client.util.handleError(err, message, this);
}
}
public resolveStaffInformation(id: string) {
return null; // acknowledgements.find((m) => m.id === id);
}
public capsFirstLetter(string?: string): string | void {
if (typeof string !== 'string') return undefined;
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
}

View File

@ -71,7 +71,7 @@ async function setupDepartmentCodes(client: Client): Promise<void> {
function logNewPager(client: Client, num: string, member: Member): void {
client.util.signale.log(`Pager Number '${num}' created for '${member.user.username}#${member.user.discriminator}'.`);
const channel = <TextableChannel> client.guilds.get(client.config.guildID).channels.get('722636436716781619');
const channel = <TextableChannel>client.guilds.get(client.config.guildID).channels.get('722636436716781619');
channel.createMessage(`__**'${member.user.username}#${member.user.discriminator}' assigned to pager number '${num}'.**__`);
}

View File

@ -4,6 +4,7 @@
import sdNotify from 'sd-notify';
import { parse } from 'yaml';
import { promises as fs } from 'fs';
import { join } from 'path';
import { Client } from './class';
import * as eventFiles from './events';
import * as commandFiles from './commands';
@ -14,7 +15,7 @@ async function main(): Promise<void> {
sdNotify.ready();
sdNotify.startWatchdogMode(2500);
}
const read = await fs.readFile('../config.yaml', 'utf8');
const read = await fs.readFile(join(__dirname, '../config.yaml'), 'utf8');
const config: Config = parse(read);
const client = new Client(config.token, { defaultImageFormat: 'png', restMode: true, intents: ['guildBans', 'guildEmojis', 'guildInvites', 'guildMembers', 'guildMessageReactions', 'guildMessages', 'guildPresences', 'guildWebhooks', 'guilds', 'directMessages'] });
client.config = config;

View File

@ -5,8 +5,10 @@ export interface MemberInterface extends Document {
additional: {
langs: ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'],
operatingSystems: ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'],
},
github: string,
gitlab: string,
bio: string,
},
}
const Member: Schema = new Schema({
@ -14,8 +16,10 @@ const Member: Schema = new Schema({
additional: {
langs: Array,
operatingSystems: Array,
},
github: String,
gitlab: String,
bio: String,
},
});
export default model<MemberInterface>('Member', Member);

View File

@ -7,9 +7,6 @@ export interface StaffInterface extends Document {
dept: string,
pn: string[],
emailAddress: string,
gitlab: string,
github: string,
bio: string,
extension: string,
acknowledgements: string[],
additionalRoles: string[]
@ -22,9 +19,6 @@ const Staff: Schema = new Schema({
dept: String,
pn: Array,
emailAddress: String,
gitlab: String,
github: String,
bio: String,
extension: String,
acknowledgements: Array,
additionalRoles: Array,

1814
yarn.lock

File diff suppressed because it is too large Load Diff