Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code interpreter #31

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

136 changes: 136 additions & 0 deletions src/commands/code-interpreter/slash/RunCode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import {SlashCommand} from "../../../modules/handlers/HandlerBuilders.js";
import {Attachment, EmbedBuilder, SlashCommandBuilder} from "discord.js";
import {CreateRunnerResponse, GetRunnerDetails} from "../utils/ApiTypes.js";
import {checkRunnerDetails, checkRunnerStatus, getResultEmbed, extensions} from "../utils/RunnerUtils.js";

export default new SlashCommand({
builder: new SlashCommandBuilder()
.setName("run-code")
.setDescription("Code running category")
.addSubcommand(c => c
.setName("from-file")
.setDescription("Run code from a file you upload")
.addAttachmentOption(o => o
.setName("file")
.setDescription("The file to run the code from, the language will be inferred from the file extension")
.setRequired(true)
)
.addStringOption(o => o
.setName("stdin")
.setDescription("Pass input to the code interpreter.")
)
)
.addSubcommand(c => c
.setName("from-input")
.setDescription("Run code directly from your command input")
.addStringOption(o => o
.setName("code")
.setDescription("The script to run.")
.setRequired(true)
)
.addStringOption(o => o
.setName("language")
.setDescription("The coding language the script is written on")
.setRequired(true)
.addChoices(
{name: "C | C17/clang10", value: "c"},
{name: "C++ | C++17/clang10", value: "cpp"},
{name: "Java | OpenJDK18", value: "java"},
{name: "Kotlin | 1.7.10/JRE18", value: "kotlin"},
{name: "Swift | 5.6.2", value: "swift"},
{name: "C# | Mono 6", value: "csharp"},
{name: "Go | 1.19", value: "go"},
{name: "Python 3 | 3.8.10", value: "python3"},
{name: "Ruby | 3.1.2p20", value: "ruby"},
{name: "PHP | 8.1.9 cli", value: "php"},
{name: "BASH | 5.0.17", value: "bash"},
{name: "RLang | 3.6.3", value: "r"},
{name: "JavaScript | NodeJS 16.17.0", value: "javascript"},
{name: "Visual Basic | Mono 6", value: "vb"},
{name: "BrainFuck", value: "brainfuck"},
{name: "Cobol | cobc 2.2.0", value: "cobol"},
{name: "F# | F# Interactive 4.0", value: "fsharp"},
{name: "Elixir | 1.12.3", value: "elixir"},
{name: "Rust | rustc 1.59.0", value: "rust"},
{name: "TypeScript | 4.8.2", value: "typescript"}
)
)
.addStringOption(o => o
.setName("stdin")
.setDescription("Pass input to the code interpreter.")
)
),

async handler(): Promise<void> {
let code: string;
let language: string;
const stdin: string | null = this.context.options.getString("stdin");

if (this.context.options.getSubcommand() === "from-file") {
const file: Attachment = this.context.options.getAttachment("file")!;
code = await fetch(file.url).then(f => f.text());

const splitName: string[] = file.name.split('.');

if (splitName.length < 2
|| !Object.keys(extensions).includes(splitName[splitName.length - 1])) {

const unknownLangEmbed: EmbedBuilder = new EmbedBuilder()
.setTitle("Invalid language")
.setDescription("Inferred language is not valid, valid languages include:\n\n```" +
Object.keys(extensions).map(e => `*.${e}`).join(", ") +
"```"
)
.setColor("#FF0000");

await this.context.reply({
embeds: [unknownLangEmbed],
ephemeral: true
});
return;
}

language = extensions[splitName[splitName.length - 1]];
} else {
code = this.context.options.getString("code")!;
language = this.context.options.getString("language")!;
}

await this.context.deferReply();

const createRunnerResult: CreateRunnerResponse = await fetch("http://api.paiza.io/runners/create?"
+ `source_code=${encodeURIComponent(code)}&`
+ `language=${encodeURIComponent(language)}&`
+ "api_key=guest&"
+ (stdin !== null ? `input=${encodeURIComponent(stdin)}` : ""),
{ method: "POST" }
)
.then(r => r.json());

setTimeout(async (): Promise<void> => {
const checkRunnerResult: CreateRunnerResponse = await checkRunnerStatus(createRunnerResult.id);

if (checkRunnerResult.status === "running") {
const timeoutEmbed: EmbedBuilder = new EmbedBuilder()
.setTitle("Long running session!")
.setDescription(
"Looks like this session is going to take some time," +
"but don't worry you can still use the </run-code get-output:1221193746209701920>" +
"command and check the output for this session once it's finished!"
)
.addFields(
{name: "Session ID", value: checkRunnerResult.id, inline: true},
{name: "Status", value: checkRunnerResult.status, inline: true}
)
.setColor("#FFA500");

await this.context.editReply({embeds: [timeoutEmbed]});
return;
}

const getRunnerResult: GetRunnerDetails = await checkRunnerDetails(createRunnerResult.id);

await this.context.editReply({embeds: [getResultEmbed(getRunnerResult)]});
}, 3000);
}
});
49 changes: 49 additions & 0 deletions src/commands/code-interpreter/slash/SessionResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {SlashCommand} from "../../../modules/handlers/HandlerBuilders.js";
import {EmbedBuilder, SlashCommandBuilder} from "discord.js";
import {CreateRunnerResponse} from "../utils/ApiTypes.js";
import {checkRunnerDetails, checkRunnerStatus, getResultEmbed} from "../utils/RunnerUtils.js";

