diff --git a/package.json b/package.json index 9317a49..0bd496d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "check": "tsc" }, "dependencies": { + "@types/node": "^22.14.0", + "acorn": "^8.14.1", + "astring": "^1.9.0", "canvas": "^3.1.0", "discord.js": "^14.17.2", "sharp": "git+ssh://git@github.com/lovell/sharp.git", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e579bc..c65f982 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,6 +13,15 @@ importers: .: dependencies: + '@types/node': + specifier: ^22.14.0 + version: 22.14.0 + acorn: + specifier: ^8.14.1 + version: 8.14.1 + astring: + specifier: ^1.9.0 + version: 1.9.0 canvas: specifier: ^3.1.0 version: 3.1.0 @@ -24,7 +33,7 @@ importers: version: https://codeload.github.com/lovell/sharp/tar.gz/03e1b19764719d1b031045420223e78160db9bd0(patch_hash=aec393aef46aa4fa39e5c8d152bbe386c4b9df13f70cc0a46ee371547d7395dc) ts-node: specifier: ^10.9.2 - version: 10.9.2(@types/node@22.10.5)(typescript@5.7.2) + version: 10.9.2(@types/node@22.14.0)(typescript@5.7.2) zod: specifier: ^3.24.2 version: 3.24.2 @@ -214,8 +223,8 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/node@22.10.5': - resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} + '@types/node@22.14.0': + resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==} '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} @@ -228,14 +237,18 @@ packages: resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} + acorn@8.14.1: + resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==} engines: {node: '>=0.4.0'} hasBin: true arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -435,8 +448,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - undici-types@6.20.0: - resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} undici@6.19.8: resolution: {integrity: sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==} @@ -632,24 +645,26 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@types/node@22.10.5': + '@types/node@22.14.0': dependencies: - undici-types: 6.20.0 + undici-types: 6.21.0 '@types/ws@8.5.13': dependencies: - '@types/node': 22.10.5 + '@types/node': 22.14.0 '@vladfrangu/async_event_emitter@2.4.6': {} acorn-walk@8.3.4: dependencies: - acorn: 8.14.0 + acorn: 8.14.1 - acorn@8.14.0: {} + acorn@8.14.1: {} arg@4.1.3: {} + astring@1.9.0: {} + base64-js@1.5.1: {} bl@4.1.0: @@ -863,15 +878,15 @@ snapshots: ts-mixer@6.0.4: {} - ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2): + ts-node@10.9.2(@types/node@22.14.0)(typescript@5.7.2): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.10.5 - acorn: 8.14.0 + '@types/node': 22.14.0 + acorn: 8.14.1 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 @@ -889,7 +904,7 @@ snapshots: typescript@5.7.2: {} - undici-types@6.20.0: {} + undici-types@6.21.0: {} undici@6.19.8: {} diff --git a/src/commands/eval.ts b/src/commands/eval.ts new file mode 100644 index 0000000..f51439f --- /dev/null +++ b/src/commands/eval.ts @@ -0,0 +1,133 @@ +import { + ApplicationCommandType, + ContextMenuCommandBuilder, + ContextMenuCommandInteraction, escapeCodeBlock, + InteractionContextType, + Message, + Snowflake, + User, UserResolvable +} from "discord.js"; +import { parse as acornParse } from 'acorn' +import { ContextCommand } from "../command.ts"; +import {ModuleDeclaration, Statement} from "acorn"; +import {generate} from "astring"; +import {inspect} from "node:util"; + + +function transformLastInBlock( + array: Array) { + if (array) { + array[array.length - 1] = transformStatement(array[array.length - 1]) + } +} +function transformStatement( + ast: T): T | Statement { + switch (ast.type) { + case 'ExpressionStatement': + return { + type: 'ExpressionStatement', + start: 0, + end: 0, + expression: { + type: 'AssignmentExpression', + operator: '=', + start: 0, + end: 0, + left: { + start: 0, + end: 0, + type: 'Identifier', + name: '__ret' + }, + right: ast.expression + } + } + case 'BlockStatement': + transformLastInBlock(ast.body) + break + case 'ForStatement': + case "WhileStatement": + case 'ForOfStatement': + case 'ForInStatement': + case 'DoWhileStatement': + case 'WithStatement': + ast.body = transformStatement(ast.body) + break + case 'IfStatement': + ast.consequent = transformStatement(ast.consequent) + if (ast.alternate) + ast.alternate = transformStatement(ast.alternate) + break + } + + return ast +} + + +export default class Mock extends ContextCommand { + targetType: ApplicationCommandType.Message = ApplicationCommandType.Message; + contextDefinition: ContextMenuCommandBuilder = + new ContextMenuCommandBuilder() + .setName('eval') + .setType(ApplicationCommandType.Message) + async run(interaction: ContextMenuCommandInteraction, target: Message): Promise { + await interaction.deferReply(); + const match = target.content.match(/```js\n(.*?)```/s) + if (!match) { + await interaction.followUp("no codeblock found") + return + } + const code = match[1]; + let ast = acornParse(code, { + ecmaVersion: 2020, + allowReturnOutsideFunction: true, + allowAwaitOutsideFunction: true, + }) + if (ast.body) { + ast.body.unshift({ + type: 'VariableDeclaration', + kind: 'let', + end: 0, start: 0, + declarations: [{ + end: 0, start: 0, + id: { + start: 0, + end: 0, + type: 'Identifier', + name: '__ret' + }, + type: "VariableDeclarator" + }] + }) + transformLastInBlock(ast.body) + ast.body.push({ + type: 'ReturnStatement', + end: 0, start: 0, + argument: { + type: 'Identifier', + end: 0, + start: 0, + name: '__ret' + } + }) + } + const mappedCode = generate(ast) + + const bindings: { name: string, value: any }[] = [ + { name: 'add100', value: (x: number) => x + 100 }, + { name: 'client', value: interaction.client }, + { name: 'interaction', value: interaction }, + { name: 'getUser', value: (snowflake: UserResolvable) => interaction.client.users.fetch(snowflake) }, + ] + const func = new Function(...bindings.map(it => it.name), `"use strict";\nreturn (async () => {\n${mappedCode}\n})();`,) + + await interaction.editReply("running...") + let result + result = await func(...bindings.map(it => it.value)) + if (typeof result === "undefined") { + await interaction.editReply("result was undefined, did you forget to return?") + } else { + await interaction.editReply('```js\n' + escapeCodeBlock(inspect(result)) + "\n```") + } + } +} \ No newline at end of file