initial files

This commit is contained in:
Erik 2021-06-18 16:41:57 +03:00
commit 856f2dc0a7
No known key found for this signature in database
GPG Key ID: 7E862371D3409F16
16 changed files with 2389 additions and 0 deletions

208
.eslintrc.js Normal file
View 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
View File

@ -0,0 +1,9 @@
# Env
config.js
# Node bs
node_modules
# Logs & cache
logs
modmail_cache

5
index.js Normal file
View 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
View 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
View File

@ -0,0 +1,3 @@
module.exports = {
Logger: require('./Logger')
};

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

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

View File

@ -0,0 +1,4 @@
module.exports = {
DiscordWebhook: require('./DiscordWebhook.js'),
FileExtension: require('./FileExtension.js')
};

25
package.json Normal file
View 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
View 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
View 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
View 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
View 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;

View 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
View File

@ -0,0 +1,3 @@
module.exports = {
Client: require('./Client.js')
};

1727
yarn.lock Normal file

File diff suppressed because it is too large Load Diff