fixed conflict

merge-requests/6/head
DedShot™#9195 2020-05-18 22:55:32 -04:00
commit d4ef0db1cf
49 changed files with 3316 additions and 166 deletions

View File

@ -39,6 +39,7 @@
"import/prefer-default-export": "off",
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": 2,
"import/extensions": "off"
"import/extensions": "off",
"no-param-reassign": "off"
}
}

3
.gitignore vendored
View File

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

15
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,15 @@
stages:
- lint
- build
lint:
stage: lint
script: |
yarn install
yarn lint
tsc:
stage: build
script: |
yarn install
tsc -p tsconfig.json -noEmit

View File

@ -0,0 +1,13 @@
# BUG REPORT
**Brief Description:**
**Priority:** (1-5, 5 being the most urgent)
**Steps to Reproduce:** (separated with numbers)
**Expected Result:**
**Actual Result:**
**Notes:** (delete if none)

View File

@ -0,0 +1,5 @@
## FEATURE REQUEST / SUGGESTION
**Request:**
**Description:**

View File

@ -9,3 +9,8 @@ We accept contributions from the community, however there's a few steps you need
## Issues
If you're interested in tackling an issue, please comment on that particular issue that you're handling it so Maintainers can label it appropriately.
## Other Information
* Make sure your contributions match the current style of the code, run `yarn run lint` to find issues with the style. Requests will be denied if they do not comply with styling.
* Submit your merge requests to the **dev** branch only.
* If you can use TypeScript functionality, do it. For example, don't declare something as `any` if it can be typed.

View File

@ -4,7 +4,7 @@ clean:
@-rm -rf build
build:
tsc -p ./tsconfig.json
-tsc -p ./tsconfig.json
run:
cd build && node main

View File

@ -3,30 +3,37 @@
"version": "1.0.0",
"description": "The official system for handling Community Relations in the LOC Discord server.",
"main": "build/main.js",
"scripts": {
"lint": "eslint -c ./.eslintrc.json src --ext ts"
},
"repository": "https://gitlab.libraryofcode.org/engineering/communityrelations.git",
"author": "Matthew R <matthew@staff.libraryofcode.org>",
"license": "AGPL-3.0",
"private": false,
"devDependencies": {
"@types/mongoose": "^5.7.10",
"@types/node": "^13.11.0",
"@types/express": "^4.17.6",
"@types/helmet": "^0.0.47",
"@types/mongoose": "^5.7.19",
"@types/node": "^14.0.1",
"@types/signale": "^1.4.1",
"@types/uuid": "^7.0.2",
"@types/yaml": "^1.2.0",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"eslint": "^6.8.0",
"@types/uuid": "^7.0.3",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"eslint": "^7.0.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2",
"typescript": "^3.8.3"
"typescript": "^3.9.2"
},
"dependencies": {
"axios": "^0.19.2",
"eris": "bsian03/eris#bsian",
"moment": "^2.24.0",
"mongoose": "^5.9.9",
"eris-pagination": "bsian03/eris-pagination",
"express": "^4.17.1",
"helmet": "^3.22.0",
"moment": "^2.25.3",
"mongoose": "^5.9.13",
"signale": "^1.4.0",
"uuid": "^7.0.3",
"yaml": "^1.8.3"
"uuid": "^8.0.0",
"yaml": "^1.9.2"
}
}

5
src/api/index.ts Normal file
View File

@ -0,0 +1,5 @@
import locsh from './loc.sh/main';
export default {
'loc.sh': locsh,
};

6
src/api/loc.sh/main.ts Normal file
View File

@ -0,0 +1,6 @@
import { Server, ServerManagement } from '../../class';
export default (management: ServerManagement) => {
const server = new Server(management, 3890, `${__dirname}/routes`);
return server;
};

View File

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

View File

@ -0,0 +1,24 @@
import { Route, Server } from '../../../class';
import { RedirectRaw } from '../../../models';
export default class Root extends Route {
constructor(server: Server) {
super(server);
this.conf = {
path: '/',
};
}
public bind() {
this.router.get('/:key', async (req, res) => {
try {
const link: RedirectRaw = await this.server.client.db.Redirect.findOne({ key: req.params.key }).lean().exec();
if (!link) return res.status(404).json({ code: this.constants.codes.NOT_FOUND, message: this.constants.messages.NOT_FOUND });
return res.redirect(link.to);
} catch (err) {
this.server.client.util.handleError(err);
return res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: this.constants.messages.SERVER_ERROR });
}
});
}
}

View File

