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", "import/prefer-default-export": "off",
"no-useless-constructor": "off", "no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": 2, "@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 # Package Management & Libraries
node_modules node_modules
yarn.lock package-lock.json
package-json.lock
# Configuration Files # Configuration Files
config.yaml 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 ## 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. 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 @-rm -rf build
build: build:
tsc -p ./tsconfig.json -tsc -p ./tsconfig.json
run: run:
cd build && node main cd build && node main

View File

@ -3,30 +3,37 @@
"version": "1.0.0", "version": "1.0.0",
"description": "The official system for handling Community Relations in the LOC Discord server.", "description": "The official system for handling Community Relations in the LOC Discord server.",
"main": "build/main.js", "main": "build/main.js",
"scripts": {
"lint": "eslint -c ./.eslintrc.json src --ext ts"
},
"repository": "https://gitlab.libraryofcode.org/engineering/communityrelations.git", "repository": "https://gitlab.libraryofcode.org/engineering/communityrelations.git",
"author": "Matthew R <matthew@staff.libraryofcode.org>", "author": "Matthew R <matthew@staff.libraryofcode.org>",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"private": false, "private": false,
"devDependencies": { "devDependencies": {
"@types/mongoose": "^5.7.10", "@types/express": "^4.17.6",
"@types/node": "^13.11.0", "@types/helmet": "^0.0.47",
"@types/mongoose": "^5.7.19",
"@types/node": "^14.0.1",
"@types/signale": "^1.4.1", "@types/signale": "^1.4.1",
"@types/uuid": "^7.0.2", "@types/uuid": "^7.0.3",
"@types/yaml": "^1.2.0", "@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/eslint-plugin": "^2.27.0", "@typescript-eslint/parser": "^2.33.0",
"@typescript-eslint/parser": "^2.27.0", "eslint": "^7.0.0",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0", "eslint-config-airbnb-base": "^14.1.0",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"typescript": "^3.8.3" "typescript": "^3.9.2"
}, },
"dependencies": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.19.2",
"eris": "bsian03/eris#bsian", "eris": "bsian03/eris#bsian",
"moment": "^2.24.0", "eris-pagination": "bsian03/eris-pagination",
"mongoose": "^5.9.9", "express": "^4.17.1",
"helmet": "^3.22.0",
"moment": "^2.25.3",
"mongoose": "^5.9.13",
"signale": "^1.4.0", "signale": "^1.4.0",
"uuid": "^7.0.3", "uuid": "^8.0.0",
"yaml": "^1.8.3" "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 eris from 'eris';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { Collection, Command, Util } from '.'; import { Collection, Command, Util, ServerManagement, Event } from '.';
import { Moderation, ModerationInterface } from '../models'; 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 { export default class Client extends eris.Client {
public config: { token: string, prefix: string, guildID: string, mongoDB: string }; public config: { token: string, prefix: string, guildID: string, mongoDB: string };
public commands: Collection<Command>; public commands: Collection<Command>;
public events: Collection<Event>;
public intervals: Collection<NodeJS.Timeout>; public intervals: Collection<NodeJS.Timeout>;
public util: Util; 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) { constructor(token: string, options?: eris.ClientOptions) {
super(token, options); super(token, options);
this.commands = new Collection<Command>(); this.commands = new Collection<Command>();
this.events = new Collection<Event>();
this.intervals = new Collection<NodeJS.Timeout>(); this.intervals = new Collection<NodeJS.Timeout>();
this.db = { moderation: Moderation }; this.db = { Member, Moderation, Redirect };
} }
public async loadDatabase() { public async loadDatabase() {
@ -29,6 +35,7 @@ export default class Client extends eris.Client {
public loadPlugins() { public loadPlugins() {
this.util = new Util(this); this.util = new Util(this);
this.serverManagement = new ServerManagement(this);
} }
public async loadIntervals() { public async loadIntervals() {
@ -43,25 +50,22 @@ export default class Client extends eris.Client {
} }
public async loadEvents() { public async loadEvents() {
const evtFiles = await fs.readdir(`${__dirname}/../events`); const evtFiles = Object.entries<new(client: Client) => Event>(eventFiles);
evtFiles.forEach((file) => { for (const [name, Ev] of evtFiles) {
const eventName = file.split('.')[0]; const event = new Ev(this);
if (file === 'index.js') return; this.events.add(event.event, event);
// eslint-disable-next-line this.on(event.event, event.run);
const event = new (require(`${__dirname}/../events/${file}`).default)(this); this.util.signale.success(`Successfully loaded event: ${name}`);
this.on(eventName, (...args) => event.run(...args)); delete require.cache[require.resolve(`${__dirname}/../events/${name}`)];
this.util.signale.success(`Successfully loaded event: ${eventName}`); }
delete require.cache[require.resolve(`${__dirname}/../events/${file}`)];
});
} }
public async loadCommands() { public async loadCommands() {
const commandFiles = await fs.readdir(`${__dirname}/../commands`); const cmdFiles = Object.values<new(client: Client) => Command>(commandFiles);
commandFiles.forEach((file) => { for (const Cmd of cmdFiles) {
// eslint-disable-next-line new-cap const command = new Cmd(this);
const command: Command = new (require(`${__dirname}/../commands/${file}`).default)(this);
this.commands.add(command.name, command); this.commands.add(command.name, command);
this.util.signale.success(`Successfully loaded command: ${command.name}`); this.util.signale.success(`Successfully loaded command: ${command.name}`);
}); }
} }
} }

View File

@ -26,10 +26,13 @@ export default class Command {
/** /**
* - **0:** Everyone * - **0:** Everyone
* - **1:** Associates Team+ * - **1:** Associates+
* - **2:** Sheriff+ * - **2:** Core Team+
* - **3:** Faculty Marshals+ * - **3:** Moderators, Supervisor, & Board of Directors
* - **4:** Marshal Generals of Engineering * - **4:** Technicians, Supervisor, & Board of Directors
* - **5:** Moderators, Technicians, Supervisor, & Board of Directors
* - **6:** Supervisor+
* - **7:** Board of Directors
*/ */
public permissions: number; public permissions: number;
@ -57,13 +60,19 @@ export default class Command {
case 0: case 0:
return true; return true;
case 1: 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: 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: case 3:
return member.roles.some((r) => ['662163685439045632'].includes(r)); return member.roles.some((r) => ['455972169449734144', '701454855952138300', '662163685439045632'].includes(r));
case 4: 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: default:
return false; 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 */ /* eslint-disable no-bitwise */
import { Member, User } from 'eris'; import { Member, User } from 'eris';
import { v4 as uuid } from 'uuid'; import { randomBytes } from 'crypto';
import moment, { unitOfTime } from 'moment'; import moment, { unitOfTime } from 'moment';
import { Client, RichEmbed } from '.'; import { Client, RichEmbed } from '.';
import { Moderation as ModerationModel, ModerationInterface } from '../models'; 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> { 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'); 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); 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({ const mod = new ModerationModel({
userID: user.id, userID: user.id,
logID, logID,
@ -85,7 +85,7 @@ export default class Moderation {
this.client.unbanGuildMember(this.client.config.guildID, userID, reason); this.client.unbanGuildMember(this.client.config.guildID, userID, reason);
const user = await this.client.getRESTUser(userID); const user = await this.client.getRESTUser(userID);
if (!user) throw new Error('Cannot get user.'); if (!user) throw new Error('Cannot get user.');
const logID = uuid(); const logID = randomBytes(2).toString('hex');
const mod = new ModerationModel({ const mod = new ModerationModel({
userID, userID,
logID, logID,
@ -110,4 +110,33 @@ export default class Moderation {
this.client.createMessage(this.logChannels.modlogs, { embed }); this.client.createMessage(this.logChannels.modlogs, { embed });
return mod.save(); 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 */ /* eslint-disable no-param-reassign */
export interface EmbedData { import { EmbedOptions } from 'eris';
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}[]
}
export default class RichEmbed implements EmbedData { export default class RichEmbed implements EmbedOptions {
title?: string title?: string
type?: string type?: string
@ -23,15 +11,15 @@ export default class RichEmbed implements EmbedData {
url?: string url?: string
timestamp?: Date timestamp?: string | Date
color?: number color?: number
footer?: { text: string, icon_url?: string, proxy_icon_url?: string} 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 } video?: { url: string, height?: number, width?: number }
@ -41,7 +29,7 @@ export default class RichEmbed implements EmbedData {
fields?: {name: string, value: string, inline?: boolean}[] fields?: {name: string, value: string, inline?: boolean}[]
constructor(data: EmbedData = {}) { constructor(data: EmbedOptions = {}) {
/* /*
let types: { let types: {
title?: string, type?: string, description?: string, url?: string, timestamp?: Date, color?: number, fields?: {name: string, value: string, inline?: boolean}[] 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) { setURL(url: string) {
if (typeof url !== 'string') throw new TypeError('RichEmbed URLs must be a 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); this.url = encodeURI(url);
return this; return this;
} }
@ -124,8 +112,7 @@ export default class RichEmbed implements EmbedData {
* Sets the timestamp of this embed. * Sets the timestamp of this embed.
*/ */
setTimestamp(timestamp = new Date()) { setTimestamp(timestamp = new Date()) {
// eslint-disable-next-line no-restricted-globals if (Number.isNaN(timestamp.getTime())) throw new TypeError('Expecting ISO8601 (Date constructor)');
if (isNaN(timestamp.getTime())) throw new TypeError('Expecting ISO8601 (Date constructor)');
this.timestamp = timestamp; this.timestamp = timestamp;
return this; 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[] }> { public resolveCommand(query: string | string[]): Promise<{cmd: Command, args: string[] }> {
try { try {
// let resolvedCommand: Command;
// eslint-disable-next-line no-param-reassign
if (typeof query === 'string') query = query.split(' '); if (typeof query === 'string') query = query.split(' ');
const commands = this.client.commands.toArray(); const commands = this.client.commands.toArray();
const resolvedCommand = commands.find((c) => c.name === query[0].toLowerCase() || c.aliases.includes(query[0].toLowerCase())); 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()))); || 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 { try {
this.signale.error(error); 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) { if (message) {
const embed = new RichEmbed(); const embed = new RichEmbed();
embed.setColor('FF0000'); embed.setColor('FF0000');
@ -91,9 +89,8 @@ export default class Util {
} }
await this.client.createMessage('595788220764127272', info); await this.client.createMessage('595788220764127272', info);
const msg = message ? message.content.slice(this.client.config.prefix.length).trim().split(/ +/g) : []; 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 (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) { } catch (err) {
this.signale.error(err); this.signale.error(err);
} }
@ -101,7 +98,6 @@ export default class Util {
public splitString(string: string, length: number): string[] { public splitString(string: string, length: number): string[] {
if (!string) return []; if (!string) return [];
// eslint-disable-next-line no-param-reassign
if (Array.isArray(string)) string = string.join('\n'); if (Array.isArray(string)) string = string.join('\n');
if (string.length <= length) return [string]; if (string.length <= length) return [string];
const arrayString: string[] = []; const arrayString: string[] = [];
@ -111,19 +107,24 @@ export default class Util {
pos = string.length > length ? string.lastIndexOf('\n', length) : string.length; pos = string.length > length ? string.lastIndexOf('\n', length) : string.length;
if (pos > length) pos = length; if (pos > length) pos = length;
str = string.substr(0, pos); str = string.substr(0, pos);
// eslint-disable-next-line no-param-reassign
string = string.substr(pos); string = string.substr(pos);
arrayString.push(str); arrayString.push(str);
} }
return arrayString; 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 { public decimalToHex(int: number): string {
const red = (int && 0x0000ff) << 16; const hex = int.toString(16);
const green = int && 0x00ff00; return '#000000'.substring(0, 7 - hex.length) + hex;
const blue = (int && 0xff0000) >>> 16;
const number = red | green | blue;
const asHex = number.toString(16);
return '#000000'.substring(0, 7 - asHex.length) + asHex;
} }
} }

View File

@ -1,6 +1,10 @@
export { default as Client } from './Client'; export { default as Client } from './Client';
export { default as Collection } from './Collection'; export { default as Collection } from './Collection';
export { default as Command } from './Command'; export { default as Command } from './Command';
export { default as Event } from './Event';
export { default as Moderation } from './Moderation'; export { default as Moderation } from './Moderation';
export { default as RichEmbed } from './RichEmbed'; 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'; 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.name = 'ban';
this.description = 'Bans a member from the guild.'; this.description = 'Bans a member from the guild.';
this.usage = 'ban <member> [time] [reason]'; this.usage = 'ban <member> [time] [reason]';
this.permissions = 2; this.permissions = 3;
this.guildOnly = true; this.guildOnly = true;
this.enabled = true; this.enabled = true;
} }
public async run(message: Message, args: string[]) { public async run(message: Message, args: string[]) {
try { 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)); const member = this.client.util.resolveMember(args[0], this.client.guilds.get(this.client.config.guildID));
let user: User; let user: User;
if (!member) { if (!member) {
@ -23,6 +24,8 @@ export default class Ban extends Command {
} catch { } catch {
return this.error(message.channel, 'Cannot find user.'); return this.error(message.channel, 'Cannot find user.');
} }
} else {
user = member.user;
} }
try { try {
await this.client.guilds.get(this.client.config.guildID).getBan(args[0]); 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.name = 'eval';
this.description = 'Evaluates native JS code'; this.description = 'Evaluates native JS code';
this.aliases = ['e']; this.aliases = ['e'];
this.permissions = 4; this.permissions = 7;
this.enabled = true; this.enabled = true;
this.guildOnly = false; this.guildOnly = false;
} }

View File

@ -3,11 +3,11 @@ import { Activity, Member, Message } from 'eris';
import { Client, Command, RichEmbed } from '../class'; import { Client, Command, RichEmbed } from '../class';
enum ActivityType { enum ActivityType {
PLAYING = 0, PLAYING,
STREAMING = 1, STREAMING,
LISTENING = 2, LISTENING,
WATCHING = 3, WATCHING,
CUSTOM_STATUS = 4 CUSTOM_STATUS
} }
export default class Game extends Command { export default class Game extends Command {
@ -47,13 +47,9 @@ export default class Game extends Command {
embed.setColor('#1ed760'); embed.setColor('#1ed760');
embed.addField('Song', mainStatus.details, true); embed.addField('Song', mainStatus.details, true);
embed.addField('Artist', mainStatus.state, true); embed.addField('Artist', mainStatus.state, true);
// @ts-ignore
embed.addField('Album', mainStatus.assets.large_text); embed.addField('Album', mainStatus.assets.large_text);
// @ts-ignore
embed.addField('Start', `${new Date(mainStatus.timestamps.start).toLocaleTimeString('en-us')} ET`, true); 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); 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.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.setFooter(`Listening to Spotify | ${this.client.user.username}`, 'https://media.discordapp.net/attachments/358674161566220288/496894273304920064/2000px-Spotify_logo_without_text.png');
embed.setTimestamp(); 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[]) { public async run(message: Message, args: string[]) {
try { 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]); 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 if (!role) { // if it's a role name

View File

@ -7,13 +7,14 @@ export default class Unban extends Command {
this.name = 'unban'; this.name = 'unban';
this.description = 'Unbans a member from the guild.'; this.description = 'Unbans a member from the guild.';
this.usage = 'unban <user id> [reason]'; this.usage = 'unban <user id> [reason]';
this.permissions = 2; this.permissions = 3;
this.guildOnly = true; this.guildOnly = true;
this.enabled = true; this.enabled = true;
} }
public async run(message: Message, args: string[]) { public async run(message: Message, args: string[]) {
try { try {
if (!args[0]) return this.client.commands.get('help').run(message, [this.name]);
let user: User; let user: User;
try { try {
user = await this.client.getRESTUser(args[0]); 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); const ackResolve = this.resolveStaffInformation(member.id);
let description = ''; let description = '';
if (ackResolve) { if (ackResolve) {
if (ackResolve?.title && ackResolve?.dept) { if (ackResolve?.title) {
description += `${emotes.titleAndDepartment} __**${ackResolve.title}**__, ${ackResolve.dept}\n\n`; description += `${emotes.titleAndDepartment} __**${ackResolve.title}**__\n\n`;
} }
if (ackResolve?.emailAddress) { if (ackResolve?.emailAddress) {
description += `${emotes.email} ${ackResolve.emailAddress}\n`; 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); 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('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); 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) { if (member.roles.length > 0) {
@ -78,14 +87,98 @@ export default class Whois extends Command {
const bit = member.permission.allow; const bit = member.permission.allow;
if (this.client.guilds.get(this.client.config.guildID).ownerID === member.id) serverAcknowledgements.push('Server Owner'); 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 | 8) === bit) { permissions.push('Administrator'); serverAcknowledgements.push('Server Admin'); }
if ((bit | 20) === bit) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); } if ((bit | 32) === bit) { permissions.push('Manage Server'); serverAcknowledgements.push('Server Manager'); }
if ((bit | 10) === bit) permissions.push('Manage Channels'); if ((bit | 16) === bit) permissions.push('Manage Channels');
if ((bit | 268435456) === bit) permissions.push('Manage Roles'); if ((bit | 268435456) === bit) permissions.push('Manage Roles');
if ((bit | 8192) === bit) { permissions.push('Manage Messages'); serverAcknowledgements.push('Server Moderator'); } if ((bit | 8192) === bit) { permissions.push('Manage Messages'); serverAcknowledgements.push('Server Moderator'); }
if ((bit | 134217728) === bit) permissions.push('Manage Nicknames'); if ((bit | 134217728) === bit) permissions.push('Manage Nicknames');
if ((bit | 1073741824) === bit) permissions.push('Manage Emojis'); if ((bit | 1073741824) === bit) permissions.push('Manage Emojis');
if ((bit | 4) === bit) permissions.push('Ban Members'); if ((bit | 4) === bit) permissions.push('Ban Members');
if ((bit | 2) === bit) permissions.push('Kick 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) { if (permissions.length > 0) {
embed.addField('Permissions', permissions.join(', ')); embed.addField('Permissions', permissions.join(', '));
} }

View File

@ -2,19 +2,17 @@
{ {
"name": "Matthew", "name": "Matthew",
"id": "278620217221971968", "id": "278620217221971968",
"title": "Marshal General of Engineering", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "matthew@staff.libraryofcode.org", "emailAddress": "matthew@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/matthew", "gitlab": "https://gitlab.libraryofcode.org/matthew",
"github": "https://github.com/matthew119427", "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"] "acknowledgements": ["Maintainer & Lead Developer"]
}, },
{ {
"name": "Joe", "name": "Joe",
"id": "556969659531001858", "id": "556969659531001858",
"title": "Marshal General of Community Relations", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "joe@staff.libraryofcode.org", "emailAddress": "joe@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/Joe", "gitlab": "https://gitlab.libraryofcode.org/Joe",
"github": "https://github.com/sirdroolio", "github": "https://github.com/sirdroolio",
@ -24,8 +22,7 @@
{ {
"name": "Bsian", "name": "Bsian",
"id": "253600545972027394", "id": "253600545972027394",
"title": "Assistant Marshal General of Engineering", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "bsian@staff.libraryofcode.org", "emailAddress": "bsian@staff.libraryofcode.org",
"bio": "I also like trains", "bio": "I also like trains",
"acknowledgements": ["Maintainer & Assistant Lead Developer"] "acknowledgements": ["Maintainer & Assistant Lead Developer"]
@ -33,23 +30,20 @@
{ {
"name": "NightRaven", "name": "NightRaven",
"id": "239261547959025665", "id": "239261547959025665",
"title": "Assistant Marshal General of Community Relations", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "nightraven@staff.libraryofcode.org", "emailAddress": "nightraven@staff.libraryofcode.org",
"bio": "I like trains" "bio": "I like trains"
}, },
{ {
"name": "Midori", "name": "Midori",
"id": "109122112643440640", "id": "109122112643440640",
"title": "Assistant Marshal General of Community Relations", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "midori@staff.libraryofcode.org" "emailAddress": "midori@staff.libraryofcode.org"
}, },
{ {
"name": "Unknown", "name": "Unknown",
"id": "143414786913206272", "id": "143414786913206272",
"title": "Staff Sergeant", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "unknown@staff.libraryofcode.org", "emailAddress": "unknown@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/unknown", "gitlab": "https://gitlab.libraryofcode.org/unknown",
"bio": "im not a proffesional developer or anything, i just enjoy it as a hobby." "bio": "im not a proffesional developer or anything, i just enjoy it as a hobby."
@ -57,53 +51,24 @@
{ {
"name": "TheSkele27", "name": "TheSkele27",
"id": "213632190557192192", "id": "213632190557192192",
"title": "Community Advisor", "title": "Board of Directors",
"dept": "Faculty Marshals",
"emailAddress": "theskele27@staff.libraryofcode.org", "emailAddress": "theskele27@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/TheSkele27", "gitlab": "https://gitlab.libraryofcode.org/TheSkele27",
"github": "https://github.com/TheSkele27", "github": "https://github.com/TheSkele27",
"bio": "Is water wet?" "bio": "Is water wet?"
}, },
{ {
"name": "Aikaterna", "name": "Catbirby",
"id": "154497072148643840", "id": "131953641371205632",
"title": "Sheriff", "title": "Supervisor",
"dept": "Staff Team" "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": "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": "D3XTER", "name": "D3XTER",
"id": "468009964263178262", "id": "468009964263178262",
"title": "Associate", "title": "Core Team",
"dept": "Staff Team",
"emailAddress": "dexter@staff.libraryofcode.org", "emailAddress": "dexter@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/D3XTER", "gitlab": "https://gitlab.libraryofcode.org/D3XTER",
"bio": "Hi I'm D3XTER how are ya?" "bio": "Hi I'm D3XTER how are ya?"
@ -111,8 +76,7 @@
{ {
"name": "DedShotTM", "name": "DedShotTM",
"id": "402154763363418142", "id": "402154763363418142",
"title": "Associate", "title": "Technician & Moderator",
"dept": "Staff Team",
"emailAddress": "dedshot@staff.libraryofcode.org", "emailAddress": "dedshot@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/DedShotTM", "gitlab": "https://gitlab.libraryofcode.org/DedShotTM",
"github": "https://github.com/DedShotTM", "github": "https://github.com/DedShotTM",
@ -122,8 +86,7 @@
{ {
"name": "EdgyBoi2414", "name": "EdgyBoi2414",
"id": "397432516010835970", "id": "397432516010835970",
"title": "Webmaster", "title": "Core Team",
"dept": "Staff Team",
"emailAddress": "edgyboi2414@gmail.com", "emailAddress": "edgyboi2414@gmail.com",
"gitlab": "https://gitlab.libraryofcode.org/EdgyBoi2414", "gitlab": "https://gitlab.libraryofcode.org/EdgyBoi2414",
"github": "https://github.com/EdgyBoi2414", "github": "https://github.com/EdgyBoi2414",
@ -132,18 +95,23 @@
{ {
"name": "Hector", "name": "Hector",
"id": "377781496292835339", "id": "377781496292835339",
"title": "Associate", "title": "Core Team",
"dept": "Staff Team",
"emailAddress": "hector@staff.libraryofcode.org", "emailAddress": "hector@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/Hector", "gitlab": "https://gitlab.libraryofcode.org/Hector",
"github": "https://github.com/Hector6704", "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!" "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", "name": "KhaaZ",
"id": "179908288337412096", "id": "179908288337412096",
"title": "Associate", "title": "Core Team",
"dept": "Staff Team",
"gitlab": "https://gitlab.libraryofcode.org/KhaaZ", "gitlab": "https://gitlab.libraryofcode.org/KhaaZ",
"github": "https://github.com/Khaazz", "github": "https://github.com/Khaazz",
"bio": "I baguette for a living and eat code for breakfast." "bio": "I baguette for a living and eat code for breakfast."
@ -151,9 +119,20 @@
{ {
"name": "Ryan", "name": "Ryan",
"id": "186679073764802560", "id": "186679073764802560",
"title": "Associate",
"emailAddress": "wbdvryan@staff.libraryofcode.org",
"gitlab": "https://gitlab.libraryofcode.org/plainRyan", "gitlab": "https://gitlab.libraryofcode.org/plainRyan",
"bio": "Experiment, learn, share, repeat.",
"acknowledgements": ["Contributor"] "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", "name": "Null",
"id": "323673862971588609", "id": "323673862971588609",

View File

@ -1,12 +1,13 @@
/* eslint-disable no-useless-return */ /* eslint-disable no-useless-return */
import { Message, TextChannel, NewsChannel } from 'eris'; 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; public client: Client;
constructor(client: Client) { constructor(client: Client) {
this.client = client; super(client);
this.event = 'messageCreate';
} }
public async run(message: Message) { 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.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.enabled) { message.channel.createMessage(`***${this.client.util.emojis.ERROR} This command has been disabled***`); return; }
if (!resolved.cmd.checkPermissions(message.member)) 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); await resolved.cmd.run(message, resolved.args);
} catch (err) { } catch (err) {
this.client.util.handleError(err, message); 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; public client: Client;
constructor(client: Client) { constructor(client: Client) {
this.client = client; super(client);
this.event = 'ready';
} }
public async run() { public async run() {

View File

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