Initial commit
This commit is contained in:
commit
07ec57a453
27 changed files with 1853 additions and 0 deletions
15
src/commands/about.js
Normal file
15
src/commands/about.js
Normal 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
15
src/commands/help.js
Normal 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
15
src/commands/ping.js
Normal 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
12
src/languages/haskell.js
Normal 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;
|
23
src/languages/javascript.js
Normal file
23
src/languages/javascript.js
Normal 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
12
src/languages/python.js
Normal 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
22
src/listeners/error.js
Normal 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;
|
72
src/listeners/messageInvalid.js
Normal file
72
src/listeners/messageInvalid.js
Normal 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;
|
50
src/struct/CompilerClient.js
Normal file
50
src/struct/CompilerClient.js
Normal 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
22
src/struct/Language.js
Normal 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;
|
107
src/struct/LanguageHandler.js
Normal file
107
src/struct/LanguageHandler.js
Normal 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;
|
Loading…
Add table
Add a link
Reference in a new issue