@mincraft/cli 1.2.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 +7 -0
- package/package.json +31 -0
- package/src/bot.ts +146 -0
- package/src/cli.ts +164 -0
- package/src/index.ts +9 -0
- package/src/logger.ts +25 -0
- package/src/macro.ts +85 -0
- package/src/runner.ts +106 -0
- package/tsconfig.json +11 -0
- package/tsup.config.ts +11 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{Option as I,program as n}from"commander";import*as v from"readline";import{createBot as M}from"mineflayer";import{SocksClient as E}from"socks";var p=class{proxy;client;server;options;logger;isLoggedIn;bot;constructor({proxy:t,client:e,server:o,options:i},s){this.proxy=t,this.client=e,this.server=o,this.options=i,this.logger=s,this.isLoggedIn=!1}async registerAndLogIn(){let t={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&&(t.connect=e=>{this.connectViaProxy(e)}),this.bot=M(t),this.options.verboseLogging&&this.registerClientEvents(),this.registerBotEvents()}connectViaProxy(t){this.logger.log("connecting via proxy");let{host:e,port:o,user:i,pass:s,type:a}=this.proxy,c=this.server;E.createConnection({proxy:{host:e,port:o,type:a,userId:i,password:s},command:"connect",destination:{host:c.host,port:c.port},timeout:3e4}).then(l=>{t.setSocket(l.socket),t.emit("connect")}).catch(l=>{this.logger.error(`proxy connection failed: ${l.message}`),t.emit("error",l)})}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",t=>{this.logger.error(`client error: ${t.message}`)}),this.bot._client.on("end",t=>{this.logger.warn(`client ended: ${t}`)})}registerBotEvents(){this.bot.once("spawn",()=>{let{ign:t,uuid:e}=this.client.account;this.logger.log(`logged in as ${t} (${e})`),this.isLoggedIn=!0}),this.bot.once("kicked",t=>{this.logger.warn(`kicked: ${JSON.stringify(t)}`),this.disconnect()}),this.bot.once("error",t=>{this.logger.error(`bot error: ${t.message}`),this.isLoggedIn=!1}),this.bot.once("end",t=>{this.logger.warn(`disconnected by server: ${JSON.stringify(t)}`),this.disconnect()}),this.options.logMessages&&this.bot.on("messagestr",t=>{let e=t.split(`
|
|
3
|
+
`);if(e.length===1)this.logger.log(t,"chat");else{this.logger.log(e[0],"chat");for(let o=1;o<e.length;o++)this.logger.raw(` ${e[o]}`)}})}disconnect(){this.bot&&(this.proxy?this.bot._client.socket?.destroy():this.bot.quit(),this.isLoggedIn=!1)}sendMessage(t){this.bot.chat(t)}};import*as u from"readline";function m(r){let t=(o,i="")=>`[mc${i?`/${i}`:""}] ${o}`,e=o=>{r?(u.clearLine(process.stdout,0),u.cursorTo(process.stdout,0),console.log(o),r.prompt(!0)):console.log(o)};return{log:(o,i)=>e(t(o,i)),warn:(o,i="warn")=>e(t(o,i)),error:(o,i="err")=>e(t(o,i)),raw:e}}import*as g from"fs/promises";import*as d from"path";import*as x from"esbuild";async function O(r){let e=(await x.build({entryPoints:[r],bundle:!0,write:!1,platform:"node",format:"esm",target:"es2022",loader:{".ts":"ts"},external:["mineflayer"],logLevel:"silent"})).outputFiles[0].text,o=r.replace(/\.ts$/,".macro.mjs");return await g.writeFile(o,e),o}async function S(r){try{await g.unlink(r)}catch{}}function F(r){return d.isAbsolute(r)?r:d.resolve(process.cwd(),r)}async function k(r){try{return await g.access(r),!0}catch{return!1}}async function b(r,t){let e=F(r),o=e,i=null;try{if(!await k(e))return t.logger.error(`macro file not found: ${e}`),!1;e.endsWith(".ts")&&(t.logger.log("compiling typescript macro"),i=await O(e),o=i);let c=await import(`file://${o}?t=${Date.now()}`);return typeof c.default!="function"?(t.logger.error("macro file must export a default function"),!1):(await c.default(t),!0)}catch(s){return t.logger.error(`could not load macro: ${s.message}`),!1}finally{i&&await S(i)}}var C=(i=>(i.Exit=".exit",i.Logout=".lo",i.Login=".li",i.Macro=".macro",i))(C||{});function w(r){return Object.values(C).some(t=>r.startsWith(t))}async function L(r){let t=null,e=v.createInterface({input:process.stdin,output:process.stdout}),o=m(e);e.setPrompt("> ");let i=async s=>{if(s=s.trim(),!w(s)&&!t?.isLoggedIn){o.raw("log in using .li before sending messages"),e.prompt();return}if(w(s)){let[a,...c]=s.split(" "),l=!1;switch(a){case".exit":{t?.disconnect(),e.close(),l=!0;break}case".li":{t?.isLoggedIn?o.error("client is already logged in"):(t=new p(r,o),t.registerAndLogIn());break}case".lo":{t?.isLoggedIn?t?.disconnect():o.error("client isn't logged in");break}case".macro":{if(!t?.isLoggedIn){o.error("client must be logged in to run macros");break}let f=c.join(" ");if(!f){o.error("usage: .macro <filepath>");break}let y=t.bot;y&&(o.log(`loading macro: ${f}`),await b(f,{bot:y,logger:o}));break}}l&&process.exit(0)}else t?.sendMessage(s);e.prompt()};e.on("line",i),console.log("=== mincraft REPL ==="),console.log("input plain text to send a message to the server"),console.log(`input .li to log in to ${r.server.host}`),console.log(`input .lo to log out from ${r.server.host}`),console.log("input .macro <filepath> to run a macro"),console.log("input .exit to exit the REPL"),e.prompt()}n.name("mincraft").description("Mineflayer CLI tool with supports for macros and proxies.").version("1.0.0").addHelpText("after",`
|
|
4
|
+
Examples:
|
|
5
|
+
$ mincraft mc.hypixel.net 1.21.4 --ign FuriousDestroyer --email you@example.com
|
|
6
|
+
$ mincraft mythic.gg 1.7.10 -p 58585 --ign MangoSyrup --email you@example.com --prox proxy.com:1234:mango:secret
|
|
7
|
+
`);n.argument("<host>","Server hostname").option("-p, --port <PORT>","Server port",r=>parseInt(r,10),25565);n.argument("<version>","Client version, e.g. 1.21.4");var P=["host","port","user","pass"],$=":";n.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",r=>parseInt(r,10),5);var T="microsoft";n.addOption(new I("--ign <IN-GAME NAME>","Player username").conflicts("uuid")).addOption(new I("--uuid <UUID>","Player UUID").conflicts("ign")).requiredOption("--email <EMAIL>","Account email").option("--auth <AUTH>","Account authentication method",T);n.option("--no-log-messages","Do not log messages your client receives").option("--verbose","Enable additional logging messages");n.action(async(r,t,e)=>{if(!e.ign&&!e.uuid&&n.error("error: must specify either --ign or --uuid"),e.proxFieldOrder){let i=e.proxyFieldSep??$;e.proxFieldOrder.split(i).every(a=>P.includes(a))||n.error("error: --prox-field-order includes invalid fields")}let o=await B(n);await L(o)});async function A(r){let e=await(await fetch(`https://sessionserver.mojang.com/session/minecraft/profile/${r}`)).json();if(!e?.name)throw new Error("Could not fetch IGN from UUID");return e.name}async function U(r){let e=await(await fetch(`https://api.mojang.com/users/profiles/minecraft/${r}`)).json();if(!e?.id)throw new Error("Could not fetch UUID from IGN");return e.id}function j(r,t,e){let o=r.split(e),i=Object.fromEntries(t.map((s,a)=>[s,o[a]]));return i.port=+i.port,i}async function B(r){let t=r.args,e=r.opts();return{server:{host:t[0],port:e.port},client:{version:t[1],account:{auth:e.auth,uuid:e.uuid??await U(e.ign),ign:e.ign??await A(e.uuid),email:e.email}},...e.prox?{proxy:{...j(e.prox,e.proxFieldOrder??P,e.proxFieldSep??$),type:e.proxType}}:{},options:{logMessages:e.noLogMessages??!0,verboseLogging:e.verbose??!1}}}var h=m(),Y=h.log,Z=h.warn,ee=h.error;n.parse();export{h as defaultLogger,ee as error,Y as log,Z as warn};
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mincraft/cli",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Mineflayer CLI tool with support for macros and proxies",
|
|
5
|
+
"bin": {
|
|
6
|
+
"mincraft": "./dist/index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"keywords": [
|
|
9
|
+
"minecraft",
|
|
10
|
+
"mineflayer",
|
|
11
|
+
"cli",
|
|
12
|
+
"macros"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@inquirer/prompts": "^8.1.0",
|
|
16
|
+
"commander": "^14.0.2",
|
|
17
|
+
"esbuild": "^0.27.2",
|
|
18
|
+
"mineflayer": "^4.33.0",
|
|
19
|
+
"socks": "^2.8.7",
|
|
20
|
+
"@mincraft/types": "1.2.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/debug": "^4.1.12",
|
|
24
|
+
"@types/node": "^25.0.3",
|
|
25
|
+
"tsup": "^8.5.1"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"dev": "tsup src/index.ts --watch",
|
|
29
|
+
"build": "tsup"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/bot.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Config, Logger } from "@mincraft/types";
|
|
2
|
+
import { type Bot, type BotOptions, createBot } from "mineflayer";
|
|
3
|
+
import { SocksClient } from "socks";
|
|
4
|
+
|
|
5
|
+
export class BotClient {
|
|
6
|
+
public proxy: Config["proxy"];
|
|
7
|
+
public client: Config["client"];
|
|
8
|
+
public server: Config["server"];
|
|
9
|
+
public options: Config["options"];
|
|
10
|
+
public logger: Logger;
|
|
11
|
+
|
|
12
|
+
public isLoggedIn: boolean;
|
|
13
|
+
public bot: Bot;
|
|
14
|
+
|
|
15
|
+
public constructor(
|
|
16
|
+
{ proxy, client, server, options }: Config,
|
|
17
|
+
logger: Logger,
|
|
18
|
+
) {
|
|
19
|
+
this.proxy = proxy;
|
|
20
|
+
this.client = client;
|
|
21
|
+
this.server = server;
|
|
22
|
+
this.options = options;
|
|
23
|
+
this.logger = logger;
|
|
24
|
+
|
|
25
|
+
this.isLoggedIn = false;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
public async registerAndLogIn() {
|
|
29
|
+
const botOptions: BotOptions = {
|
|
30
|
+
host: this.server.host,
|
|
31
|
+
port: this.server.port,
|
|
32
|
+
username: this.client.account.email,
|
|
33
|
+
auth: this.client.account.auth as BotOptions["auth"],
|
|
34
|
+
version: this.client.version,
|
|
35
|
+
hideErrors: true,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (this.proxy) {
|
|
39
|
+
botOptions.connect = (client) => {
|
|
40
|
+
this.connectViaProxy(client);
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
this.bot = createBot(botOptions);
|
|
45
|
+
if (this.options.verboseLogging) {
|
|
46
|
+
this.registerClientEvents();
|
|
47
|
+
}
|
|
48
|
+
this.registerBotEvents();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private connectViaProxy(client: any) {
|
|
52
|
+
this.logger.log("connecting via proxy");
|
|
53
|
+
const { host, port, user: userId, pass: password, type } = this.proxy;
|
|
54
|
+
const destination = this.server;
|
|
55
|
+
|
|
56
|
+
SocksClient.createConnection({
|
|
57
|
+
proxy: {
|
|
58
|
+
host,
|
|
59
|
+
port,
|
|
60
|
+
type,
|
|
61
|
+
userId,
|
|
62
|
+
password,
|
|
63
|
+
},
|
|
64
|
+
command: "connect",
|
|
65
|
+
destination: { host: destination.host, port: destination.port },
|
|
66
|
+
timeout: 30000,
|
|
67
|
+
})
|
|
68
|
+
.then((info) => {
|
|
69
|
+
client.setSocket(info.socket);
|
|
70
|
+
client.emit("connect");
|
|
71
|
+
})
|
|
72
|
+
.catch((err) => {
|
|
73
|
+
this.logger.error(`proxy connection failed: ${err.message}`);
|
|
74
|
+
client.emit("error", err);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
private registerClientEvents() {
|
|
79
|
+
this.bot._client.on("connect", () => {
|
|
80
|
+
this.logger.log("TCP connection established");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this.bot._client.on("login", () => {
|
|
84
|
+
this.logger.log("login packet received");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
this.bot._client.on("error", (err) => {
|
|
88
|
+
this.logger.error(`client error: ${err.message}`);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
this.bot._client.on("end", (reason) => {
|
|
92
|
+
this.logger.warn(`client ended: ${reason}`);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private registerBotEvents() {
|
|
97
|
+
this.bot.once("spawn", () => {
|
|
98
|
+
const { ign, uuid } = this.client.account;
|
|
99
|
+
this.logger.log(`logged in as ${ign} (${uuid})`);
|
|
100
|
+
this.isLoggedIn = true;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
this.bot.once("kicked", (reason) => {
|
|
104
|
+
this.logger.warn(`kicked: ${JSON.stringify(reason)}`);
|
|
105
|
+
this.disconnect();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
this.bot.once("error", (err) => {
|
|
109
|
+
this.logger.error(`bot error: ${err.message}`);
|
|
110
|
+
this.isLoggedIn = false;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
this.bot.once("end", (reason) => {
|
|
114
|
+
this.logger.warn(`disconnected by server: ${JSON.stringify(reason)}`);
|
|
115
|
+
this.disconnect();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (this.options.logMessages) {
|
|
119
|
+
this.bot.on("messagestr", (message: string) => {
|
|
120
|
+
const lines = message.split("\n");
|
|
121
|
+
if (lines.length === 1) {
|
|
122
|
+
this.logger.log(message, "chat");
|
|
123
|
+
} else {
|
|
124
|
+
this.logger.log(lines[0], "chat");
|
|
125
|
+
for (let i = 1; i < lines.length; i++) {
|
|
126
|
+
this.logger.raw(` ${lines[i]}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
public disconnect() {
|
|
134
|
+
if (!this.bot) return;
|
|
135
|
+
if (this.proxy) {
|
|
136
|
+
this.bot._client.socket?.destroy();
|
|
137
|
+
} else {
|
|
138
|
+
this.bot.quit();
|
|
139
|
+
}
|
|
140
|
+
this.isLoggedIn = false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
public sendMessage(msg: string) {
|
|
144
|
+
this.bot.chat(msg);
|
|
145
|
+
}
|
|
146
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { Config, ProxyCredentials } from "@mincraft/types";
|
|
2
|
+
import { type Command, Option, program } from "commander";
|
|
3
|
+
import { run } from "./runner";
|
|
4
|
+
|
|
5
|
+
// meta
|
|
6
|
+
program
|
|
7
|
+
.name("mincraft")
|
|
8
|
+
.description("Mineflayer CLI tool with supports for macros and proxies.")
|
|
9
|
+
.version("1.0.0")
|
|
10
|
+
.addHelpText(
|
|
11
|
+
"after",
|
|
12
|
+
`
|
|
13
|
+
Examples:
|
|
14
|
+
$ mincraft mc.hypixel.net 1.21.4 --ign FuriousDestroyer --email you@example.com
|
|
15
|
+
$ mincraft mythic.gg 1.7.10 -p 58585 --ign MangoSyrup --email you@example.com --prox proxy.com:1234:mango:secret
|
|
16
|
+
`,
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
// server
|
|
20
|
+
program
|
|
21
|
+
.argument("<host>", "Server hostname")
|
|
22
|
+
.option(
|
|
23
|
+
"-p, --port <PORT>",
|
|
24
|
+
"Server port",
|
|
25
|
+
(val) => parseInt(val, 10),
|
|
26
|
+
25565,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// client version
|
|
30
|
+
program.argument("<version>", "Client version, e.g. 1.21.4");
|
|
31
|
+
|
|
32
|
+
// proxy
|
|
33
|
+
const proxyFieldsInOrder = ["host", "port", "user", "pass"];
|
|
34
|
+
const defaultProxyFieldSep = ":";
|
|
35
|
+
program
|
|
36
|
+
.option("--prox <HOST:PORT:USER:PASS>", "Connect to the server with a proxy")
|
|
37
|
+
.option(
|
|
38
|
+
"--prox-field-order <FORMAT>",
|
|
39
|
+
'The order of the proxy credential fields, e.g. "user,pass,host,port".',
|
|
40
|
+
)
|
|
41
|
+
.option(
|
|
42
|
+
"--prox-field-sep <SEP>",
|
|
43
|
+
"The separator of the proxy credential fields",
|
|
44
|
+
)
|
|
45
|
+
.option(
|
|
46
|
+
"--prox-type <4|5>",
|
|
47
|
+
"The SOCKS proxy type",
|
|
48
|
+
(val) => parseInt(val, 10),
|
|
49
|
+
5,
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// account
|
|
53
|
+
const defaultAuthenticationMethod = "microsoft";
|
|
54
|
+
program
|
|
55
|
+
.addOption(
|
|
56
|
+
new Option("--ign <IN-GAME NAME>", "Player username").conflicts("uuid"),
|
|
57
|
+
)
|
|
58
|
+
.addOption(new Option("--uuid <UUID>", "Player UUID").conflicts("ign"))
|
|
59
|
+
.requiredOption("--email <EMAIL>", "Account email")
|
|
60
|
+
.option(
|
|
61
|
+
"--auth <AUTH>",
|
|
62
|
+
"Account authentication method",
|
|
63
|
+
defaultAuthenticationMethod,
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// extra options
|
|
67
|
+
program
|
|
68
|
+
.option("--no-log-messages", "Do not log messages your client receives")
|
|
69
|
+
.option("--verbose", "Enable additional logging messages");
|
|
70
|
+
|
|
71
|
+
program.action(async (_host, _version, options) => {
|
|
72
|
+
if (!options.ign && !options.uuid) {
|
|
73
|
+
program.error("error: must specify either --ign or --uuid");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (options.proxFieldOrder) {
|
|
77
|
+
const proxyFieldSep = options.proxyFieldSep ?? defaultProxyFieldSep;
|
|
78
|
+
const proxyFieldNames: string[] =
|
|
79
|
+
options.proxFieldOrder.split(proxyFieldSep);
|
|
80
|
+
if (!proxyFieldNames.every((field) => proxyFieldsInOrder.includes(field))) {
|
|
81
|
+
program.error("error: --prox-field-order includes invalid fields");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const config = await getConfig(program);
|
|
86
|
+
await run(config);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
async function getIGN(uuid: string) {
|
|
90
|
+
const res = await fetch(
|
|
91
|
+
`https://sessionserver.mojang.com/session/minecraft/profile/${uuid}`,
|
|
92
|
+
);
|
|
93
|
+
const data = (await res.json()) as { name?: string };
|
|
94
|
+
if (!data?.name) {
|
|
95
|
+
throw new Error("Could not fetch IGN from UUID");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return data.name;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function getUUID(ign: string) {
|
|
102
|
+
const res = await fetch(
|
|
103
|
+
`https://api.mojang.com/users/profiles/minecraft/${ign}`,
|
|
104
|
+
);
|
|
105
|
+
const data = (await res.json()) as { id?: string };
|
|
106
|
+
if (!data?.id) {
|
|
107
|
+
throw new Error("Could not fetch UUID from IGN");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return data.id;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function resolveProxyCredentials(
|
|
114
|
+
proxyString: string,
|
|
115
|
+
fieldOrder: string[],
|
|
116
|
+
fieldSep: string,
|
|
117
|
+
) {
|
|
118
|
+
const proxyParts = proxyString.split(fieldSep);
|
|
119
|
+
const credentials = Object.fromEntries(
|
|
120
|
+
fieldOrder.map((field, i) => [field, proxyParts[i]]),
|
|
121
|
+
) as unknown as ProxyCredentials;
|
|
122
|
+
credentials.port = +credentials.port;
|
|
123
|
+
|
|
124
|
+
return credentials;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function getConfig(p: Command): Promise<Config> {
|
|
128
|
+
const args = p.args;
|
|
129
|
+
const opts = p.opts();
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
server: {
|
|
133
|
+
host: args[0],
|
|
134
|
+
port: opts.port,
|
|
135
|
+
},
|
|
136
|
+
client: {
|
|
137
|
+
version: args[1],
|
|
138
|
+
account: {
|
|
139
|
+
auth: opts.auth,
|
|
140
|
+
uuid: opts.uuid ?? (await getUUID(opts.ign)),
|
|
141
|
+
ign: opts.ign ?? (await getIGN(opts.uuid)),
|
|
142
|
+
email: opts.email,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
...(opts.prox
|
|
146
|
+
? {
|
|
147
|
+
proxy: {
|
|
148
|
+
...resolveProxyCredentials(
|
|
149
|
+
opts.prox,
|
|
150
|
+
opts.proxFieldOrder ?? proxyFieldsInOrder,
|
|
151
|
+
opts.proxFieldSep ?? defaultProxyFieldSep,
|
|
152
|
+
),
|
|
153
|
+
type: opts.proxType,
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
: {}),
|
|
157
|
+
options: {
|
|
158
|
+
logMessages: opts.noLogMessages ?? true,
|
|
159
|
+
verboseLogging: opts.verbose ?? false,
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export { program };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { program } from "./cli.js";
|
|
2
|
+
import { createLogger } from "./logger.js";
|
|
3
|
+
|
|
4
|
+
export const defaultLogger = createLogger();
|
|
5
|
+
export const log = defaultLogger.log;
|
|
6
|
+
export const warn = defaultLogger.warn;
|
|
7
|
+
export const error = defaultLogger.error;
|
|
8
|
+
|
|
9
|
+
program.parse();
|
package/src/logger.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import * as readline from "node:readline";
|
|
2
|
+
import type { Logger } from "@mincraft/types";
|
|
3
|
+
|
|
4
|
+
export function createLogger(rl?: readline.Interface): Logger {
|
|
5
|
+
const format = (msg: string, scope = "") =>
|
|
6
|
+
`[mc${scope ? `/${scope}` : ""}] ${msg}`;
|
|
7
|
+
|
|
8
|
+
const write = (msg: string) => {
|
|
9
|
+
if (rl) {
|
|
10
|
+
readline.clearLine(process.stdout, 0);
|
|
11
|
+
readline.cursorTo(process.stdout, 0);
|
|
12
|
+
console.log(msg);
|
|
13
|
+
rl.prompt(true);
|
|
14
|
+
} else {
|
|
15
|
+
console.log(msg);
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
log: (msg: string, scope?: string) => write(format(msg, scope)),
|
|
21
|
+
warn: (msg: string, scope = "warn") => write(format(msg, scope)),
|
|
22
|
+
error: (msg: string, scope = "err") => write(format(msg, scope)),
|
|
23
|
+
raw: write,
|
|
24
|
+
};
|
|
25
|
+
}
|
package/src/macro.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import type { MacroContext, MacroModule } from "@mincraft/types";
|
|
4
|
+
import * as esbuild from "esbuild";
|
|
5
|
+
|
|
6
|
+
async function compileToJS(filepath: string) {
|
|
7
|
+
const result = await esbuild.build({
|
|
8
|
+
entryPoints: [filepath],
|
|
9
|
+
bundle: true,
|
|
10
|
+
write: false,
|
|
11
|
+
platform: "node",
|
|
12
|
+
format: "esm",
|
|
13
|
+
target: "es2022",
|
|
14
|
+
loader: { ".ts": "ts" },
|
|
15
|
+
external: ["mineflayer"],
|
|
16
|
+
logLevel: "silent",
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const jsCode = result.outputFiles[0].text;
|
|
20
|
+
const jsFilePath = filepath.replace(/\.ts$/, ".macro.mjs");
|
|
21
|
+
await fs.writeFile(jsFilePath, jsCode);
|
|
22
|
+
|
|
23
|
+
return jsFilePath;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function removeCompiledFile(filepath: string) {
|
|
27
|
+
try {
|
|
28
|
+
await fs.unlink(filepath);
|
|
29
|
+
} catch {}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function resolveMacroPath(filepath: string) {
|
|
33
|
+
if (path.isAbsolute(filepath)) {
|
|
34
|
+
return filepath;
|
|
35
|
+
}
|
|
36
|
+
return path.resolve(process.cwd(), filepath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function fileExists(filepath: string) {
|
|
40
|
+
try {
|
|
41
|
+
await fs.access(filepath);
|
|
42
|
+
return true;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function loadAndRunMacro(filepath: string, ctx: MacroContext) {
|
|
49
|
+
const absolutePath = resolveMacroPath(filepath);
|
|
50
|
+
let importPath = absolutePath;
|
|
51
|
+
let compiledPath: string = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
if (!(await fileExists(absolutePath))) {
|
|
55
|
+
ctx.logger.error(`macro file not found: ${absolutePath}`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const isTypescriptFile = absolutePath.endsWith(".ts");
|
|
60
|
+
|
|
61
|
+
if (isTypescriptFile) {
|
|
62
|
+
ctx.logger.log("compiling typescript macro");
|
|
63
|
+
compiledPath = await compileToJS(absolutePath);
|
|
64
|
+
importPath = compiledPath;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const fileUrl = `file://${importPath}?t=${Date.now()}`;
|
|
68
|
+
const module = (await import(fileUrl)) as MacroModule;
|
|
69
|
+
|
|
70
|
+
if (typeof module.default !== "function") {
|
|
71
|
+
ctx.logger.error("macro file must export a default function");
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await module.default(ctx);
|
|
76
|
+
return true;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
ctx.logger.error(`could not load macro: ${(err as Error).message}`);
|
|
79
|
+
return false;
|
|
80
|
+
} finally {
|
|
81
|
+
if (compiledPath) {
|
|
82
|
+
await removeCompiledFile(compiledPath);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/runner.ts
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
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
|
+
}
|
package/tsconfig.json
ADDED