Initial commit

This commit is contained in:
1computer1 2019-03-12 05:09:46 -04:00
commit 07ec57a453
27 changed files with 1853 additions and 0 deletions

15
src/commands/about.js Normal file
View file

@ -0,0 +1,15 @@
const { Command } = require('discord-akairo');
class AboutCommand extends Command {
constructor() {
super('about', {
aliases: ['about']
});
}
exec(message) {
return message.util.send('I do things!');
}
}
module.exports = AboutCommand;

15
src/commands/help.js Normal file
View file

@ -0,0 +1,15 @@
const { Command } = require('discord-akairo');
class HelpCommand extends Command {
constructor() {
super('help', {
aliases: ['help']
});
}
exec(message) {
return message.util.send('Maybe later.');
}
}
module.exports = HelpCommand;

15
src/commands/ping.js Normal file
View file

@ -0,0 +1,15 @@
const { Command } = require('discord-akairo');
class PingCommand extends Command {
constructor() {
super('ping', {
aliases: ['ping']
});
}
exec(message) {
return message.util.send('Pong!');
}
}
module.exports = PingCommand;

12
src/languages/haskell.js Normal file
View file

@ -0,0 +1,12 @@
const Language = require('../struct/Language');
class Haskell extends Language {
constructor() {
super('haskell', {
highlight: 'hs',
aliases: ['haskell', 'hs']
});
}
}
module.exports = Haskell;

View file

@ -0,0 +1,23 @@
const Language = require('../struct/Language');
class JavaScript extends Language {
constructor() {
super('javaScript', {
highlight: 'js',
aliases: ['javaScript', 'js'],
options: {
harmony: () => true
}
});
}
runWith(options) {
if (options.has('harmony')) {
return { id: this.id, env: { EVAL_HARMONY: 'true' } };
}
return { id: this.id, env: {} };
}
}
module.exports = JavaScript;

12
src/languages/python.js Normal file
View file

@ -0,0 +1,12 @@
const Language = require('../struct/Language');
class Python extends Language {
constructor() {
super('python', {
highlight: 'py',
aliases: ['python', 'py']
});
}
}
module.exports = Python;

22
src/listeners/error.js Normal file
View file

@ -0,0 +1,22 @@
const { Listener } = require('discord-akairo');
class ErrorListener extends Listener {
constructor() {
super('error', {
emitter: 'commandHandler',
event: 'error',
category: 'commandHandler'
});
}
exec(message, err) {
message.util.send([
'An error occured:',
'```',
err.toString(),
'```'
]);
}
}
module.exports = ErrorListener;

View file

@ -0,0 +1,72 @@
const { Listener } = require('discord-akairo');
const fetch = require('node-fetch');
class MessageInvalidListener extends Listener {
constructor() {
super('messageInvalid', {
emitter: 'commandHandler',
event: 'messageInvalid',
category: 'commandHandler'
});
}
async exec(message) {
const parse = this.parseMessage(message);
if (!parse) {
return null;
}
const result = await this.client.languageHandler.evalCode(message, parse)
.catch(e => e.message) || '\n';
const output = `\`\`\`${parse.language.highlight}\n${result}\`\`\``;
if (output.length >= 2000) {
const key = await fetch('https://hastebin.com/documents', { method: 'POST', body: result })
.then(res => res.json())
.then(json => json.key);
return message.util.send(`https://hastebin.com/${key}.js`);
}
return message.util.send(output);
}
parseMessage(message) {
const regex1 = /^\s*>\s*(?:\[(.+?)\])?\s*```(.+?)\n([^]+)```\s*$/;
const regex2 = /^\s*>\s*(?:\[(.+?)\])?\s*`(.+?) \s*([^]+)`\s*$/;
const match = message.content.match(regex1) || message.content.match(regex2);
if (!match) {
return null;
}
const language = this.parseLanguage(match[2]);
if (!language) {
return null;
}
const code = match[3].trim();
const options = this.parseOptions(language, match[1] || '');
return { id: message.id, language, code, options };
}
parseLanguage(language) {
const match = this.client.languageHandler.findLanguage(language.trim());
if (!match) {
return null;
}
return match;
}
parseOptions(language, options) {
return new Map(options.split(';')
.map(opt => {
const [k, v = ''] = opt.split('=');
return [k.toLowerCase().trim(), v.trim()];
})
.filter(([key, value]) =>
Object.prototype.hasOwnProperty.call(language.options, key) && language.options[key](value)));
}
}
module.exports = MessageInvalidListener;