export default new SlashCommand({
builder: new SlashCommandBuilder()
.setName("code-session")
.setDescription("Get data from code sessions")
.addSubcommand(c => c
.setName("get-result")
.setDescription("Get result from a long running session")
.addStringOption(o => o
.setName("id")
.setDescription("The session ID")
.setRequired(true)
)
),

async handler(): Promise<void> {
await this.context.deferReply();
const sessionId: string = this.context.options.getString("id")!;

const status: CreateRunnerResponse = await checkRunnerStatus(sessionId);

if (status.status === "running") {
const timeoutEmbed: EmbedBuilder = new EmbedBuilder()
.setTitle("On it!")
.setDescription(
"This runner is still running," +
"please, wait and run this command again in a few seconds"
)
.addFields(
{name: "Session ID", value: status.id, inline: true},
{name: "Status", value: status.status, inline: true}
)
.setColor("#FFA500")

await this.context.editReply({
embeds: [timeoutEmbed]
});
return;
}

await this.context.editReply({
embeds: [getResultEmbed(await checkRunnerDetails(sessionId))]
});
}
})
19 changes: 19 additions & 0 deletions src/commands/code-interpreter/utils/ApiTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

export interface CreateRunnerResponse {
id: string;
status: "running" | "completed";
}

export interface GetRunnerDetails {
id: string;

build_stderr: string | null;
build_stdout: string | null;
build_exit_code: number;
build_result: "success" | "failure" | "error";

stdout: string | null;
stderr: string | null;
result: "success" | "failure" | "error";
exit_code: number;
}
59 changes: 59 additions & 0 deletions src/commands/code-interpreter/utils/RunnerUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {CreateRunnerResponse, GetRunnerDetails} from "./ApiTypes.js";
import {EmbedBuilder} from "discord.js";

export const extensions: Record<string, string> = {
"c": "c",
"cpp": "cpp",
"java": "java",
"kt": "kotlin",
"swift": "swift",
"cs": "csharp",
"go": "go",
"py": "python3",
"rb": "ruby",
"php": "php",
"sh": "bash",
"r": "r",
"js": "javascript",
"vb": "vb",
"bf": "brainfuck",
"cob": "cobol",
"fs": "fsharp",
"ex": "elixir",
"rs": "rust",
"ts": "typescript"
}

export async function checkRunnerDetails(id: string): Promise<GetRunnerDetails> {
return await fetch(`http://api.paiza.io/runners/get_details?id=${encodeURIComponent(id)}&api_key=guest`)
.then(r => r.json());
}

export async function checkRunnerStatus(id: string): Promise<CreateRunnerResponse> {
return await fetch(`http://api.paiza.io/runners/get_status?id=${encodeURIComponent(id)}&api_key=guest`)
.then(r => r.json());
}

export function getResultEmbed(details: GetRunnerDetails): EmbedBuilder {
if (details.build_result !== "success" && details.build_result !== null) {
return new EmbedBuilder()
.setTitle("Build error")
.setDescription(`Session ID: \`${details.id}\`\n\`\`\`${details.build_stderr ?? details.build_stdout}\`\`\``)
.setColor("#FF0000")
.setFooter({text: `Exited with code: ${details.build_exit_code}`});
}

if (details.result !== "success") {
return new EmbedBuilder()
.setTitle("Runtime error")
.setDescription(`Session ID: \`${details.id}\`\n\`\`\`${details.stderr ?? details.stdout}\`\`\``)
.setColor("#FF0000")
.setFooter({text: `Exited with code: ${details.exit_code}`});
}

return new EmbedBuilder()
.setTitle("Success")
.setDescription(`Session ID: \`${details.id}\`\n\`\`\`${details.stdout}\`\`\``)
.setColor("#00B000")
.setFooter({text: `Exited with code: ${details.exit_code}`});
}
12 changes: 9 additions & 3 deletions src/modules/handlers/HandlerParameters.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import {Events, Interaction, SlashCommandBuilder} from "discord.js";
import {BaseContext, ButtonContext, CommandContext, EventContext, SelectMenuContext} from "./HandlerContext.js";
import {Events, Interaction, SlashCommandSubcommandsOnlyBuilder} from "discord.js";
import {
BaseContext,
ButtonContext,
CommandContext,
EventContext,
SelectMenuContext
} from "./HandlerContext.js";

export interface BaseParameters<TThis extends BaseContext> {
handler: (this: TThis, ...params: any[]) => Promise<void> | void;
Expand All @@ -8,7 +14,7 @@ export interface BaseParameters<TThis extends BaseContext> {
export interface CommandParameters<TInteraction extends Interaction>
extends BaseParameters<CommandContext<TInteraction>> {

builder: SlashCommandBuilder;
builder: SlashCommandSubcommandsOnlyBuilder;
}

export interface EventParameters
Expand Down
Loading