forked from Galactic/modmail
initial files
This commit is contained in:
commit
856f2dc0a7
208
.eslintrc.js
Normal file
208
.eslintrc.js
Normal file
@ -0,0 +1,208 @@
|
||||
module.exports = {
|
||||
'env': {
|
||||
'commonjs': true,
|
||||
'es2021': true,
|
||||
'node': true
|
||||
},
|
||||
'extends': 'eslint:recommended',
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 12
|
||||
},
|
||||
'rules': {
|
||||
"accessor-pairs": "warn",
|
||||
"array-callback-return": "warn",
|
||||
"arrow-parens": "warn",
|
||||
"arrow-spacing": "warn",
|
||||
"block-scoped-var": "warn",
|
||||
"block-spacing": "warn",
|
||||
"brace-style": "warn",
|
||||
"callback-return": "warn",
|
||||
"camelcase": "warn",
|
||||
"comma-dangle": "warn",
|
||||
"comma-spacing": [
|
||||
"warn",
|
||||
{
|
||||
"after": true,
|
||||
"before": false
|
||||
}
|
||||
],
|
||||
"comma-style": "warn",
|
||||
"computed-property-spacing": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"consistent-this": "warn",
|
||||
"dot-notation": [
|
||||
"warn",
|
||||
{
|
||||
"allowKeywords": true
|
||||
}
|
||||
],
|
||||
"eol-last": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"eqeqeq": "warn",
|
||||
"func-call-spacing": "warn",
|
||||
"func-name-matching": "warn",
|
||||
"func-names": "warn",
|
||||
"func-style": "warn",
|
||||
"function-paren-newline": "warn",
|
||||
"generator-star-spacing": "warn",
|
||||
"grouped-accessor-pairs": "warn",
|
||||
"guard-for-in": "warn",
|
||||
"handle-callback-err": "warn",
|
||||
"id-blacklist": "warn",
|
||||
"id-match": "warn",
|
||||
"implicit-arrow-linebreak": "warn",
|
||||
"indent": [
|
||||
"warn",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"init-declarations": "warn",
|
||||
"jsx-quotes": "warn",
|
||||
"key-spacing": "warn",
|
||||
"linebreak-style": [
|
||||
"warn",
|
||||
"windows"
|
||||
],
|
||||
"lines-around-comment": "warn",
|
||||
"lines-around-directive": "warn",
|
||||
"lines-between-class-members": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"max-classes-per-file": "warn",
|
||||
"max-nested-callbacks": "warn",
|
||||
"new-parens": "warn",
|
||||
"no-alert": "warn",
|
||||
"no-array-constructor": "warn",
|
||||
"no-bitwise": "warn",
|
||||
"no-buffer-constructor": "warn",
|
||||
"no-caller": "warn",
|
||||
"no-console": "warn",
|
||||
"no-div-regex": "warn",
|
||||
"no-dupe-else-if": "warn",
|
||||
"no-duplicate-imports": "warn",
|
||||
"no-else-return": "warn",
|
||||
"no-empty-function": "warn",
|
||||
"no-eq-null": "warn",
|
||||
"no-eval": "warn",
|
||||
"no-extend-native": "warn",
|
||||
"no-extra-bind": "warn",
|
||||
"no-extra-label": "warn",
|
||||
"no-extra-parens": "warn",
|
||||
"no-floating-decimal": "warn",
|
||||
"no-implicit-coercion": "warn",
|
||||
"no-implicit-globals": "warn",
|
||||
"no-implied-eval": "warn",
|
||||
"no-import-assign": "warn",
|
||||
"no-invalid-this": "warn",
|
||||
"no-iterator": "warn",
|
||||
"no-label-var": "warn",
|
||||
// "no-labels": "warn",
|
||||
"no-lone-blocks": "warn",
|
||||
"no-loop-func": "warn",
|
||||
"no-mixed-requires": "warn",
|
||||
"no-multi-assign": "warn",
|
||||
"no-multi-spaces": "warn",
|
||||
"no-multi-str": "warn",
|
||||
"no-multiple-empty-lines": "warn",
|
||||
"no-native-reassign": "warn",
|
||||
"no-negated-in-lhs": "warn",
|
||||
"no-nested-ternary": "warn",
|
||||
"no-new": "warn",
|
||||
"no-new-func": "warn",
|
||||
"no-new-object": "warn",
|
||||
"no-new-require": "warn",
|
||||
"no-new-wrappers": "warn",
|
||||
"no-octal-escape": "warn",
|
||||
"no-path-concat": "warn",
|
||||
"no-process-exit": "warn",
|
||||
"no-proto": "warn",
|
||||
"no-restricted-globals": "warn",
|
||||
"no-restricted-imports": "warn",
|
||||
"no-restricted-modules": "warn",
|
||||
"no-restricted-properties": "warn",
|
||||
"no-restricted-syntax": "warn",
|
||||
"no-return-assign": "warn",
|
||||
"no-return-await": "warn",
|
||||
"no-script-url": "warn",
|
||||
"no-self-compare": "warn",
|
||||
"no-sequences": "warn",
|
||||
"no-setter-return": "warn",
|
||||
"no-spaced-func": "warn",
|
||||
"no-tabs": "warn",
|
||||
"no-template-curly-in-string": "warn",
|
||||
"no-throw-literal": "warn",
|
||||
"no-undef-init": "warn",
|
||||
"no-unmodified-loop-condition": "warn",
|
||||
"no-unneeded-ternary": "warn",
|
||||
"no-unused-expressions": "warn",
|
||||
"no-use-before-define": "warn",
|
||||
"no-useless-call": "warn",
|
||||
"no-useless-computed-key": "warn",
|
||||
"no-useless-concat": "warn",
|
||||
"no-useless-constructor": "warn",
|
||||
"no-useless-rename": "warn",
|
||||
"no-useless-return": "warn",
|
||||
"no-var": "warn",
|
||||
"no-void": "warn",
|
||||
"no-whitespace-before-property": "warn",
|
||||
"nonblock-statement-body-position": "warn",
|
||||
"object-curly-spacing": [
|
||||
"warn",
|
||||
"always"
|
||||
],
|
||||
"object-shorthand": "warn",
|
||||
"one-var-declaration-per-line": "warn",
|
||||
"operator-assignment": "warn",
|
||||
"padding-line-between-statements": "warn",
|
||||
"prefer-arrow-callback": "warn",
|
||||
"prefer-const": "warn",
|
||||
"prefer-destructuring": "warn",
|
||||
"prefer-exponentiation-operator": "warn",
|
||||
"prefer-numeric-literals": "warn",
|
||||
"prefer-object-spread": "warn",
|
||||
"prefer-promise-reject-errors": "warn",
|
||||
"prefer-regex-literals": "warn",
|
||||
"prefer-rest-params": "warn",
|
||||
"prefer-spread": "warn",
|
||||
"require-jsdoc": "warn",
|
||||
"require-unicode-regexp": "warn",
|
||||
"rest-spread-spacing": "warn",
|
||||
"semi": "warn",
|
||||
"semi-spacing": "warn",
|
||||
"semi-style": [
|
||||
"warn",
|
||||
"last"
|
||||
],
|
||||
"space-before-blocks": "warn",
|
||||
"space-in-parens": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"switch-colon-spacing": "warn",
|
||||
"symbol-description": "warn",
|
||||
"template-curly-spacing": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"template-tag-spacing": "warn",
|
||||
"unicode-bom": [
|
||||
"warn",
|
||||
"never"
|
||||
],
|
||||
"vars-on-top": "warn",
|
||||
"wrap-iife": "warn",
|
||||
"wrap-regex": "warn",
|
||||
"yield-star-spacing": "warn",
|
||||
"yoda": [
|
||||
"warn",
|
||||
"never"
|
||||
]
|
||||
}
|
||||
};
|
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
# Env
|
||||
config.js
|
||||
|
||||
# Node bs
|
||||
node_modules
|
||||
|
||||
# Logs & cache
|
||||
logs
|
||||
modmail_cache
|
5
index.js
Normal file
5
index.js
Normal file
@ -0,0 +1,5 @@
|
||||
const Options = require('./config');
|
||||
const { Client } = require('./structure');
|
||||
|
||||
const client = new Client(Options);
|
||||
client.init();
|
77
logger/Logger.js
Normal file
77
logger/Logger.js
Normal file
@ -0,0 +1,77 @@
|
||||
const { createLogger, format, transports: { Console }, config } = require('winston');
|
||||
const moment = require('moment');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const { DiscordWebhook, FileExtension } = require('./transports/index.js');
|
||||
|
||||
const Constants = {
|
||||
Colors: {
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
info: 'blue',
|
||||
verbose: 'cyan',
|
||||
debug: 'magenta',
|
||||
silly: 'magentaBright'
|
||||
}
|
||||
};
|
||||
|
||||
class Logger {
|
||||
|
||||
constructor(client, options) {
|
||||
this.client = client;
|
||||
this.options = options;
|
||||
|
||||
const transports = [
|
||||
new FileExtension({ filename: `logs/${this.date.split(' ')[0]}.log`, level: 'debug' }), //Will NOT log "silly" logs, could change in future.
|
||||
new FileExtension({ filename: `logs/errors/${this.date.split(' ')[0]}-error.log`, level: 'error' }),
|
||||
new Console({ level: 'silly' }) //Will log EVERYTHING.
|
||||
];
|
||||
|
||||
if (!options.webhook.disabled) transports.push(new DiscordWebhook({ level: 'error', ...options.webhook })); //Broadcast errors to a discord webhook.
|
||||
|
||||
this.logger = createLogger({
|
||||
levels: config.npm.levels,
|
||||
format:
|
||||
format.cli({
|
||||
colors: Constants.Colors
|
||||
}),
|
||||
transports
|
||||
});
|
||||
|
||||
//TODO: Add proper date-oriented filenames and add a daily rotation file (?).
|
||||
|
||||
}
|
||||
|
||||
write(type = 'silly', string = '') {
|
||||
|
||||
const color = Constants.Colors[type];
|
||||
const header = `${chalk[color](`[${this.date}][modmail]`)}`;
|
||||
|
||||
|
||||
this.logger.log(type, `${header} : ${string}`);
|
||||
|
||||
}
|
||||
|
||||
get date() {
|
||||
return moment().format("YYYY-MM-DD hh:mm:ss");
|
||||
}
|
||||
|
||||
info(message) {
|
||||
this.write('info', message);
|
||||
}
|
||||
|
||||
warn(message) {
|
||||
this.write('warn', message);
|
||||
}
|
||||
|
||||
error(message) {
|
||||
this.write('error', message);
|
||||
}
|
||||
|
||||
debug(message) {
|
||||
this.write('debug', message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Logger;
|
3
logger/index.js
Normal file
3
logger/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
Logger: require('./Logger')
|
||||
};
|
50
logger/transports/DiscordWebhook.js
Normal file
50
logger/transports/DiscordWebhook.js
Normal file
@ -0,0 +1,50 @@
|
||||
const Transport = require('winston-transport');
|
||||
const { WebhookClient } = require('discord.js');
|
||||
const os = require('os');
|
||||
const { username } = os.userInfo();
|
||||
|
||||
//eslint-disable-next-line no-control-regex
|
||||
const regex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/gu;
|
||||
|
||||
class DiscordWebhook extends Transport {
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
|
||||
this.webhookClient = new WebhookClient(
|
||||
opts.id,
|
||||
opts.token
|
||||
);
|
||||
}
|
||||
|
||||
log(info, callback) {
|
||||
setImmediate(() => {
|
||||
this.emit('logged', info);
|
||||
});
|
||||
|
||||
const message = info.message.replace(regex, '')
|
||||
//.replace(new RegExp(options.bot.token, 'gu'), '<redacted>')
|
||||
.replace(new RegExp(username, 'gu'), '<redacted>');
|
||||
|
||||
const developers = [
|
||||
'nolan',
|
||||
'navy',
|
||||
'sema'
|
||||
];
|
||||
const random = developers[Math.floor(Math.random() * developers.length)];
|
||||
|
||||
const embed = {
|
||||
color: 0xe88388,
|
||||
timestamp: new Date(),
|
||||
description: `\`\`\`${message}\`\`\``,
|
||||
footer: {
|
||||
text: `probably ${random}'s fault`
|
||||
}
|
||||
};
|
||||
|
||||
this.webhookClient.send('', { embeds: [embed] });
|
||||
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DiscordWebhook;
|
119
logger/transports/FileExtension.js
Normal file
119
logger/transports/FileExtension.js
Normal file
@ -0,0 +1,119 @@
|
||||
/* eslint-disable */
|
||||
const { transports: { File } } = require('winston');
|
||||
const diagnostics = require('diagnostics');
|
||||
const debug = diagnostics('winston:file');
|
||||
const { MESSAGE } = require('triple-beam');
|
||||
const moment = require('moment');
|
||||
|
||||
const regex = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
|
||||
|
||||
class FileExtension extends File {
|
||||
|
||||
constructor(opts) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
log(info, callback = () => {}) {
|
||||
// Remark: (jcrugzz) What is necessary about this callback(null, true) now
|
||||
// when thinking about 3.x? Should silent be handled in the base
|
||||
// TransportStream _write method?
|
||||
if (this.silent) {
|
||||
callback();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Output stream buffer is full and has asked us to wait for the drain event
|
||||
if (this._drain) {
|
||||
this._stream.once('drain', () => {
|
||||
this._drain = false;
|
||||
this.log(info, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this._rotate) {
|
||||
this._stream.once('rotate', () => {
|
||||
this._rotate = false;
|
||||
this.log(info, callback);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab the raw string and append the expected EOL.
|
||||
const output = `${info[MESSAGE]}${this.eol}`.replace(regex, '');
|
||||
const bytes = Buffer.byteLength(output);
|
||||
|
||||
// After we have written to the PassThrough check to see if we need
|
||||
// to rotate to the next file.
|
||||
//
|
||||
// Remark: This gets called too early and does not depict when data
|
||||
// has been actually flushed to disk.
|
||||
function logged() {
|
||||
this._size += bytes;
|
||||
this._pendingSize -= bytes;
|
||||
|
||||
debug('logged %s %s', this._size, output);
|
||||
this.emit('logged', info);
|
||||
|
||||
// Do not attempt to rotate files while opening
|
||||
if (this._opening) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if we need to end the stream and create a new one.
|
||||
if (!this._needsNewFile()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// End the current stream, ensure it flushes and create a new one.
|
||||
// This could potentially be optimized to not run a stat call but its
|
||||
// the safest way since we are supporting `maxFiles`.
|
||||
this._rotate = true;
|
||||
this._endStream(() => this._rotateFile());
|
||||
}
|
||||
|
||||
// Keep track of the pending bytes being written while files are opening
|
||||
// in order to properly rotate the PassThrough this._stream when the file
|
||||
// eventually does open.
|
||||
this._pendingSize += bytes;
|
||||
if (this._opening
|
||||
&& !this.rotatedWhileOpening
|
||||
&& this._needsNewFile(this._size + this._pendingSize)) {
|
||||
this.rotatedWhileOpening = true;
|
||||
}
|
||||
|
||||
const written = this._stream.write(output, logged.bind(this));
|
||||
if (!written) {
|
||||
this._drain = true;
|
||||
this._stream.once('drain', () => {
|
||||
this._drain = false;
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
callback(); // eslint-disable-line callback-return
|
||||
}
|
||||
|
||||
debug('written', written, this._drain);
|
||||
|
||||
this.finishIfEnding();
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
get date() {
|
||||
return moment().format("MM-DD-YYYY hh:mm:ss");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const Constants = {
|
||||
Colors: {
|
||||
error: 'red',
|
||||
warn: 'yellow',
|
||||
info: 'blue',
|
||||
verbose: 'cyan',
|
||||
debug: 'magenta',
|
||||
silly: 'magentaBright'
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = FileExtension;
|
4
logger/transports/index.js
Normal file
4
logger/transports/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
DiscordWebhook: require('./DiscordWebhook.js'),
|
||||
FileExtension: require('./FileExtension.js')
|
||||
};
|
25
package.json
Normal file
25
package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "modmail",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "Navy <navydotgif@gmail.com>",
|
||||
"license": "MIT",
|
||||
"private": false,
|
||||
"description": "Modmail bot with eventual integration with Galactic Bot's API",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"dev": "nodemon index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.28.0",
|
||||
"nodemon": "^2.0.7"
|
||||
},
|
||||
"dependencies": {
|
||||
"chalk": "^4.1.1",
|
||||
"diagnostics": "^2.0.2",
|
||||
"discord.js": "^12.5.3",
|
||||
"moment": "^2.29.1",
|
||||
"winston": "^3.3.3",
|
||||
"winston-transport": "^4.4.0"
|
||||
}
|
||||
}
|
50
structure/Client.js
Normal file
50
structure/Client.js
Normal file
@ -0,0 +1,50 @@
|
||||
const { Client } = require('discord.js');
|
||||
|
||||
const { Logger } = require('../logger');
|
||||
const Modmail = require('./Modmail');
|
||||
const Registry = require('./Registry');
|
||||
|
||||
class ModmailClient extends Client {
|
||||
|
||||
constructor(options) {
|
||||
|
||||
super(options.clientOptions);
|
||||
|
||||
this._options = options;
|
||||
this._ready = false;
|
||||
|
||||
this.logger = new Logger(this, options.loggerOptions);
|
||||
this.modmail = new Modmail(this);
|
||||
this.registry = new Registry(this);
|
||||
|
||||
this.on('ready', () => {
|
||||
this.logger.info(`Client ready, logged in as ${this.user.tag}`);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
async init() {
|
||||
|
||||
this.logger.info(`Logging in`);
|
||||
await this.login(this._options.discordToken);
|
||||
this.logger.info(`Starting up modmail`);
|
||||
this.modmail.init();
|
||||
this.registry.loadCommands();
|
||||
|
||||
this.on('message', this.handleMessage.bind(this));
|
||||
|
||||
this._ready = true;
|
||||
|
||||
}
|
||||
|
||||
async handleMessage(message) {
|
||||
|
||||
if (!message.guild) return this.modmail.handleUser(message);
|
||||
|
||||
const { channel, guild } = message;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ModmailClient;
|
21
structure/Command.js
Normal file
21
structure/Command.js
Normal file
@ -0,0 +1,21 @@
|
||||
class Command {
|
||||
|
||||
constructor(client, options) {
|
||||
|
||||
Object.entries(options).forEach(([key, val]) => {
|
||||
this[key] = val;
|
||||
});
|
||||
|
||||
if (!this.name) throw new Error(`Missing name for command`);
|
||||
|
||||
this.client = client;
|
||||
|
||||
}
|
||||
|
||||
async execute() {
|
||||
throw new Error(`Missing execute in ${this.name}`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Command;
|
35
structure/Modmail.js
Normal file
35
structure/Modmail.js
Normal file
@ -0,0 +1,35 @@
|
||||
class Modmail {
|
||||
|
||||
constructor(client) {
|
||||
|
||||
this.client = client;
|
||||
this.mainServer = null;
|
||||
this.bansServer = null;
|
||||
|
||||
}
|
||||
|
||||
init() {
|
||||
|
||||
const { bansGuild, mainGuild } = this.client._options;
|
||||
|
||||
this.mainServer = this.client.guilds.cache.get(mainGuild);
|
||||
if (!this.mainServer) throw new Error(`Missing main server: ${mainGuild} is not a valid server ID`);
|
||||
|
||||
this.bansServer = this.client.guilds.cache.get(bansGuild) || null;
|
||||
this.client.logger.warn(`Missing bans server: ${bansGuild} is not a valid server ID`);
|
||||
|
||||
}
|
||||
|
||||
async handleUser(message) {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
async handleServer() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Modmail;
|
40
structure/Registry.js
Normal file
40
structure/Registry.js
Normal file
@ -0,0 +1,40 @@
|
||||
const { Collection } = require("discord.js");
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
class Registry {
|
||||
|
||||
constructor(client) {
|
||||
|
||||
this.client = client;
|
||||
|
||||
this.commands = new Collection();
|
||||
|
||||
}
|
||||
|
||||
loadCommands() {
|
||||
|
||||
const commandsDir = path.join(process.cwd(), 'structure', 'commands');
|
||||
const files = fs.readdirSync(commandsDir);
|
||||
|
||||
for (const file of files) {
|
||||
|
||||
const commandPath = path.join(commandsDir, file);
|
||||
const commandClass = require(commandPath);
|
||||
|
||||
if (typeof commandClass !== 'function') {
|
||||
delete require.cache[commandPath];
|
||||
continue;
|
||||
}
|
||||
|
||||
const command = new commandClass(this.client);
|
||||
if (this.commands.has(command.name)) this.client.logger(`Command by name ${command.name} already exists, skipping duplicate at path ${commandPath}`);
|
||||
else this.commands.set(command.name, command);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Registry;
|
13
structure/commands/Ping.js
Normal file
13
structure/commands/Ping.js
Normal file
@ -0,0 +1,13 @@
|
||||
const Command = require('../Command');
|
||||
|
||||
class Ping extends Command {
|
||||
|
||||
constructor(client) {
|
||||
super(client, {
|
||||
name: 'ping'
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = Ping;
|
3
structure/index.js
Normal file
3
structure/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
Client: require('./Client.js')
|
||||
};
|
Loading…
Reference in New Issue
Block a user