View file

@ -0,0 +1,50 @@
const { AkairoClient, CommandHandler, ListenerHandler } = require('discord-akairo');
const LanguageHandler = require('./LanguageHandler');
const path = require('path');
class CompilerClient extends AkairoClient {
constructor(config) {
super({
ownerID: config.owner
}, {
disabledEveryone: true,
disabledEvents: ['TYPING_START']
});
this.commandHandler = new CommandHandler(this, {
directory: path.join(__dirname, '../commands'),
prefix: '>',
allowMention: true,
commandUtil: true,
commandUtilLifetime: 3e5,
handleEdits: true
});
this.listenerHandler = new ListenerHandler(this, {
directory: path.join(__dirname, '../listeners')
});
this.languageHandler = new LanguageHandler(this, {
directory: path.join(__dirname, '../languages')
});
this.config = config;
}
start() {
this.commandHandler.useListenerHandler(this.listenerHandler);
this.listenerHandler.setEmitters({
commandHandler: this.commandHandler,
listenerHandler: this.listenerHandler,
languageHandler: this.languageHandler
});
this.commandHandler.loadAll();
this.listenerHandler.loadAll();
this.languageHandler.loadAll();
this.languageHandler.buildDocker();
return this.login(this.config.token);
}
}
module.exports = CompilerClient;

22
src/struct/Language.js Normal file
View file

@ -0,0 +1,22 @@
const { AkairoModule } = require('discord-akairo');
class Language extends AkairoModule {
constructor(id, {
category,
highlight,
aliases,
options = {}
} = {}) {
super(id, { category });
this.highlight = highlight;
this.aliases = aliases;
this.options = options;
}
runWith() {
return { id: this.id, env: {} };
}
}
module.exports = Language;

View file

@ -0,0 +1,107 @@
const { AkairoHandler } = require('discord-akairo');
const { Collection } = require('discord.js');
const Language = require('./Language');
const childProcess = require('child_process');
class LanguageHandler extends AkairoHandler {
constructor(client, {
directory,
classToHandle = Language,
extensions = ['.js', '.ts'],
automateCategories,
loadFilter
}) {
super(client, {
directory,
classToHandle,
extensions,
automateCategories,
loadFilter
});
this.aliases = new Collection();
}
register(language, filepath) {
super.register(language, filepath);
for (let alias of language.aliases) {
const conflict = this.aliases.get(alias.toLowerCase());
if (conflict) throw new TypeError(`Alias conflict of ${alias} between ${language.id} and ${conflict}`);
alias = alias.toLowerCase();
this.aliases.set(alias, language.id);
}
}
deregister(language) {
for (let alias of language.aliases) {
alias = alias.toLowerCase();
this.aliases.delete(alias);
}
super.deregister(language);
}
findLanguage(alias) {
return this.modules.get(this.aliases.get(alias.toLowerCase()));
}
buildDocker() {
for (const [, { id }] of this.modules) {
childProcess.execSync(`docker build -t "1computer1/comp_iler:${id}" ./docker/${id}`);
}
}
evalCode(message, { language, code, options }) {
return new Promise((resolve, reject) => {
const name = `comp_iler-${message.id}-${Date.now()}`;
const { id, env } = language.runWith(options);
const proc = childProcess.spawn('docker', [
'run', '--rm', `--name=${name}`,
'--net=none', '--cpus=0.5', '-m=256m',
...Object.entries(env).map(([k, v]) => `-e${k}=${v}`),
`1computer1/comp_iler:${id}`,
'/bin/sh', '/var/run/run.sh', code
]);
setTimeout(() => {
try {
if (process.platform === 'win32') {
childProcess.execSync(`docker kill --signal=9 ${name} >nul 2>nul`);
} else {
childProcess.execSync(`docker kill --signal=9 ${name} >/dev/null 2>/dev/nul`);
}
reject(new Error('Evaluation timed out'));
} catch (e) {
reject(e);
}
}, 10000);
let data = '';
proc.stdout.on('data', chunk => {
data += chunk;
});
proc.stderr.on('data', chunk => {
data += chunk;
});
proc.on('error', error => {
error.data = data;
reject(error);
});
proc.on('exit', status => {
if (status !== 0) {
reject(new Error(data));
} else {
resolve(data);
}
});
});
}
}
module.exports = LanguageHandler;