@mincraft/cli 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +5 -3
- package/package.json +3 -2
- package/src/cli.ts +24 -4
- package/src/exec.ts +73 -0
- package/src/index.ts +2 -0
- package/src/logger.ts +29 -3
- package/src/macro.ts +24 -9
- package/src/repl.ts +137 -0
- package/src/runner.ts +0 -106
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{Option as
|
|
3
|
-
`);if(e.length===1)this.logger.log(
|
|
2
|
+
import{Option as O,program as l}from"commander";function F(t){let o=[],e="",r=0;for(;r<t.length;)t[r]==="\\"&&r+1<t.length?(e+=t[r]+t[r+1],r+=2):t[r]===","?(o.push(e),e="",r++):(e+=t[r],r++);return o.push(e),o}function T(t){let o="",e=0;for(;e<t.length;)if(t[e]==="\\"&&e+1<t.length){let r=t[e+1];r===","||r==="{"||r==="}"?(o+=r,e+=2):(o+=t[e],e++)}else o+=t[e],e++;return o}function v(t){let o=[],e=F(t),r=/^\{\{(\d+(?:\.\d+)?)\}\}$/;for(let n of e){let s=n.trim();if(!s)continue;let a=s.match(r);if(a){let i=parseFloat(a[1]),c=Math.round(i*1e3);o.push({command:"",delay:c})}else{let i=T(s);o.push({command:i,delay:0})}}return o}import*as I from"readline";import p from"chalk";import{createBot as A}from"mineflayer";import{SocksClient as D}from"socks";var u=class{proxy;client;server;options;logger;isLoggedIn;bot;constructor({proxy:o,client:e,server:r,options:n},s){this.proxy=o,this.client=e,this.server=r,this.options=n,this.logger=s,this.isLoggedIn=!1}async registerAndLogIn(){let o={host:this.server.host,port:this.server.port,username:this.client.account.email,auth:this.client.account.auth,version:this.client.version,hideErrors:!0};this.proxy&&(o.connect=e=>{this.connectViaProxy(e)}),this.bot=A(o),this.options.verboseLogging&&this.registerClientEvents(),this.registerBotEvents()}connectViaProxy(o){this.logger.log("connecting via proxy");let{host:e,port:r,user:n,pass:s,type:a}=this.proxy,i=this.server;D.createConnection({proxy:{host:e,port:r,type:a,userId:n,password:s},command:"connect",destination:{host:i.host,port:i.port},timeout:3e4}).then(c=>{o.setSocket(c.socket),o.emit("connect")}).catch(c=>{this.logger.error(`proxy connection failed: ${c.message}`),o.emit("error",c)})}registerClientEvents(){this.bot._client.on("connect",()=>{this.logger.log("TCP connection established")}),this.bot._client.on("login",()=>{this.logger.log("login packet received")}),this.bot._client.on("error",o=>{this.logger.error(`client error: ${o.message}`)}),this.bot._client.on("end",o=>{this.logger.warn(`client ended: ${o}`)})}registerBotEvents(){this.bot.once("spawn",()=>{let{ign:o,uuid:e}=this.client.account;this.logger.log(`logged in as ${o} (${e})`),this.isLoggedIn=!0}),this.bot.once("kicked",o=>{this.logger.warn(`kicked: ${JSON.stringify(o)}`),this.disconnect()}),this.bot.once("error",o=>{this.logger.error(`bot error: ${o.message}`),this.isLoggedIn=!1}),this.bot.once("end",o=>{this.logger.warn(`disconnected by server: ${JSON.stringify(o)}`),this.disconnect()}),this.options.logMessages&&this.bot.on("messagestr",o=>{let e=o.split(`
|
|
3
|
+
`);if(e.length===1)this.logger.log(o,"chat");else{this.logger.log(e[0],"chat");for(let r=1;r<e.length;r++)this.logger.raw(` ${e[r]}`)}})}disconnect(){this.bot&&(this.proxy?this.bot._client.socket?.destroy():this.bot.quit(),this.isLoggedIn=!1)}sendMessage(o){this.bot.chat(o)}};import*as y from"readline";import h from"chalk";function U(t){return t?`/${t.split("/").map((r,n)=>n===0?r==="err"?h.red(r):r==="warn"?h.yellow(r):h.gray(r):h.blue(r)).join("/")}`:""}function m(t,o){let e=(n,s="")=>{let a=o?s?`${o}/${s}`:o:s;return`[mc${U(a)}] ${n}`},r=n=>{t?(y.clearLine(process.stdout,0),y.cursorTo(process.stdout,0),console.log(n),t.prompt(!0)):console.log(n)};return{log:(n,s)=>r(e(n,s)),warn:(n,s="warn")=>r(e(n,s)),error:(n,s="err")=>r(e(n,s)),raw:r}}import*as f from"fs/promises";import*as d from"path";import*as C from"esbuild";async function j(t){let e=(await C.build({entryPoints:[t],bundle:!0,write:!1,platform:"node",format:"esm",target:"es2022",loader:{". ts":"ts"},external:["mineflayer"],logLevel:"silent"})).outputFiles[0].text,r=t.replace(/\.ts$/,". macro.mjs");return await f.writeFile(r,e),r}async function B(t){try{await f.unlink(t)}catch{}}function N(t){return d.isAbsolute(t)?t:d.resolve(process.cwd(),t)}async function R(t){try{return await f.access(t),!0}catch{return!1}}function _(t){return d.basename(t).replace(/\.(ts|js|mjs)$/,"")}async function $(t,o,e){let r=N(t),n=r,s=null,a=m(e,`macro/${_(t)}`);try{if(!await R(r))return a.error(`file not found: ${r}`),!1;r.endsWith(".ts")&&(a.log("compiling macro to js"),s=await j(r),n=s);let g=await import(`file://${n}?t=${Date.now()}`);return typeof g.default!="function"?(a.error("macro file must export a default function"),!1):(a.log("running"),await g.default({bot:o,logger:a}),a.log("finished"),!0)}catch(i){return a.error(`failed: ${i.message}`),!1}finally{s&&await B(s)}}var E=(s=>(s.Exit=".exit",s.Logout=".lo",s.Login=".li",s.Macro=".macro",s.Help=".help",s))(E||{});function L(t){return Object.values(E).some(o=>t.startsWith(o))}function H(t,o){t(`type ${p.magenta(".li")} to log in to ${o}`),t(`type ${p.magenta(".lo")} to log out from ${o}`),t(`type ${p.magenta(".macro <filepath>")} to run a macro`),t(`type ${p.magenta(".help")} to show this REPL reference`),t(`type ${p.magenta(".exit")} to exit the REPL`),t(`type ${p.bold("any other value")} to send a message to the server`)}async function P(t,o){process.stdout.write("\x1B[2J\x1B[2;0H");let e=null,r=I.createInterface({input:process.stdin,output:process.stdout}),n=m(r),s=i=>n.raw(`>> ${i}`);r.setPrompt("> ");let a=async i=>{if(i=i.trim(),!i)return!1;if(!L(i)&&!e?.isLoggedIn)return n.error("log in using .li before sending messages"),!1;if(L(i)){let[c,...g]=i.split(" ");switch(c){case".exit":return e?.disconnect(),r.close(),!0;case".li":{e?.isLoggedIn?n.error("client is already logged in"):(e=new u(t,n),e.registerAndLogIn());break}case".lo":{e?.isLoggedIn?e?.disconnect():n.error("client isn't logged in");break}case".macro":{if(!e?.isLoggedIn){n.error("client must be logged in to run macros");break}let b=g.join(" ");if(!b){n.error("usage: .macro <filepath>");break}let w=e.bot;w&&await $(b,w,r);break}case".help":{H(s,t.server.host);break}}}else e?.sendMessage(i);return!1};if(r.on("line",async i=>{await a(i)&&process.exit(0),r.prompt()}),console.log(p.green(`mincraft REPL ${M}`)),s('type ".help" for more information.'),o&&o.length>0)for(let{command:i,delay:c}of o)c>0&&await new Promise(g=>setTimeout(g,c)),i&&(n.raw(`> ${i}`),await a(i)&&process.exit(0));r.prompt()}l.name("mincraft").description("Mineflayer CLI tool with supports for macros and proxies.").version("1.0.0").addHelpText("after",`
|
|
4
4
|
Examples:
|
|
5
5
|
$ mincraft mc.hypixel.net 1.21.4 --ign FuriousDestroyer --email you@example.com
|
|
6
6
|
$ mincraft mythic.gg 1.7.10 -p 58585 --ign MangoSyrup --email you@example.com --prox proxy.com:1234:mango:secret
|
|
7
|
-
|
|
7
|
+
$ mincraft mc.hypixel.net 1.21.4 --ign Player --email you@example.com --exec "{{1}},.li,{{2.5}},hello,{{0.5}},.lo"
|
|
8
|
+
$ mincraft mc.hypixel.net 1.21.4 --ign Player --email you@example.com --exec "{{1.5}},.li,{{5}},hello\\,world,{{0.5}},.lo"
|
|
9
|
+
`);l.argument("<host>","Server hostname").option("-p, --port <PORT>","Server port",t=>parseInt(t,10),25565);l.argument("<version>","Client version, e.g. 1.21.4");var S=["host","port","user","pass"],k=":";l.option("--prox <HOST:PORT:USER:PASS>","Connect to the server with a proxy").option("--prox-field-order <FORMAT>",'The order of the proxy credential fields, e.g. "user,pass,host,port".').option("--prox-field-sep <SEP>","The separator of the proxy credential fields").option("--prox-type <4|5>","The SOCKS proxy type",t=>parseInt(t,10),5);var G="microsoft";l.addOption(new O("--ign <IN-GAME NAME>","Player username").conflicts("uuid")).addOption(new O("--uuid <UUID>","Player UUID").conflicts("ign")).requiredOption("--email <EMAIL>","Account email").option("--auth <AUTH>","Account authentication method",G);l.option("--no-log-messages","Do not log messages your client receives").option("--verbose","Enable additional logging messages").option("--exec <COMMANDS>",'Execute REPL commands on startup with optional delays, e.g. "{{1}},.li,{{2.5}},hello,{{0.5}},.lo", where {{N}} represents a delay. You can escape curly braces and commas to include them in commands/messages.',(t,o)=>o.concat(t),[]);l.action(async(t,o,e)=>{if(!e.ign&&!e.uuid&&l.error("error: must specify either --ign or --uuid"),e.proxFieldOrder){let s=e.proxyFieldSep??k;e.proxFieldOrder.split(s).every(i=>S.includes(i))||l.error("error: --prox-field-order includes invalid fields")}let r=await W(l),n=[];for(let s of e.exec){let a=v(s);n.push(...a)}await P(r,n.length>0?n:void 0)});async function J(t){let e=await(await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${t}`)).json();if(!e?.name)throw new Error("Could not fetch IGN from UUID");return e.name}async function q(t){let e=await(await fetch(`https://api.mojang.com/users/profiles/minecraft/${t}`)).json();if(!e?.id)throw new Error("Could not fetch UUID from IGN");return e.id}function V(t,o,e){let r=t.split(e),n=Object.fromEntries(o.map((s,a)=>[s,r[a]]));return n.port=+n.port,n}async function W(t){let o=t.args,e=t.opts();return{server:{host:o[0],port:e.port},client:{version:o[1],account:{auth:e.auth,uuid:e.uuid??await q(e.ign),ign:e.ign??await J(e.uuid),email:e.email}},...e.prox?{proxy:{...V(e.prox,e.proxFieldOrder??S,e.proxFieldSep??k),type:e.proxType}}:{},options:{logMessages:e.noLogMessages??!0,verboseLogging:e.verbose??!1}}}var x=m(),de=x.log,ue=x.warn,he=x.error,M="v1.4.0";l.parse();export{x as defaultLogger,he as error,de as log,M as version,ue as warn};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mincraft/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Mineflayer CLI tool with support for macros and proxies",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mincraft": "./dist/index.mjs"
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
],
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@inquirer/prompts": "^8.1.0",
|
|
16
|
+
"chalk": "^5.6.2",
|
|
16
17
|
"commander": "^14.0.2",
|
|
17
18
|
"esbuild": "^0.27.2",
|
|
18
19
|
"mineflayer": "^4.33.0",
|
|
19
20
|
"socks": "^2.8.7",
|
|
20
|
-
"@mincraft/types": "1.
|
|
21
|
+
"@mincraft/types": "1.4.0"
|
|
21
22
|
},
|
|
22
23
|
"devDependencies": {
|
|
23
24
|
"@types/debug": "^4.1.12",
|
package/src/cli.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Config,
|
|
3
|
+
DelayableCommands,
|
|
4
|
+
ProxyCredentials,
|
|
5
|
+
} from "@mincraft/types";
|
|
2
6
|
import { type Command, Option, program } from "commander";
|
|
3
|
-
import {
|
|
7
|
+
import { parseExecCommands } from "./exec";
|
|
8
|
+
import { run } from "./repl";
|
|
4
9
|
|
|
5
10
|
// meta
|
|
6
11
|
program
|
|
@@ -13,6 +18,8 @@ program
|
|
|
13
18
|
Examples:
|
|
14
19
|
$ mincraft mc.hypixel.net 1.21.4 --ign FuriousDestroyer --email you@example.com
|
|
15
20
|
$ mincraft mythic.gg 1.7.10 -p 58585 --ign MangoSyrup --email you@example.com --prox proxy.com:1234:mango:secret
|
|
21
|
+
$ mincraft mc.hypixel.net 1.21.4 --ign Player --email you@example.com --exec "{{1}},.li,{{2.5}},hello,{{0.5}},.lo"
|
|
22
|
+
$ mincraft mc.hypixel.net 1.21.4 --ign Player --email you@example.com --exec "{{1.5}},.li,{{5}},hello\\,world,{{0.5}},.lo"
|
|
16
23
|
`,
|
|
17
24
|
);
|
|
18
25
|
|
|
@@ -66,7 +73,14 @@ program
|
|
|
66
73
|
// extra options
|
|
67
74
|
program
|
|
68
75
|
.option("--no-log-messages", "Do not log messages your client receives")
|
|
69
|
-
.option("--verbose", "Enable additional logging messages")
|
|
76
|
+
.option("--verbose", "Enable additional logging messages")
|
|
77
|
+
.option(
|
|
78
|
+
"--exec <COMMANDS>",
|
|
79
|
+
'Execute REPL commands on startup with optional delays, e.g. "{{1}},.li,{{2.5}},hello,{{0.5}},.lo", where {{N}} represents a delay.' +
|
|
80
|
+
" You can escape curly braces and commas to include them in commands/messages.",
|
|
81
|
+
(val: string, prev: string[]) => prev.concat(val),
|
|
82
|
+
[],
|
|
83
|
+
);
|
|
70
84
|
|
|
71
85
|
program.action(async (_host, _version, options) => {
|
|
72
86
|
if (!options.ign && !options.uuid) {
|
|
@@ -83,7 +97,13 @@ program.action(async (_host, _version, options) => {
|
|
|
83
97
|
}
|
|
84
98
|
|
|
85
99
|
const config = await getConfig(program);
|
|
86
|
-
|
|
100
|
+
|
|
101
|
+
const commands: DelayableCommands = [];
|
|
102
|
+
for (const cmd of options.exec) {
|
|
103
|
+
const parsed = parseExecCommands(cmd);
|
|
104
|
+
commands.push(...parsed);
|
|
105
|
+
}
|
|
106
|
+
await run(config, commands.length > 0 ? commands : undefined);
|
|
87
107
|
});
|
|
88
108
|
|
|
89
109
|
async function getIGN(uuid: string) {
|
package/src/exec.ts
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { DelayableCommands } from "@mincraft/types";
|
|
2
|
+
|
|
3
|
+
function splitByUnescapedCommas(input: string) {
|
|
4
|
+
const parts: string[] = [];
|
|
5
|
+
let current = "";
|
|
6
|
+
let i = 0;
|
|
7
|
+
|
|
8
|
+
while (i < input.length) {
|
|
9
|
+
if (input[i] === "\\" && i + 1 < input.length) {
|
|
10
|
+
current += input[i] + input[i + 1];
|
|
11
|
+
i += 2;
|
|
12
|
+
} else if (input[i] === ",") {
|
|
13
|
+
parts.push(current);
|
|
14
|
+
current = "";
|
|
15
|
+
i++;
|
|
16
|
+
} else {
|
|
17
|
+
current += input[i];
|
|
18
|
+
i++;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
parts.push(current);
|
|
22
|
+
|
|
23
|
+
return parts;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function unescapeString(input: string) {
|
|
27
|
+
let result = "";
|
|
28
|
+
let i = 0;
|
|
29
|
+
|
|
30
|
+
while (i < input.length) {
|
|
31
|
+
if (input[i] === "\\" && i + 1 < input.length) {
|
|
32
|
+
const nextChar = input[i + 1];
|
|
33
|
+
if (nextChar === "," || nextChar === "{" || nextChar === "}") {
|
|
34
|
+
result += nextChar;
|
|
35
|
+
i += 2;
|
|
36
|
+
} else {
|
|
37
|
+
result += input[i];
|
|
38
|
+
i++;
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
result += input[i];
|
|
42
|
+
i++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseExecCommands(input: string) {
|
|
50
|
+
const result: DelayableCommands = [];
|
|
51
|
+
const parts = splitByUnescapedCommas(input);
|
|
52
|
+
|
|
53
|
+
const delayPattern = /^\{\{(\d+(?:\.\d+)?)\}\}$/;
|
|
54
|
+
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
const trimmed = part.trim();
|
|
57
|
+
if (!trimmed) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const delayMatch = trimmed.match(delayPattern);
|
|
62
|
+
if (delayMatch) {
|
|
63
|
+
const delaySeconds = parseFloat(delayMatch[1]);
|
|
64
|
+
const delayMs = Math.round(delaySeconds * 1000);
|
|
65
|
+
result.push({ command: "", delay: delayMs });
|
|
66
|
+
} else {
|
|
67
|
+
const command = unescapeString(trimmed);
|
|
68
|
+
result.push({ command, delay: 0 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return result;
|
|
73
|
+
}
|
package/src/index.ts
CHANGED
package/src/logger.ts
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
1
|
import * as readline from "node:readline";
|
|
2
2
|
import type { Logger } from "@mincraft/types";
|
|
3
|
+
import chalk from "chalk";
|
|
3
4
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
function formatScope(fullScope: string) {
|
|
6
|
+
if (!fullScope) return "";
|
|
7
|
+
|
|
8
|
+
const parts = fullScope.split("/");
|
|
9
|
+
const colored = parts.map((part, i) => {
|
|
10
|
+
if (i === 0) {
|
|
11
|
+
if (part === "err") return chalk.red(part);
|
|
12
|
+
if (part === "warn") return chalk.yellow(part);
|
|
13
|
+
return chalk.gray(part);
|
|
14
|
+
}
|
|
15
|
+
return chalk.blue(part);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
return `/${colored.join("/")}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function createLogger(
|
|
22
|
+
rl?: readline.Interface,
|
|
23
|
+
baseScope?: string,
|
|
24
|
+
): Logger {
|
|
25
|
+
const format = (msg: string, scope = "") => {
|
|
26
|
+
const fullScope = baseScope
|
|
27
|
+
? scope
|
|
28
|
+
? `${baseScope}/${scope}`
|
|
29
|
+
: baseScope
|
|
30
|
+
: scope;
|
|
31
|
+
return `[mc${formatScope(fullScope)}] ${msg}`;
|
|
32
|
+
};
|
|
7
33
|
|
|
8
34
|
const write = (msg: string) => {
|
|
9
35
|
if (rl) {
|
package/src/macro.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import type {
|
|
3
|
+
import type { Interface } from "node:readline";
|
|
4
|
+
import type { MacroModule } from "@mincraft/types";
|
|
4
5
|
import * as esbuild from "esbuild";
|
|
6
|
+
import type { Bot } from "mineflayer";
|
|
7
|
+
import { createLogger } from "./logger";
|
|
5
8
|
|
|
6
9
|
async function compileToJS(filepath: string) {
|
|
7
10
|
const result = await esbuild.build({
|
|
@@ -11,13 +14,13 @@ async function compileToJS(filepath: string) {
|
|
|
11
14
|
platform: "node",
|
|
12
15
|
format: "esm",
|
|
13
16
|
target: "es2022",
|
|
14
|
-
loader: { ".ts": "ts" },
|
|
17
|
+
loader: { ". ts": "ts" },
|
|
15
18
|
external: ["mineflayer"],
|
|
16
19
|
logLevel: "silent",
|
|
17
20
|
});
|
|
18
21
|
|
|
19
22
|
const jsCode = result.outputFiles[0].text;
|
|
20
|
-
const jsFilePath = filepath.replace(/\.ts$/, ".macro.mjs");
|
|
23
|
+
const jsFilePath = filepath.replace(/\.ts$/, ". macro.mjs");
|
|
21
24
|
await fs.writeFile(jsFilePath, jsCode);
|
|
22
25
|
|
|
23
26
|
return jsFilePath;
|
|
@@ -45,21 +48,31 @@ async function fileExists(filepath: string) {
|
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
|
|
48
|
-
|
|
51
|
+
function getMacroName(filepath: string) {
|
|
52
|
+
return path.basename(filepath).replace(/\.(ts|js|mjs)$/, "");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function loadAndRunMacro(
|
|
56
|
+
filepath: string,
|
|
57
|
+
bot: Bot,
|
|
58
|
+
rl?: Interface,
|
|
59
|
+
) {
|
|
49
60
|
const absolutePath = resolveMacroPath(filepath);
|
|
50
61
|
let importPath = absolutePath;
|
|
51
62
|
let compiledPath: string = null;
|
|
52
63
|
|
|
64
|
+
const logger = createLogger(rl, `macro/${getMacroName(filepath)}`);
|
|
65
|
+
|
|
53
66
|
try {
|
|
54
67
|
if (!(await fileExists(absolutePath))) {
|
|
55
|
-
|
|
68
|
+
logger.error(`file not found: ${absolutePath}`);
|
|
56
69
|
return false;
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
const isTypescriptFile = absolutePath.endsWith(".ts");
|
|
60
73
|
|
|
61
74
|
if (isTypescriptFile) {
|
|
62
|
-
|
|
75
|
+
logger.log("compiling macro to js");
|
|
63
76
|
compiledPath = await compileToJS(absolutePath);
|
|
64
77
|
importPath = compiledPath;
|
|
65
78
|
}
|
|
@@ -68,14 +81,16 @@ export async function loadAndRunMacro(filepath: string, ctx: MacroContext) {
|
|
|
68
81
|
const module = (await import(fileUrl)) as MacroModule;
|
|
69
82
|
|
|
70
83
|
if (typeof module.default !== "function") {
|
|
71
|
-
|
|
84
|
+
logger.error("macro file must export a default function");
|
|
72
85
|
return false;
|
|
73
86
|
}
|
|
74
87
|
|
|
75
|
-
|
|
88
|
+
logger.log("running");
|
|
89
|
+
await module.default({ bot, logger });
|
|
90
|
+
logger.log("finished");
|
|
76
91
|
return true;
|
|
77
92
|
} catch (err) {
|
|
78
|
-
|
|
93
|
+
logger.error(`failed: ${(err as Error).message}`);
|
|
79
94
|
return false;
|
|
80
95
|
} finally {
|
|
81
96
|
if (compiledPath) {
|
package/src/repl.ts
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
import type { Config, DelayableCommands } from "@mincraft/types";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { version } from ".";
|
|
5
|
+
import { BotClient } from "./bot";
|
|
6
|
+
import { createLogger } from "./logger";
|
|
7
|
+
import { loadAndRunMacro } from "./macro";
|
|
8
|
+
|
|
9
|
+
export enum BotCommand {
|
|
10
|
+
Exit = ".exit",
|
|
11
|
+
Logout = ".lo",
|
|
12
|
+
Login = ".li",
|
|
13
|
+
Macro = ".macro",
|
|
14
|
+
Help = ".help",
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function isCommand(value: string) {
|
|
18
|
+
return Object.values(BotCommand).some((cmd) => value.startsWith(cmd));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function showReplHelp(log: (msg: string) => void, serverHost: string) {
|
|
22
|
+
log(`type ${chalk.magenta(".li")} to log in to ${serverHost}`);
|
|
23
|
+
log(`type ${chalk.magenta(".lo")} to log out from ${serverHost}`);
|
|
24
|
+
log(`type ${chalk.magenta(".macro <filepath>")} to run a macro`);
|
|
25
|
+
log(`type ${chalk.magenta(".help")} to show this REPL reference`);
|
|
26
|
+
log(`type ${chalk.magenta(".exit")} to exit the REPL`);
|
|
27
|
+
log(`type ${chalk.bold("any other value")} to send a message to the server`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function run(config: Config, commands?: DelayableCommands) {
|
|
31
|
+
process.stdout.write("\u001b[2J\u001b[2;0H");
|
|
32
|
+
let bot: BotClient = null;
|
|
33
|
+
|
|
34
|
+
const rl = readline.createInterface({
|
|
35
|
+
input: process.stdin,
|
|
36
|
+
output: process.stdout,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const logger = createLogger(rl);
|
|
40
|
+
const helpLogger = (msg: string) => logger.raw(`>> ${msg}`);
|
|
41
|
+
|
|
42
|
+
rl.setPrompt("> ");
|
|
43
|
+
|
|
44
|
+
const handleInput = async (input: string) => {
|
|
45
|
+
input = input.trim();
|
|
46
|
+
|
|
47
|
+
if (!input) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!isCommand(input) && !bot?.isLoggedIn) {
|
|
52
|
+
logger.error("log in using .li before sending messages");
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (isCommand(input)) {
|
|
57
|
+
const [cmd, ...args] = input.split(" ");
|
|
58
|
+
|
|
59
|
+
switch (cmd) {
|
|
60
|
+
case BotCommand.Exit: {
|
|
61
|
+
bot?.disconnect();
|
|
62
|
+
rl.close();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
case BotCommand.Login: {
|
|
66
|
+
if (bot?.isLoggedIn) {
|
|
67
|
+
logger.error("client is already logged in");
|
|
68
|
+
} else {
|
|
69
|
+
bot = new BotClient(config, logger);
|
|
70
|
+
bot.registerAndLogIn();
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case BotCommand.Logout: {
|
|
75
|
+
if (!bot?.isLoggedIn) {
|
|
76
|
+
logger.error("client isn't logged in");
|
|
77
|
+
} else {
|
|
78
|
+
bot?.disconnect();
|
|
79
|
+
}
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
case BotCommand.Macro: {
|
|
83
|
+
if (!bot?.isLoggedIn) {
|
|
84
|
+
logger.error("client must be logged in to run macros");
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const filepath = args.join(" ");
|
|
88
|
+
if (!filepath) {
|
|
89
|
+
logger.error("usage: .macro <filepath>");
|
|
90
|
+
break;
|
|
91
|
+
}
|
|
92
|
+
const mineflayer = bot.bot;
|
|
93
|
+
if (mineflayer) {
|
|
94
|
+
await loadAndRunMacro(filepath, mineflayer, rl);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case BotCommand.Help: {
|
|
99
|
+
showReplHelp(helpLogger, config.server.host);
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} else {
|
|
104
|
+
bot?.sendMessage(input);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return false;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
rl.on("line", async (input) => {
|
|
111
|
+
const shouldExit = await handleInput(input);
|
|
112
|
+
if (shouldExit) {
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}
|
|
115
|
+
rl.prompt();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
console.log(chalk.green(`mincraft REPL ${version}`));
|
|
119
|
+
helpLogger('type ".help" for more information.');
|
|
120
|
+
|
|
121
|
+
if (commands && commands.length > 0) {
|
|
122
|
+
for (const { command, delay } of commands) {
|
|
123
|
+
if (delay > 0) {
|
|
124
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
125
|
+
}
|
|
126
|
+
if (command) {
|
|
127
|
+
logger.raw(`> ${command}`);
|
|
128
|
+
const shouldExit = await handleInput(command);
|
|
129
|
+
if (shouldExit) {
|
|
130
|
+
process.exit(0);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
rl.prompt();
|
|
137
|
+
}
|
package/src/runner.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as readline from "node:readline";
|
|
2
|
-
import type { Config } from "@mincraft/types";
|
|
3
|
-
import { BotClient } from "./bot";
|
|
4
|
-
import { createLogger } from "./logger";
|
|
5
|
-
import { loadAndRunMacro } from "./macro";
|
|
6
|
-
|
|
7
|
-
export enum BotCommand {
|
|
8
|
-
Exit = ".exit",
|
|
9
|
-
Logout = ".lo",
|
|
10
|
-
Login = ".li",
|
|
11
|
-
Macro = ".macro",
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function isCommand(value: string) {
|
|
15
|
-
return Object.values(BotCommand).some((cmd) => value.startsWith(cmd));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function run(config: Config) {
|
|
19
|
-
let bot: BotClient | null = null;
|
|
20
|
-
|
|
21
|
-
const rl = readline.createInterface({
|
|
22
|
-
input: process.stdin,
|
|
23
|
-
output: process.stdout,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const logger = createLogger(rl);
|
|
27
|
-
|
|
28
|
-
rl.setPrompt("> ");
|
|
29
|
-
|
|
30
|
-
const handleInput = async (input: string) => {
|
|
31
|
-
input = input.trim();
|
|
32
|
-
|
|
33
|
-
if (!isCommand(input) && !bot?.isLoggedIn) {
|
|
34
|
-
logger.raw("log in using .li before sending messages");
|
|
35
|
-
rl.prompt();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
if (isCommand(input)) {
|
|
40
|
-
const [cmd, ...args] = input.split(" ");
|
|
41
|
-
let exit = false;
|
|
42
|
-
|
|
43
|
-
switch (cmd) {
|
|
44
|
-
case BotCommand.Exit: {
|
|
45
|
-
bot?.disconnect();
|
|
46
|
-
rl.close();
|
|
47
|
-
exit = true;
|
|
48
|
-
break;
|
|
49
|
-
}
|
|
50
|
-
case BotCommand.Login: {
|
|
51
|
-
if (bot?.isLoggedIn) {
|
|
52
|
-
logger.error("client is already logged in");
|
|
53
|
-
} else {
|
|
54
|
-
bot = new BotClient(config, logger);
|
|
55
|
-
bot.registerAndLogIn();
|
|
56
|
-
}
|
|
57
|
-
break;
|
|
58
|
-
}
|
|
59
|
-
case BotCommand.Logout: {
|
|
60
|
-
if (!bot?.isLoggedIn) {
|
|
61
|
-
logger.error("client isn't logged in");
|
|
62
|
-
} else {
|
|
63
|
-
bot?.disconnect();
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
67
|
-
case BotCommand.Macro: {
|
|
68
|
-
if (!bot?.isLoggedIn) {
|
|
69
|
-
logger.error("client must be logged in to run macros");
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
const filepath = args.join(" ");
|
|
73
|
-
if (!filepath) {
|
|
74
|
-
logger.error("usage: .macro <filepath>");
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
const mineflayer = bot.bot;
|
|
78
|
-
if (mineflayer) {
|
|
79
|
-
logger.log(`loading macro: ${filepath}`);
|
|
80
|
-
await loadAndRunMacro(filepath, { bot: mineflayer, logger });
|
|
81
|
-
}
|
|
82
|
-
break;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (exit) {
|
|
87
|
-
process.exit(0);
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
bot?.sendMessage(input);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
rl.prompt();
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
rl.on("line", handleInput);
|
|
97
|
-
|
|
98
|
-
console.log("=== mincraft REPL ===");
|
|
99
|
-
console.log("input plain text to send a message to the server");
|
|
100
|
-
console.log(`input .li to log in to ${config.server.host}`);
|
|
101
|
-
console.log(`input .lo to log out from ${config.server.host}`);
|
|
102
|
-
console.log("input .macro <filepath> to run a macro");
|
|
103
|
-
console.log("input .exit to exit the REPL");
|
|
104
|
-
|
|
105
|
-
rl.prompt();
|
|
106
|
-
}
|