Skip to content

Commit 0d0f97d

Browse files
committed
/run Bug fix + Improvements
- Can queue requests, previously crashed on simultaneous requests by single or multiple users - If a user triggers multiple /run instances, only the latest request will be persisted and all previous PENDING requests by that user will be destroyed - Fixed compiler/interpreter conflicts
1 parent 7cdaecf commit 0d0f97d

1 file changed

Lines changed: 85 additions & 46 deletions

File tree

commands/run.js

Lines changed: 85 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,56 +21,25 @@ const langs = {
2121
'TypeScript':'typescript-3.9.5',
2222
};
2323

24-
module.exports = {
25-
data: new SlashCommandBuilder()
26-
.setName('run')
27-
.setDescription('👩‍💻Runs your <code/>;')
28-
.addStringOption(option => {
29-
option
30-
.setName('language')
31-
.setDescription('Select programming language')
32-
.setRequired(true);
33-
for (const [lang, val] of Object.entries(langs)) {
34-
option.addChoice(`${lang}`, `${val}`);
35-
}
36-
return option;
37-
}),
38-
async execute(interaction) {
39-
const lang = interaction.options.getString('language');
40-
await interaction.reply({ content: 'Send your code in this channel enclosed in a code block. Request expires in 15 minutes!', ephemeral: true });
41-
// Check if the message is a codeblock or not
42-
const filter = msg => msg.content.startsWith('```') && msg.content.endsWith('```');
43-
// Create collector to collect multi-line input via codeblock
44-
// Waits for codeblock reply. Ends collector after 15 minutes if no response is received
45-
const collector = interaction.channel.createMessageCollector({ max: 1, filter, time: 900000 });
46-
47-
collector.on('collect', async message => {
48-
const code = format(message.content);
49-
const output = await run(code, lang);
50-
await interaction.followUp({
51-
content: `[Source code](${message.url}) by ${interaction.user}`,
52-
embeds : [embed(output)],
53-
components: [link(output.url)] });
54-
});
55-
56-
collector.on('end', async (collected) => {
57-
console.log(`Collected ${collected.size} items`);
58-
if (collected.size == 0) await interaction.followUp({ content: 'Token expired', ephemreal: true });
59-
});
60-
},
61-
};
24+
// Version of selected language
25+
// This object is honestly a bad addition, but djs has left no other way to access OptionChoice `name` directly
26+
const versions = {};
27+
Object.keys(langs).forEach(key => {
28+
versions[langs[key]] = key;
29+
});
6230

6331
// Parses code from code block
6432
const format = code => {
6533
const start = code.indexOf('\n') + 1;
6634
const end = code.lastIndexOf('\n');
6735
return code.slice(start, end);
6836
};
37+
6938
// POST code to wandbox API
70-
const run = async (code, lang) => {
39+
const run = async (code, version) => {
7140
const form = {
7241
'code':code,
73-
'compiler': lang,
42+
'compiler': version,
7443
'save':true,
7544
};
7645
const headers = {
@@ -84,11 +53,17 @@ const run = async (code, lang) => {
8453
};
8554

8655
// EMBED CONSTRUCTORS
87-
const embed = output => new MessageEmbed()
88-
.setColor('#15e854')
89-
.setTitle('OUTPUT')
90-
.setDescription(`\`\`\`\n${output.program_message}\`\`\``)
91-
.setFooter(`Exit Status Code: ${output.status}`);
56+
const embed = (output, lang) => {
57+
let out = output.program_message;
58+
if (out == undefined) out = output.compiler_message;
59+
return new MessageEmbed()
60+
.setColor('#15e854')
61+
.setTitle('output')
62+
.setDescription(`\`\`\`\n${out}\`\`\``)
63+
.addFields(
64+
{ name: 'language', value: `${lang}` })
65+
.setFooter(`Exit Status Code: ${output.status}`);
66+
};
9267

9368
// BUTTON CONSTRUCTORS
9469
const link = url => new MessageActionRow()
@@ -97,4 +72,68 @@ const link = url => new MessageActionRow()
9772
.setLabel('View/Edit 👩🏻‍💻 code in browser')
9873
.setURL(url)
9974
.setStyle('LINK'),
100-
);
75+
);
76+
77+
// Active Collectors array, stores userIds with active collectors, follow further code to understand
78+
const active = new Map();
79+
80+
module.exports = {
81+
data: new SlashCommandBuilder()
82+
.setName('run')
83+
.setDescription('👩‍💻Runs your <code/>;')
84+
.addStringOption(option => {
85+
option
86+
.setName('language')
87+
.setDescription('Select programming language')
88+
.setRequired(true);
89+
for (const [lang, version] of Object.entries(langs)) {
90+
option.addChoice(`${lang}`, `${version}`);
91+
}
92+
return option;
93+
}),
94+
async execute(interaction) {
95+
96+
// options.getString('language') will actually return option choice `value`, and not choice `name`
97+
// https://discordjs.guide/interactions/replying-to-slash-commands.html#parsing-options
98+
const version = interaction.options.getString('language');
99+
const lang = versions[version];
100+
const user = interaction.user.id;
101+
102+
// prompt user to send code
103+
await interaction.reply({ content: `Send your **${lang}** code enclosed in a code block. Request expires in 15 minutes!`, ephemeral: true });
104+
105+
// Check if any received message by user is a codeblock, if not wait for reply until timer ends
106+
// `filter` returns a boolean, code will be processed only when it returns True
107+
function filter(message) {
108+
return (message.author.id == user) &&
109+
message.content.startsWith('```') && message.content.endsWith('```');
110+
}
111+
112+
// Collector is created when /run command is triggered
113+
114+
// Check if user has already created a collector
115+
// if yes, destroy previous collector
116+
const activeCollector = active.get(user);
117+
if (activeCollector) activeCollector.stop();
118+
119+
// Create new collector to collect multi-line input via codeblock
120+
// Waits for codeblock reply. Ends collector after 15 minutes if no response is received
121+
const collector = interaction.channel.createMessageCollector({ max: 1, filter, time: 900000 });
122+
active.set(user, collector);
123+
124+
collector.on('collect', async message => {
125+
const code = format(message.content);
126+
const output = await run(code, version);
127+
await message.reply({
128+
embeds : [embed(output, lang)],
129+
components: [link(output.url)] });
130+
});
131+
132+
collector.on('end', async (collected) => {
133+
active.delete(user);
134+
const tag = interaction.user.tag;
135+
if (collected.size == 0) console.log(`${tag}'s /run request expired!`);
136+
else console.log(`${tag}'s /run request processed!`);
137+
});
138+
},
139+
};

0 commit comments

Comments
 (0)