diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index 0a8a18b..e2d69cd 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -2,6 +2,37 @@
+
+
+
\ No newline at end of file
diff --git a/src/commands/code-interpreter/slash/RunCode.ts b/src/commands/code-interpreter/slash/RunCode.ts
new file mode 100644
index 0000000..465f6c2
--- /dev/null
+++ b/src/commands/code-interpreter/slash/RunCode.ts
@@ -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 {
+ 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 => {
+ 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 " +
+ "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);
+ }
+});
\ No newline at end of file
diff --git a/src/commands/code-interpreter/slash/SessionResult.ts b/src/commands/code-interpreter/slash/SessionResult.ts
new file mode 100644
index 0000000..5e33722
--- /dev/null
+++ b/src/commands/code-interpreter/slash/SessionResult.ts
@@ -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 {
+ 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))]
+ });
+ }
+})
\ No newline at end of file
diff --git a/src/commands/code-interpreter/utils/ApiTypes.ts b/src/commands/code-interpreter/utils/ApiTypes.ts
new file mode 100644
index 0000000..c60a615
--- /dev/null
+++ b/src/commands/code-interpreter/utils/ApiTypes.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/src/commands/code-interpreter/utils/RunnerUtils.ts b/src/commands/code-interpreter/utils/RunnerUtils.ts
new file mode 100644
index 0000000..7fb36ab
--- /dev/null
+++ b/src/commands/code-interpreter/utils/RunnerUtils.ts
@@ -0,0 +1,59 @@
+import {CreateRunnerResponse, GetRunnerDetails} from "./ApiTypes.js";
+import {EmbedBuilder} from "discord.js";
+
+export const extensions: Record = {
+ "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 {
+ 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 {
+ 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}`});
+}
\ No newline at end of file
diff --git a/src/modules/handlers/HandlerParameters.ts b/src/modules/handlers/HandlerParameters.ts
index 59668d3..a769061 100644
--- a/src/modules/handlers/HandlerParameters.ts
+++ b/src/modules/handlers/HandlerParameters.ts
@@ -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 {
handler: (this: TThis, ...params: any[]) => Promise | void;
@@ -8,7 +14,7 @@ export interface BaseParameters {
export interface CommandParameters
extends BaseParameters> {
- builder: SlashCommandBuilder;
+ builder: SlashCommandSubcommandsOnlyBuilder;
}
export interface EventParameters