@ -1,26 +1,32 @@
import eris from 'eris';
import mongoose from 'mongoose';
import { promises as fs } from 'fs';
import { Collection, Command, Util } from '.';
import { Moderation, ModerationInterface } from '../models';
import { Collection, Command, Util, ServerManagement, Event } from '.';
import { Member, MemberInterface, Moderation, ModerationInterface, Redirect, RedirectInterface } from '../models';
import * as eventFiles from '../events';
import * as commandFiles from '../commands';
export default class Client extends eris.Client {
public config: { token: string, prefix: string, guildID: string, mongoDB: string };
public commands: Collection<Command>;
public events: Collection<Event>;
public intervals: Collection<NodeJS.Timeout>;
public util: Util;
public db: { moderation: mongoose.Model<ModerationInterface> };
public serverManagement: ServerManagement;
public db: { Member: mongoose.Model<MemberInterface>, Moderation: mongoose.Model<ModerationInterface>, Redirect: mongoose.Model<RedirectInterface> };
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
constructor(token: string, options?: eris.ClientOptions) {
super(token, options);
this.commands = new Collection<Command>();
this.events = new Collection<Event>();
this.intervals = new Collection<NodeJS.Timeout>();
this.db = { moderation: Moderation };
this.db = { Member, Moderation, Redirect };
}
public async loadDatabase() {
@ -29,6 +35,7 @@ export default class Client extends eris.Client {
public loadPlugins() {
this.util = new Util(this);
this.serverManagement = new ServerManagement(this);
}
public async loadIntervals() {
@ -43,25 +50,22 @@ export default class Client extends eris.Client {
}
public async loadEvents() {
const evtFiles = await fs.readdir(`${__dirname}/../events`);
evtFiles.forEach((file) => {
const eventName = file.split('.')[0];
if (file === 'index.js') return;
// eslint-disable-next-line
const event = new (require(`${__dirname}/../events/${file}`).default)(this);
this.on(eventName, (...args) => event.run(...args));
this.util.signale.success(`Successfully loaded event: ${eventName}`);
delete require.cache[require.resolve(`${__dirname}/../events/${file}`)];
});
const evtFiles = Object.entries<new(client: Client) => Event>(eventFiles);
for (const [name, Ev] of evtFiles) {
const event = new Ev(this);
this.events.add(event.event, event);
this.on(event.event, event.run);
this.util.signale.success(`Successfully loaded event: ${name}`);
delete require.cache[require.resolve(`${__dirname}/../events/${name}`)];
}
}
public async loadCommands() {
const commandFiles = await fs.readdir(`${__dirname}/../commands`);
commandFiles.forEach((file) => {
// eslint-disable-next-line new-cap
const command: Command = new (require(`${__dirname}/../commands/${file}`).default)(this);
const cmdFiles = Object.values<new(client: Client) => Command>(commandFiles);
for (const Cmd of cmdFiles) {
const command = new Cmd(this);
this.commands.add(command.name, command);
this.util.signale.success(`Successfully loaded command: ${command.name}`);
});
}
}
}

View File

@ -26,10 +26,13 @@ export default class Command {
/**
* - **0:** Everyone
* - **1:** Associates Team+
* - **2:** Sheriff+
* - **3:** Faculty Marshals+
* - **4:** Marshal Generals of Engineering
* - **1:** Associates+
* - **2:** Core Team+
* - **3:** Moderators, Supervisor, & Board of Directors
* - **4:** Technicians, Supervisor, & Board of Directors
* - **5:** Moderators, Technicians, Supervisor, & Board of Directors
* - **6:** Supervisor+
* - **7:** Board of Directors
*/
public permissions: number;
@ -57,13 +60,19 @@ export default class Command {
case 0:
return true;
case 1:
return member.roles.some((r) => ['662163685439045632', '455972169449734144', '453689940140883988'].includes(r));
return member.roles.some((r) => ['701481967149121627', '453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
case 2:
return member.roles.some((r) => ['662163685439045632', '455972169449734144'].includes(r));
return member.roles.some((r) => ['453689940140883988', '455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
case 3:
return member.roles.some((r) => ['662163685439045632'].includes(r));
return member.roles.some((r) => ['455972169449734144', '701454855952138300', '662163685439045632'].includes(r));
case 4:
return member.id === '278620217221971968' || member.id === '253600545972027394';
return member.roles.some((r) => ['701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
case 5:
return member.roles.some((r) => ['455972169449734144', '701454780828221450', '701454855952138300', '662163685439045632'].includes(r));
case 6:
return member.roles.some((r) => ['701454855952138300', '662163685439045632'].includes(r));
case 7:
return member.roles.includes('662163685439045632');
default:
return false;
}

14
src/class/Event.ts Normal file
View File

@ -0,0 +1,14 @@
import { Client } from '.';
export default class Event {
public client: Client
public event: string;
constructor(client: Client) {
this.client = client;
this.event = '';
}
public async run(...args: any[]): Promise<void> { return Promise.resolve(); }
}

View File

@ -1,6 +1,6 @@
/* eslint-disable no-bitwise */
import { Member, User } from 'eris';
import { v4 as uuid } from 'uuid';
import { randomBytes } from 'crypto';
import moment, { unitOfTime } from 'moment';
import { Client, RichEmbed } from '.';
import { Moderation as ModerationModel, ModerationInterface } from '../models';
@ -43,7 +43,7 @@ export default class Moderation {
public async ban(user: User, moderator: Member, duration: number, reason?: string): Promise<ModerationInterface> {
if (reason && reason.length > 512) throw new Error('Ban reason cannot be longer than 512 characters');
await this.client.guilds.get(this.client.config.guildID).banMember(user.id, 7, reason);
const logID = uuid();
const logID = randomBytes(2).toString('hex');
const mod = new ModerationModel({
userID: user.id,
logID,
@ -85,7 +85,7 @@ export default class Moderation {
this.client.unbanGuildMember(this.client.config.guildID, userID, reason);
const user = await this.client.getRESTUser(userID);
if (!user) throw new Error('Cannot get user.');
const logID = uuid();
const logID = randomBytes(2).toString('hex');
const mod = new ModerationModel({
userID,
logID,
@ -110,4 +110,33 @@ export default class Moderation {
this.client.createMessage(this.logChannels.modlogs, { embed });
return mod.save();
}
public async kick(user: User, moderator: Member, reason?: string): Promise<ModerationInterface> {
if (reason && reason.length > 512) throw new Error('Kick reason cannot be longer than 512 characters');
await this.client.guilds.get(this.client.config.guildID).kickMember(user.id, reason);
const logID = randomBytes(2).toString('hex');
const mod = new ModerationModel({
userID: user.id,
logID,
moderatorID: moderator.id,
reason: reason || null,
type: 5,
date: new Date(),
});
const embed = new RichEmbed();
embed.setTitle(`Case ${logID} | Kick`);
embed.setColor('#e74c3c');
embed.setAuthor(user.username, user.avatarURL);
embed.setThumbnail(user.avatarURL);
embed.addField('User', `<@${user.id}>`, true);
embed.addField('Moderator', `<@${moderator.id}>`, true);
if (reason) {
embed.addField('Reason', reason, true);
}
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
this.client.createMessage(this.logChannels.modlogs, { embed });
return mod.save();
}
}

View File

@ -1,20 +1,8 @@
/* eslint-disable no-param-reassign */
export interface EmbedData {
title?: string
description?: string
url?: string
timestamp?: Date
color?: number
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 }
author?: { name: string, url?: string, proxy_icon_url?: string, icon_url?: string}
fields?: {name: string, value: string, inline?: boolean}[]
}
import { EmbedOptions } from 'eris';
export default class RichEmbed implements EmbedData {
export default class RichEmbed implements EmbedOptions {
title?: string
type?: string
@ -23,15 +11,15 @@ export default class RichEmbed implements EmbedData {
url?: string
timestamp?: Date
timestamp?: string | Date
color?: number
footer?: { text: string, icon_url?: string, proxy_icon_url?: string}
image?: { url: string, proxy_url?: string, height?: number, width?: number }
image?: { url?: string, proxy_url?: string, height?: number, width?: number }
thumbnail?: { 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 }
@ -41,7 +29,7 @@ export default class RichEmbed implements EmbedData {
fields?: {name: string, value: string, inline?: boolean}[]
constructor(data: EmbedData = {}) {
constructor(data: EmbedOptions = {}) {
/*
let types: {
title?: string, type?: string, description?: string, url?: string, timestamp?: Date, color?: number, fields?: {name: string, value: string, inline?: boolean}[]
@ -87,7 +75,7 @@ export default class RichEmbed implements EmbedData {
*/
setURL(url: string) {
if (typeof url !== 'string') throw new TypeError('RichEmbed URLs must be a string.');
if (!url.startsWith('http://') || !url.startsWith('https://')) url = `https://${url}`;
if (!url.startsWith('http://') && !url.startsWith('https://')) url = `https://${url}`;
this.url = encodeURI(url);
return this;
}
@ -124,8 +112,7 @@ export default class RichEmbed implements EmbedData {
* Sets the timestamp of this embed.
*/
setTimestamp(timestamp = new Date()) {
// eslint-disable-next-line no-restricted-globals
if (isNaN(timestamp.getTime())) throw new TypeError('Expecting ISO8601 (Date constructor)');
if (Number.isNaN(timestamp.getTime())) throw new TypeError('Expecting ISO8601 (Date constructor)');
this.timestamp = timestamp;
return this;
}

75
src/class/Route.ts Normal file
View File

@ -0,0 +1,75 @@
/* eslint-disable consistent-return */
import { Router, Request, Response } from 'express';
import { Server } from '.';
export default class Route {
public server: Server;
public conf: { path: string; deprecated?: boolean; maintenance?: boolean; };
public router: Router;
constructor(server: Server) {
this.server = server;
this.conf = { path: '' };
this.router = Router();
}
public bind() {}
public init() {
this.router.all('*', (req, res, next) => {
this.server.client.util.signale.log(`'${req.method}' request from '${req.ip}' to '${req.hostname}${req.path}'.`);
if (this.conf.maintenance === true) res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE });
else if (this.conf.deprecated === true) res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED });
else next();
});
}
public deprecated(): void {
this.router.all('*', (_req, res) => {
res.status(501).json({ code: this.constants.codes.DEPRECATED, message: this.constants.messages.DEPRECATED });
});
}
public maintenance(): void {
this.router.all('*', (_req, res) => {
res.status(503).json({ code: this.constants.codes.MAINTENANCE_OR_UNAVAILABLE, message: this.constants.messages.MAINTENANCE_OR_UNAVAILABLE });
});
}
public handleError(error: Error, res: Response) {
res.status(500).json({ code: this.constants.codes.SERVER_ERROR, message: this.constants.messages.SERVER_ERROR });
this.server.parent.client.util.handleError(error);
}
get constants() {
return {
codes: {
SUCCESS: 100,
UNAUTHORIZED: 101,
PERMISSION_DENIED: 104,
ENDPOINT_NOT_FOUND: 104,
NOT_FOUND: 1041,
ACCOUNT_NOT_FOUND: 1041,
CLIENT_ERROR: 1044,
SERVER_ERROR: 105,
DEPRECATED: 1051,
MAINTENANCE_OR_UNAVAILABLE: 1053,
},
messages: {
UNAUTHORIZED: ['CREDENTIALS_INVALID', 'The credentials you supplied are invalid.'],
BEARER_TOKEN_INVALID: ['BEARER_TOKEN_INVALID', 'The Bearer token you supplied is invalid.'],
PERMISSION_DENIED: ['PERMISSION_DENIED', 'You do not have valid credentials to access this resource.'],
NOT_FOUND: ['NOT_FOUND', 'The resource you requested cannot be located.'],
ENDPOINT_NOT_FOUND: ['ENDPOINT_NOT_FOUND', 'The endpoint you requested does not exist or cannot be located.'],
SERVER_ERROR: ['INTERNAL_ERROR', 'An internal error has occurred, Engineers have been notified.'],
DEPRECATED: ['ENDPOINT_OR_RESOURCE_DEPRECATED', 'The endpoint or resource you\'re trying to access has been deprecated.'],
MAINTENANCE_OR_UNAVAILABLE: ['SERVICE_UNAVAILABLE', 'The endpoint or resource you\'re trying to access is either in maintenance or is not available.'],
},
discord: {
SERVER_ID: '446067825673633794',
},
};
}
}

68
src/class/Server.ts Normal file
View File

@ -0,0 +1,68 @@
import express from 'express';
import helmet from 'helmet';
import { promises as fs } from 'fs';
import { Server as HTTPServer } from 'http';
import { Collection, ServerManagement, Route } from '.';
export default class Server {
public app: express.Application;
public routes: Collection<Route>;
public parent: ServerManagement;
public port: number;
private root: string;
constructor(parent: ServerManagement, port: number, routeRoot: string) {
this.parent = parent;
this.app = express();
this.routes = new Collection<Route>();
this.port = port;
this.root = routeRoot;
this.loadRoutes();
this.init();
}
get client() {
return this.parent.client;
}
public async loadRoutes() {
const routes = Object.values<typeof Route>(require(this.root));
for (const RouteFile of routes) {
const route = new RouteFile(this);
if (route.conf.deprecated) {
route.deprecated();
} else if (route.conf.maintenance) {
route.maintenance();
} else {
route.init();
route.bind();
}
this.parent.client.util.signale.success(`Successfully loaded route 'http://localhost:${this.port}/${route.conf.path}'.`);
this.routes.add(route.conf.path, route);
this.app.use(route.conf.path, route.router);
}
this.app.listen(this.port);
}
public init() {
this.app.set('trust proxy', 'loopback');
this.app.use(helmet({
hsts: false,
hidePoweredBy: false,
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
},
},
}));
}
public listen(port: number): HTTPServer {
return this.app.listen(port);
}
}

View File

@ -0,0 +1,22 @@
import { Client, Collection, Server } from '.';
import serverSetup from '../api';
export default class ServerManagement {
public client: Client;
public servers: Collection<Server>;
constructor(client: Client) {
this.client = client;
this.servers = new Collection<Server>();
this.loadServers();
}
public async loadServers() {
const apiRoot = Object.entries<(management: ServerManagement) => Server>(serverSetup);
for (const [api, server] of apiRoot) {
this.servers.add(api, server(this));
this.client.util.signale.success(`Successfully loaded server '${api}'.`);
}
}
}

View File

@ -37,8 +37,6 @@ export default class Util {
*/
public resolveCommand(query: string | string[]): Promise<{cmd: Command, args: string[] }> {
try {
// let resolvedCommand: Command;
// eslint-disable-next-line no-param-reassign
if (typeof query === 'string') query = query.split(' ');
const commands = this.client.commands.toArray();
const resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase()));
@ -70,10 +68,10 @@ export default class Util {
|| members.find((m) => m.username.toLowerCase().startsWith(query.toLowerCase()) || (m.nick && m.nick.toLowerCase().startsWith(query.toLowerCase())));
}
public async handleError(error: Error, message?: Message, command?: Command, disable?: boolean): Promise<void> {
public async handleError(error: Error, message?: Message, command?: Command, disable = true): Promise<void> {
try {
this.signale.error(error);
const info = { content: `\`\`\`js\n${error.stack}\n\`\`\``, embed: null };
const info = { content: `\`\`\`js\n${error.stack || error}\n\`\`\``, embed: null };
if (message) {
const embed = new RichEmbed();
embed.setColor('FF0000');
@ -91,9 +89,8 @@ export default class Util {
}
await this.client.createMessage('595788220764127272', info);
const msg = message ? message.content.slice(this.client.config.prefix.length).trim().split(/ +/g) : [];
// eslint-disable-next-line no-param-reassign
if (command && disable) this.resolveCommand(msg).then((c) => { c.cmd.enabled = false; });
if (message) message.channel.createMessage(`***${this.emojis.ERROR} An unexpected error has occured - please contact a Faculty Marshal.${command && disable ? ' This command has been disabled.' : ''}***`);
if (message) message.channel.createMessage(`***${this.emojis.ERROR} An unexpected error has occured - please contact a Staff member.${command && disable ? ' This command has been disabled.' : ''}***`);
} catch (err) {
this.signale.error(err);
}
@ -101,7 +98,6 @@ export default class Util {
public splitString(string: string, length: number): string[] {
if (!string) return [];
// eslint-disable-next-line no-param-reassign
if (Array.isArray(string)) string = string.join('\n');
if (string.length <= length) return [string];
const arrayString: string[] = [];
@ -111,19 +107,24 @@ export default class Util {
pos = string.length > length ? string.lastIndexOf('\n', length) : string.length;
if (pos > length) pos = length;
str = string.substr(0, pos);
// eslint-disable-next-line no-param-reassign
string = string.substr(pos);
arrayString.push(str);
}
return arrayString;
}
public splitFields(fields: { name: string, value: string, inline?: boolean }[]): { name: string, value: string, inline?: boolean }[][] {
let index = 0;
const array: {name: string, value: string, inline?: boolean}[][] = [[]];
while (fields.length) {
if (array[index].length >= 25) { index += 1; array[index] = []; }
array[index].push(fields[0]); fields.shift();
}
return array;
}
public decimalToHex(int: number): string {
const red = (int && 0x0000ff) << 16;
const green = int && 0x00ff00;
const blue = (int && 0xff0000) >>> 16;
const number = red | green | blue;
const asHex = number.toString(16);
return '#000000'.substring(0, 7 - asHex.length) + asHex;
const hex = int.toString(16);
return '#000000'.substring(0, 7 - hex.length) + hex;
}
}

View File

@ -1,6 +1,10 @@
export { default as Client } from './Client';
export { default as Collection } from './Collection';
export { default as Command } from './Command';
export { default as Event } from './Event';
export { default as Moderation } from './Moderation';
export { default as RichEmbed } from './RichEmbed';
export { default as Route } from './Route';
export { default as Server } from './Server';
export { default as ServerManagement } from './ServerManagement';
export { default as Util } from './Util';

60
src/commands/additem.ts Normal file
View File

@ -0,0 +1,60 @@
import { Message } from 'eris';
import { Client, Command, RichEmbed } from '../class';
export default class AddItem extends Command {
constructor(client: Client) {
super(client);
this.name = 'additem';
this.description = 'Adds information to your whois embed.';
this.usage = 'additem [code]';
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (args.length < 1) {
const embed = new RichEmbed();
embed.setTitle('Whois Data Codes');
embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts');
embed.addField('Operating Systems', '**Arch:** os-arch\n**Debian:** os-deb\n**CentOS:** os-cent\n**Fedora:** os-fedora\n**macOS:** os-mdarwin\n**Manjaro:** os-manjaro\n**RedHat:** os-redhat\n**Ubuntu:** os-ubuntu\n**Windows:** os-win');
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
return message.channel.createMessage({ embed });
}
if (args[0].split('-')[0] === 'os' && ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'].includes(args[0].split('-')[1])) {
const account = await this.client.db.Member.findOne({ userID: message.member.id });
if (!account) {
const newAccount = new this.client.db.Member({
userID: message.member.id,
additional: {
operatingSystems: [args[0].split('-')[1]],
},
});
await newAccount.save();
} else {
await account.updateOne({ $addToSet: { 'additional.operatingSystems': args[0].split('-')[1] } });
}
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added OS code ${args[0]} to profile.***`);
}
if (args[0].split('-')[0] === 'lang' && ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) {
const account = await this.client.db.Member.findOne({ userID: message.member.id });
if (!account) {
const newAccount = new this.client.db.Member({
userID: message.member.id,
additional: {
langs: [args[0].split('-')[1]],
},
});
await newAccount.save();
} else {
await account.updateOne({ $addToSet: { 'additional.langs': args[0].split('-')[1] } });
}
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Added language code ${args[0]} to profile.***`);
}
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid data code.***`);
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

View File

@ -0,0 +1,37 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
export default class AddRedirect extends Command {
constructor(client: Client) {
super(client);
this.name = 'addredirect';
this.description = 'Adds a redirect link for \'loc.sh\'';
this.usage = 'addredirect <redirect to url> <key>';
this.aliases = ['ar'];
this.permissions = 6;
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 check = await this.client.db.Redirect.findOne({ key: args[1].toLowerCase() });
if (check) return this.error(message.channel, `Redirect key ${args[1].toLowerCase()} already exists. Linked to: ${check.to}`);
try {
const test = new URL(args[0]);
if (test.protocol !== 'https:') return this.error(message.channel, 'Protocol must be HTTPS.');
} catch {
return this.error(message.channel, 'This doesn\'t appear to be a valid URL.');
}
if ((/^[a-zA-Z0-9]+$/gi.test(args[1].toLowerCase().replace('-', '').trim()) === false) || args[1].toLowerCase().length > 15) return this.error(message.channel, 'Invalid key. The key must be alphanumeric and less than 16 characters.');
const redirect = new this.client.db.Redirect({
key: args[1].toLowerCase(),
to: args[0],
});
await redirect.save();
return this.success(message.channel, `Redirect https://loc.sh/${args[1].toLowerCase()} -> ${args[0]} is now active.`);
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

View File

@ -8,13 +8,14 @@ export default class Ban extends Command {
this.name = 'ban';
this.description = 'Bans a member from the guild.';
this.usage = 'ban <member> [time] [reason]';
this.permissions = 2;
this.permissions = 3;
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.client.guilds.get(this.client.config.guildID));
let user: User;
if (!member) {
@ -23,6 +24,8 @@ export default class Ban extends Command {
} catch {
return this.error(message.channel, 'Cannot find user.');
}
} else {
user = member.user;
}
try {
await this.client.guilds.get(this.client.config.guildID).getBan(args[0]);

46
src/commands/delitem.ts Normal file
View File

@ -0,0 +1,46 @@
import { Message } from 'eris';
import { Client, Command, RichEmbed } from '../class';
export default class DelItem extends Command {
constructor(client: Client) {
super(client);
this.name = 'delitem';
this.description = 'Removes information to your whois embed.';
this.usage = 'delitem [code]';
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (args.length < 1) {
const embed = new RichEmbed();
embed.setTitle('Whois Data Codes');
embed.addField('Languages', '**Assembly Language:** lang-asm\n**C/C++:** lang-cfam\n**C#:** lang-csharp\n**Go:** lang-go\n**Java:** lang-java\n**JavaScript:** lang-js\n**Kotlin:** lang-kt\n**Python:** lang-py\n**Ruby:** lang-rb\n**Rust:** lang-rs\n**Swift:** lang-swift\n**TypeScript:** lang-ts');
embed.addField('Operating Systems', '**Arch:** os-arch\n**Debian:** os-deb\n**CentOS:** os-cent\n**Fedora:** os-fedora\n**macOS:** os-mdarwin\n**Manjaro:** os-manjaro\n**RedHat:** os-redhat\n**Ubuntu:** os-ubuntu\n**Windows:** os-win');
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
return message.channel.createMessage({ embed });
}
if (args[0].split('-')[0] === 'os' && ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'].includes(args[0].split('-')[1])) {
const account = await this.client.db.Member.findOne({ userID: message.member.id });
if (!account || !account?.additional.operatingSystems || account?.additional.operatingSystems.length < 1) {
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any operating systems to remove.***`);
}
await account.updateOne({ $pull: { 'additional.operatingSystems': args[0].split('-')[1] } });
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed OS code ${args[0]} from profile.***`);
}
if (args[0].split('-')[0] === 'lang' && ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'].includes(args[0].split('-')[1])) {
const account = await this.client.db.Member.findOne({ userID: message.member.id });
if (!account || !account?.additional.langs || account?.additional.langs.length < 1) {
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} You don't have any languages to remove.***`);
}
await account.updateOne({ $pull: { 'additional.langs': args[0].split('-')[1] } });
return message.channel.createMessage(`***${this.client.util.emojis.SUCCESS} Removed language code ${args[0]} from profile.***`);
}
return message.channel.createMessage(`***${this.client.util.emojis.ERROR} Invalid data code.***`);
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

View File

@ -0,0 +1,26 @@
import { Message } from 'eris';
import { Client, Command } from '../class';
export default class DelRedirect extends Command {
constructor(client: Client) {
super(client);
this.name = 'delredirect';
this.description = 'Delete a redirect link for \'loc.sh\'';
this.usage = 'delredirect <key>';
this.aliases = ['dr'];
this.permissions = 6;
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 check = await this.client.db.Redirect.findOne({ key: args[0].toLowerCase() });
if (!check) return this.error(message.channel, `Redirect key ${args[0].toLowerCase()} doesn't exist.`);
await this.client.db.Redirect.deleteOne({ key: args[0].toLowerCase() });
return this.success(message.channel, `Deleted redirect https://loc.sh/${args[0].toLowerCase()}.`);
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

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

@ -0,0 +1,29 @@
import { Message, EmbedOptions } from 'eris';
import axios, { AxiosResponse } from 'axios';
import { Client, Command, RichEmbed } from '../class';
export default class DJS extends Command {
constructor(client: Client) {
super(client);
this.name = 'djs';
this.description = 'Get information about Discord.js.';
this.usage = 'djs <query>';
this.permissions = 0;
this.guildOnly = false;
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 { data }: AxiosResponse<EmbedOptions> = await axios.get(`https://djsdocs.sorta.moe/v2/embed?src=master&q=${args[0]}`);
if (!data) return this.error(message.channel, 'Could not find information. Try something else.');
const embed = new RichEmbed(data);
return message.channel.createMessage({ embed });
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

View File

@ -9,7 +9,7 @@ export default class Eval extends Command {
this.name = 'eval';
this.description = 'Evaluates native JS code';
this.aliases = ['e'];
this.permissions = 4;
this.permissions = 7;
this.enabled = true;
this.guildOnly = false;
}

View File

@ -3,11 +3,11 @@ import { Activity, Member, Message } from 'eris';
import { Client, Command, RichEmbed } from '../class';
enum ActivityType {
PLAYING = 0,
STREAMING = 1,
LISTENING = 2,
WATCHING = 3,
CUSTOM_STATUS = 4
PLAYING,
STREAMING,
LISTENING,
WATCHING,
CUSTOM_STATUS
}
export default class Game extends Command {
@ -47,13 +47,9 @@ export default class Game extends Command {
embed.setColor('#1ed760');
embed.addField('Song', mainStatus.details, true);
embed.addField('Artist', mainStatus.state, true);
// @ts-ignore
embed.addField('Album', mainStatus.assets.large_text);
// @ts-ignore
embed.addField('Start', `${new Date(mainStatus.timestamps.start).toLocaleTimeString('en-us')} ET`, true);
// @ts-ignore
embed.addField('End', `${new Date(mainStatus.timestamps.end).toLocaleTimeString('en-us')} ET`, true);
// @ts-ignore
embed.setThumbnail(`https://i.scdn.co/image/${mainStatus.assets.large_image.split(':')[1]}`);
embed.setFooter(`Listening to Spotify | ${this.client.user.username}`, 'https://media.discordapp.net/attachments/358674161566220288/496894273304920064/2000px-Spotify_logo_without_text.png');
embed.setTimestamp();

115
src/commands/help.ts Normal file
View File

@ -0,0 +1,115 @@
import { createPaginationEmbed } from 'eris-pagination';
import { Message } from 'eris';
import { Client, Command, RichEmbed } from '../class';
export default class Help extends Command {
constructor(client: Client) {
super(client);
this.name = 'help';
this.description = 'Information about commands.';
this.usage = 'help [command]';
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (args.length > 0) {
const command = this.client.commands.get(args[0].toLowerCase());
if (!command) return this.error(message.channel, 'The command you provided doesn\'t exist.');
const embed = new RichEmbed();
embed.setTitle(`${this.client.config.prefix}${command.name}`);
embed.addField('Description', command.description ?? '-');
embed.addField('Usage', command.usage ?? '-');
if (command.aliases.length > 0) {
embed.addField('Aliases', command.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', '));
}
let description: string = '';
if (!command.enabled) {
description += 'This command is disabled.';
}
if (command.guildOnly) {
description += 'This command can only be ran in a guild.';
}
embed.setDescription(description);
switch (command.permissions) {
case 0:
break;
case 1:
embed.addField('Permissions', 'Associates+');
break;
case 2:
embed.addField('Permissions', 'Core Team+');
break;
case 3:
embed.addField('Permissions', 'Moderators, Supervisor, & Board of Directors');
break;
case 4:
embed.addField('Permissions', 'Technicians, Supervisor, & Board of Directors');
break;
case 5:
embed.addField('Permissions', 'Moderators, Technicians, Supervisor, & Board of Directors');
break;
case 6:
embed.addField('Permissions', 'Supervisor+');
break;
case 7:
embed.addField('Permissions', 'Board of Directors');
break;
default:
break;
}
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
return message.channel.createMessage({ embed });
}
const cmdList: Command[] = [];
this.client.commands.forEach((c) => cmdList.push(c));
const commands = this.client.commands.map((c) => {
const aliases = c.aliases.map((alias) => `${this.client.config.prefix}${alias}`).join(', ');
let perm: string;
switch (c.permissions) {
case 0:
break;
case 1:
perm = 'Associates+';
break;
case 2:
perm = 'Core Team+';
break;
case 3:
perm = 'Moderators, Supervisor, & Board of Directors';
break;
case 4:
perm = 'Technicians, Supervisor, & Board of Directors';
break;
case 5:
perm = 'Moderators, Technicians, Supervisor, & Board of Directors';
break;
case 6:
perm = 'Supervisor+';
break;
case 7:
perm = 'Board of Directors';
break;
default:
break;
}
return { name: `${this.client.config.prefix}${c.name}`, value: `**Description:** ${c.description}\n**Aliases:** ${aliases}\n**Usage:** ${c.usage}\n**Permissions:** ${perm ?? ''}`, inline: false };
});
const splitCommands = this.client.util.splitFields(commands);
const cmdPages: RichEmbed[] = [];
splitCommands.forEach((splitCmd) => {
const embed = new RichEmbed();
embed.setTimestamp(); embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setDescription(`Command list for ${this.client.user.username}`);
splitCmd.forEach((c) => embed.addField(c.name, c.value, c.inline));
return cmdPages.push(embed);
});
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
return createPaginationEmbed(message, cmdPages);
} catch (err) {
return this.client.util.handleError(err, message, this, false);
}
}
}

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

@ -0,0 +1,34 @@
import { Message } from 'eris';
import { totalmem } from 'os';
import { Client, Command, RichEmbed } from '../class';
import { version as erisVersion } from '../../node_modules/eris/package.json';
import { version as mongooseVersion } from '../../node_modules/mongoose/package.json';
export default class Info extends Command {
constructor(client: Client) {
super(client);
this.name = 'info';
this.description = 'System information.';
this.usage = 'info';
this.permissions = 0;
this.enabled = true;
}
public async run(message: Message) {
try {
const embed = new RichEmbed();
embed.setTitle('Information');
embed.setThumbnail(this.client.user.avatarURL);
embed.addField('Language(s)', '<:TypeScript:703451285789343774> TypeScript', true);
embed.addField('Discord Library', `Eris (${erisVersion})`, true);
embed.addField('Database Library', `MongoDB w/ Mongoose ODM (${mongooseVersion})`, true);
embed.addField('Repository', 'https://gitlab.libraryofcode.org/engineering/communityrelations | Licensed under GNU Affero General Public License V3', true);
embed.addField('Memory Usage', `${Math.round(process.memoryUsage().rss / 1024 / 1024)} MB / ${Math.round(totalmem() / 1024 / 1024 / 1024)} GB`, true);
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
message.channel.createMessage({ embed });
} catch (err) {
this.client.util.handleError(err, message, this);
}
}
}

38
src/commands/kick.ts Normal file
View File

@ -0,0 +1,38 @@
import { Message, User } from 'eris';
import { Client, Command } from '../class';
export default class Kick extends Command {
constructor(client: Client) {
super(client);
this.name = 'kick';
this.description = 'Kicks a member from the guild.';
this.usage = 'kick <member> [reason]';
this.permissions = 3;
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.client.guilds.get(this.client.config.guildID));
let user: User;
if (!member) {
try {
user = await this.client.getRESTUser(args[0]);
} catch {
return this.error(message.channel, 'Cannot find user.');
}
}
if (member && !this.client.util.moderation.checkPermissions(member, message.member)) return this.error(message.channel, 'Permission Denied.');
message.delete();
const reason: string = args[1];
if (reason.length > 512) return this.error(message.channel, 'Kick reasons cannot be longer than 512 characters.');
await this.client.util.moderation.kick(user, message.member, reason);
return this.success(message.channel, `${user.username}#${user.id} has been kicked.`);
} catch (err) {
return this.client.util.handleError(err, message, this, false);
}
}
}

View File

@ -0,0 +1,52 @@
import { Message } from 'eris';
import { createPaginationEmbed } from 'eris-pagination';
import { Client, Command, RichEmbed } from '../class';
export default class DelRedirect extends Command {
constructor(client: Client) {
super(client);
this.name = 'listredirects';
this.description = 'Delete a redirect link for \'loc.sh\'';
this.usage = 'listredirects [key || redirect to]';
this.aliases = ['getredirect', 'lsredirects', 'listlinks', 'lsr', 'gr'];
this.permissions = 6;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (args[0]) {
const redirects = await this.client.db.Redirect.find({ $or: [{ key: args[0].toLowerCase() }, { to: args[0].toLowerCase() }] });
if (redirects.length <= 0) return this.error(message.channel, 'Could not find an entry matching that query.');
const embed = new RichEmbed();
embed.setTitle('Redirect Information');
for (const redirect of redirects) {
embed.addField(redirect.key, redirect.to);
}
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setTimestamp();
return message.channel.createMessage({ embed });
}
const redirects = await this.client.db.Redirect.find();
if (!redirects) return this.error(message.channel, 'No redirect links found.');
const redirectArray: [{ name: string, value: string }?] = [];
for (const redirect of redirects) {
redirectArray.push({ name: redirect.key, value: redirect.to });
}
const splitRedirects = this.client.util.splitFields(redirectArray);
const cmdPages: RichEmbed[] = [];
splitRedirects.forEach((split) => {
const embed = new RichEmbed();
embed.setTitle('Redirect Information');
embed.setTimestamp();
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
split.forEach((c) => embed.addField(c.name, c.value));
return cmdPages.push(embed);
});
if (cmdPages.length === 1) return message.channel.createMessage({ embed: cmdPages[0] });
return createPaginationEmbed(message, cmdPages);
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

64
src/commands/npm.ts Normal file
View File

@ -0,0 +1,64 @@
import { Message } from 'eris';
import axios from 'axios';
import { Client, Command, RichEmbed } from '../class';
export default class NPM extends Command {
constructor(client: Client) {
super(client);
this.name = 'npm';
this.description = 'Get information about npm modules.';
this.usage = 'npm <module name>';
this.permissions = 0;
this.guildOnly = false;
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 res = await axios.get(`https://registry.npmjs.com/${args[0]}`, { validateStatus: (_) => true });
if (res.status === 404) return this.error(message.channel, 'Could not find the library, try something else.');
const { data } = res;
const bugs: string = data.bugs?.url || '';
const description: string = data.description || 'None';
const version: string = data['dist-tags']?.latest || 'Unknown';
const homepage: string = data.homepage || '';
let license: string = 'None';
if (typeof data.license === 'object') {
license = data.license.type;
} else if (typeof data.license === 'string') {
license = data.license;
}
let dependencies: string = 'None';
if (version !== 'Unknown' && data.versions[version].dependencies !== undefined && Object.keys(data.versions[version].dependencies).length > 0) {
dependencies = Object.keys(data.versions[version].dependencies).join(', ');
}
const name: string = data.name || 'None';
const repository: string = bugs.replace('/issues', '') || '';
const creation: string = data.time.created ? new Date(data.time.created).toLocaleString('en') : 'None';
const modification: string = data.time.modified ? new Date(data.time.modified).toLocaleString('en') : 'None';
const embed = new RichEmbed();
embed.setColor(0xCC3534);
embed.setTimestamp();
embed.setFooter(this.client.user.username, this.client.user.avatarURL);
embed.setAuthor('NPM', 'https://i.imgur.com/ErKf5Y0.png', 'https://www.npmjs.com/');
embed.setDescription(`[NPM](https://www.npmjs.com/package/${args[0]}) | [Homepage](${homepage}) | [Repository](${repository}) | [Bugs](${bugs})`);
embed.addField('Name', name, true);
embed.addField('Latest version', version, true);
embed.addField('License', license, true);
embed.addField('Description', description, false);
embed.addField('Dependencies', dependencies, false);
embed.addField('Creation Date', creation, true);
embed.addField('Modification Date', modification, true);
return message.channel.createMessage({ embed });
} catch (err) {
return this.client.util.handleError(err, message, this);
}
}
}

View File

@ -15,7 +15,7 @@ export default class Roleinfo extends Command {
public async run(message: Message, args: string[]) {
try {
if (!args[0]) return this.error(message.channel, 'You need to specifiy a role ID or a role name.');
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
let role: Role = this.client.guilds.get(this.client.config.guildID).roles.find((r: Role) => r.id === args[0]);
if (!role) { // if it's a role name

View File

@ -7,13 +7,14 @@ export default class Unban extends Command {
this.name = 'unban';
this.description = 'Unbans a member from the guild.';
this.usage = 'unban <user id> [reason]';
this.permissions = 2;
this.permissions = 3;
this.guildOnly = true;
this.enabled = true;
}
public async run(message: Message, args: string[]) {
try {
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
let user: User;
try {
user = await this.client.getRESTUser(args[0]);

View File

@ -42,8 +42,8 @@ export default class Whois extends Command {
const ackResolve = this.resolveStaffInformation(member.id);
let description = '';
if (ackResolve) {
if (ackResolve?.title && ackResolve?.dept) {
description += `${emotes.titleAndDepartment} __**${ackResolve.title}**__, ${ackResolve.dept}\n\n`;
if (ackResolve?.title) {
description += `${emotes.titleAndDepartment} __**${ackResolve.title}**__\n\n`;
}
if (ackResolve?.emailAddress) {
description += `${emotes.email} ${ackResolve.emailAddress}\n`;
@ -68,6 +68,15 @@ export default class Whois extends Command {
}
}
embed.addField('Status', `${member.status[0].toUpperCase()}${member.status.slice(1)}`, true);
if (member.bot) {
embed.addField('Platform', 'API/WebSocket', true);
} else if (member.clientStatus?.web === 'online' || member.clientStatus?.web === 'idle' || member.clientStatus?.web === 'dnd') {
embed.addField('Platform', 'Web', true);
} else if (member.clientStatus?.desktop === 'online' || member.clientStatus?.desktop === 'idle' || member.clientStatus?.desktop === 'dnd') {
embed.addField('Platform', 'Desktop', true);
} else if (member.clientStatus?.mobile === 'online' || member.clientStatus?.mobile === 'idle' || member.clientStatus?.mobile === 'dnd') {
embed.addField('Platform', 'Mobile', true);
}
embed.addField('Joined At', `${moment(new Date(member.joinedAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true);
embed.addField('Created At', `${moment(new Date(member.user.createdAt)).format('dddd, MMMM Do YYYY, h:mm:ss A')} ET`, true);
if (member.roles.length > 0) {
@ -78,14 +87,98 @@ export default class Whois extends Command {
const bit = member.permission.allow;
if (this.client.guilds.get(this.client.config.guildID).ownerID === member.id) serverAcknowledgements.push('Server Owner');
if ((bit | 8) === bit) { permissions.push('Administrator'); serverAcknowledgements.push('Server Admin'); }
if ((bit | 20) === bit) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); }
if ((bit | 10) === bit) permissions.push('Manage Channels');
if ((bit | 32) === bit) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); }
if ((bit | 16) === bit) permissions.push('Manage Channels');
if ((bit | 268435456) === bit) permissions.push('Manage Roles');
if ((bit | 8192) === bit) { permissions.push('Manage Messages'); serverAcknowledgements.push('Server Moderator'); }
if ((bit | 134217728) === bit) permissions.push('Manage Nicknames');
if ((bit | 1073741824) === bit) permissions.push('Manage Emojis');
if ((bit | 4) === bit) permissions.push('Ban Members');
if ((bit | 2) === bit) permissions.push('Kick Members');
const account = await this.client.db.Member.findOne({ userID: member.id });
if (account?.additional?.langs.length > 0) {
const langs: string[] = [];
for (const lang of account.additional.langs.sort((a, b) => a.localeCompare(b))) {
switch (lang) {
case 'asm':
langs.push('<:AssemblyLanguage:703448714248716442> Assembly Language');
break;
case 'cfam':
langs.push('<:clang:553684262193332278> C/C++');
break;
case 'csharp':
langs.push('<:csharp:553684277280112660> C#');
break;
case 'go':
langs.push('<:Go:703449475405971466> Go');
break;
case 'java':
langs.push('<:Java:703449725181100135> Java');
break;
case 'js':
langs.push('<:JavaScriptECMA:703449987916496946> JavaScript');
break;
case 'kt':
langs.push('<:Kotlin:703450201838321684> Kotlin');
break;
case 'py':
langs.push('<:python:553682965482176513> Python');
break;
case 'rb':
langs.push('<:ruby:604812470451699712> Ruby');
break;
case 'rs':
langs.push('<:Rust:703450901960196206> Rust');
break;
case 'swift':
langs.push('<:Swift:703451096093294672> Swift');
break;
case 'ts':
langs.push('<:TypeScript:703451285789343774> TypeScript');
break;
default:
break;
}
}
embed.addField('Known Languages', langs.join(', '));
}
if (account?.additional?.operatingSystems.length > 0) {
const operatingSystems: string[] = [];
for (const os of account.additional.operatingSystems.sort((a, b) => a.localeCompare(b))) {
switch (os) {
case 'arch':
operatingSystems.push('<:arch:707694976523304960> Arch');
break;
case 'deb':
operatingSystems.push('<:debian:707695042617147589> Debian');
break;
case 'cent':
operatingSystems.push('<:centos:707702165816213525> CentOS');
break;
case 'fedora':
operatingSystems.push('<:fedora:707695073151680543> Fedora');
break;
case 'manjaro':
operatingSystems.push('<:manjaro:707701473680556062> Manjaro');
break;
case 'mdarwin':
operatingSystems.push('<:mac:707695427754917919> macOS');
break;
case 'redhat':
operatingSystems.push('<:redhat:707695102159749271> RedHat Enterprise Linux');
break;
case 'ubuntu':
operatingSystems.push('<:ubuntu:707695136888586300> Ubuntu');
break;
case 'win':
operatingSystems.push('<:windows10:707695160259248208> Windows');
break;
default:
break;
}
}
embed.addField('Used Operating Systems', operatingSystems.join(', '));
}
if (permissions.length > 0) {
embed.addField('Permissions', permissions.join(', '));
}

View File

@ -2,19 +2,17 @@
{
"name": "Matthew",
"id": "278620217221971968",
"title": "Marshal General of Engineering",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "matthew@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/matthew",
"github": "https://github.com/matthew119427",
"bio": "If you removed all the arteries, veins, & capillaries from a persons body, and tied them end-to-end…the person will die. - Dr. Niel deGrasse Tyson",
"bio": "so baby come light me up, and maybe ill let you on it. a little bit dangerous, but baby thats how i want it. a little less conversation and a little more touch my body. cuz im so into you... ~ Ariana Grande, Into You - Dangerous Woman",
"acknowledgements": ["Maintainer & Lead Developer"]
},
{
"name": "Joe",
"id": "556969659531001858",
"title": "Marshal General of Community Relations",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "joe@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/Joe",
"github": "https://github.com/sirdroolio",
@ -24,8 +22,7 @@
{
"name": "Bsian",
"id": "253600545972027394",
"title": "Assistant Marshal General of Engineering",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "bsian@staff.libraryofcode.org",
"bio": "I also like trains",
"acknowledgements": ["Maintainer & Assistant Lead Developer"]
@ -33,23 +30,20 @@
{
"name": "NightRaven",
"id": "239261547959025665",
"title": "Assistant Marshal General of Community Relations",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "nightraven@staff.libraryofcode.org",
"bio": "I like trains"
},
{
"name": "Midori",
"id": "109122112643440640",
"title": "Assistant Marshal General of Community Relations",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "midori@staff.libraryofcode.org"
},
{
"name": "Unknown",
"id": "143414786913206272",
"title": "Staff Sergeant",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "unknown@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/unknown",
"bio": "im not a proffesional developer or anything, i just enjoy it as a hobby."
@ -57,53 +51,24 @@
{
"name": "TheSkele27",
"id": "213632190557192192",
"title": "Community Advisor",
"dept": "Faculty Marshals",
"title": "Board of Directors",
"emailAddress": "theskele27@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/TheSkele27",
"github": "https://github.com/TheSkele27",
"bio": "Is water wet?"
},
{
"name": "Aikaterna",
"id": "154497072148643840",
"title": "Sheriff",
"dept": "Staff Team"
},
{
"name": "Boss",
"id": "344954369285947392",
"title": "Sheriff",
"dept": "Staff Team",
"emailAddress": "boss@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/Boss",
"bio": "I cant find a bio for user \"boss\""
},
{
"name": "Besero",
"id": "283318906024886272",
"title": "Associate",
"dept": "Staff Team",
"emailAddress": "besero@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/besero_gg",
"github": "https://github.com/kledi-harding",
"bio": "Just looking for some fun. I like going on long walks in the park. No guys",
"acknowledgements": ["Graphic Designer"]
},
{
"name": "CoalSephos",
"id": "155698776512790528",
"title": "Associate",
"dept": "Staff Team",
"gitlab": "https://gitlab.libraryofcode.org/CoalSephos",
"github": "https://github.com/CoalSephos",
"bio": "[Tastes like chicken?]"
"name": "Catbirby",
"id": "131953641371205632",
"title": "Supervisor",
"emailAddress": "catbirby@staff.libraryofcode.org",
"github": "https://github.com/catbirby",
"bio": "Computer Tech/Networking Nerd/Sysadmin/Graphic Designer/Audiophile. I don't do much coding but know my way around most languages."
},
{
"name": "D3XTER",
"id": "468009964263178262",
"title": "Associate",
"dept": "Staff Team",
"title": "Core Team",
"emailAddress": "dexter@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/D3XTER",
"bio": "Hi I'm D3XTER how are ya?"
@ -111,8 +76,7 @@
{
"name": "DedShotTM",
"id": "402154763363418142",
"title": "Associate",
"dept": "Staff Team",
"title": "Technician & Moderator",
"emailAddress": "dedshot@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/DedShotTM",
"github": "https://github.com/DedShotTM",
@ -122,8 +86,7 @@
{
"name": "EdgyBoi2414",
"id": "397432516010835970",
"title": "Webmaster",
"dept": "Staff Team",
"title": "Core Team",
"emailAddress": "edgyboi2414@gmail.com",
"gitlab": "https://gitlab.libraryofcode.org/EdgyBoi2414",
"github": "https://github.com/EdgyBoi2414",
@ -132,18 +95,23 @@
{
"name": "Hector",
"id": "377781496292835339",
"title": "Associate",
"dept": "Staff Team",
"title": "Core Team",
"emailAddress": "hector@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/Hector",
"github": "https://github.com/Hector6704",
"bio": "Hi there, I'm the developer of Delta, the Discord bot. I'm a free-time French JavaScript developer. I hope you'll enjoy LOC!"
},
{
"name": "Realitus",
"id": "156450671338586112",
"title": "Associate",
"github": "https://github.com/Realitus",
"bio": "A hobbyist software developer with some rather strange ideas, and even stranger implementations."
},
{
"name": "KhaaZ",
"id": "179908288337412096",
"title": "Associate",
"dept": "Staff Team",
"title": "Core Team",
"gitlab": "https://gitlab.libraryofcode.org/KhaaZ",
"github": "https://github.com/Khaazz",
"bio": "I baguette for a living and eat code for breakfast."
@ -151,9 +119,20 @@
{
"name": "Ryan",
"id": "186679073764802560",
"title": "Associate",
"emailAddress": "wbdvryan@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/plainRyan",
"bio": "Experiment, learn, share, repeat.",
"acknowledgements": ["Contributor"]
},
{
"name": "Zloth",
"id": "382368885267234816",
"title": "Associate",
"emailAddress": "zloth@staff.libraryofcode.org",
"github": "https://github.com/gavintjhxx",
"bio": "Wake up. Eat. Code. Sleep. Loop()"
},
{
"name": "Null",
"id": "323673862971588609",

View File

@ -1,12 +1,13 @@
/* eslint-disable no-useless-return */
import { Message, TextChannel, NewsChannel } from 'eris';
import { Client } from '../class';
import { Client, Event } from '../class';
export default class {
export default class CommandHandler extends Event {
public client: Client;
constructor(client: Client) {
this.client = client;
super(client);
this.event = 'messageCreate';
}
public async run(message: Message) {
@ -19,7 +20,10 @@ export default class {
if (resolved.cmd.guildOnly && !(message.channel instanceof TextChannel || message.channel instanceof NewsChannel)) return;
if (!resolved.cmd.enabled) { message.channel.createMessage(`***${this.client.util.emojis.ERROR} This command has been disabled***`); return; }
if (!resolved.cmd.checkPermissions(message.member)) return;
this.client.util.signale.info(`User '${message.author.username}#${message.author.discriminator}' ran command '${resolved.cmd.name}' in '${message.channel.id}'.`);
if ((message.channel.type === 0) && !message.channel.guild.members.get(message.author.id)) {
message.channel.guild.members.add(await message.channel.guild.getRESTMember(message.author.id));
}
this.client.util.signale.log(`User '${message.author.username}#${message.author.discriminator}' ran command '${resolved.cmd.name}' in '${message.channel.id}'.`);
await resolved.cmd.run(message, resolved.args);
} catch (err) {
this.client.util.handleError(err, message);

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

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

View File

@ -1,10 +1,11 @@
import { Client } from '../class';
import { Client, Event } from '../class';
export default class {
export default class Ready extends Event {
public client: Client;
constructor(client: Client) {
this.client = client;
super(client);
this.event = 'ready';
}
public async run() {

View File

@ -2,10 +2,10 @@ import { Client } from '../class';
let interval: NodeJS.Timeout;
export default function checkLock(client: Client) {
export default function checkLock(client: Client): NodeJS.Timeout {
interval = setInterval(async () => {
try {
const moderations = await client.db.moderation.find();
const moderations = await client.db.Moderation.find();
moderations.forEach(async (moderation) => {
if (!moderation.expiration) return;
if (moderation.expiration.processed) return;

View File

@ -0,0 +1,11 @@
import { Client } from '../class';
let interval: NodeJS.Timeout;
export default async function fetchMembers(client: Client): Promise<NodeJS.Timeout> {
await client.guilds.get(client.config.guildID).fetchAllMembers();
interval = setInterval(async () => {
await client.guilds.get(client.config.guildID).fetchAllMembers();
}, 1800000);
return interval;
}

View File

@ -1,3 +1,6 @@
/* DM Ramirez with the code below to claim 500 free Rubies!
d2c3d8e14b
*/
import { parse } from 'yaml';
import { promises as fs } from 'fs';
import { Client } from './class';

19
src/models/Member.ts Normal file
View File

@ -0,0 +1,19 @@
import { Document, Schema, model } from 'mongoose';
export interface MemberInterface extends Document {
userID: string
additional: {
langs: ['js', 'py', 'rb', 'ts', 'rs', 'go', 'cfam', 'csharp', 'swift', 'java', 'kt', 'asm'],
operatingSystems: ['arch', 'deb', 'cent', 'fedora', 'manjaro', 'mdarwin', 'redhat', 'ubuntu', 'win'],
},
}
const Member: Schema = new Schema({
userID: String,
additional: {
langs: Array,
operatingSystems: Array,
},
});
export default model<MemberInterface>('Member', Member);

18
src/models/Redirect.ts Normal file
View File

@ -0,0 +1,18 @@
import { Document, Schema, model } from 'mongoose';
export interface RedirectInterface extends Document {
key: string,
to: string,
}
export interface RedirectRaw {
key: string,
to: string,
}
const Redirect: Schema = new Schema({
key: String,
to: String,
});
export default model<RedirectInterface>('Redirect', Redirect);

View File

@ -1 +1,3 @@
export { default as Member, MemberInterface } from './Member';
export { default as Moderation, ModerationInterface } from './Moderation';
export { default as Redirect, RedirectInterface, RedirectRaw } from './Redirect';

2223
yarn.lock Normal file

File diff suppressed because it is too large Load Diff