Show MineGeo console output and release plugin 0.1.2

This commit is contained in:
2026-10-05 18:37:32 +05:00
parent 891de77a10
commit b33db48b63
7 changed files with 60 additions and 9 deletions

View File

@@ -1,6 +1,8 @@
{
"passed": true,
"checks": [
"local console help, status and errors produce visible output",
"remote console help produces visible output",
"missing export guidance",
"empty area failure",
"real Paper export without a player",
@@ -16,5 +18,5 @@
"blocks": 10112,
"chunks": 16,
"viewpoints": 24,
"directory": ".cache/plugin-regression-1791186051478"
"directory": ".cache/plugin-regression-1791207258927"
}

View File

@@ -1,8 +1,8 @@
# Server plugin 0.1.1
# Server plugin 0.1.2
## Update and connect
1. Stop the Minecraft server, replace its old MineGeo JAR with `public/tools/plugin/minegeo-plugin-1.21.11.jar`, and restart. Run `/version MineGeo` to verify 0.1.1.
1. Stop the Minecraft server, replace its old MineGeo JAR with `public/tools/plugin/minegeo-plugin-1.21.11.jar`, and restart. Run `version MineGeo` in the console to verify 0.1.2.
2. Open your MineGeo website dashboard, sign in, and generate a plugin token.
3. In the Minecraft server console, configure the website and token. Console commands do not need the leading slash:

View File

@@ -1 +1 @@
allprojects { group = 'dev.minegeo'; version = '0.1.1'; repositories { mavenCentral() } }
allprojects { group = 'dev.minegeo'; version = '0.1.2'; repositories { mavenCentral() } }

View File

@@ -37,7 +37,10 @@ public final class MineGeoPlugin extends JavaPlugin {
}
@Override public void onDisable(){cancelled.set(true);}
@Override public boolean onCommand(CommandSender sender,Command command,String label,String[] args){
if(!sender.hasPermission("minegeo.admin")){sender.sendMessage("MineGeo: administrator permission required.");return true;}
// Paper may dispatch hosting-panel commands through a remote console sender
// whose permissions differ from the local console. Check permissions here so
// every rejected command receives a visible explanation.
if(!(sender instanceof ConsoleCommandSender||sender instanceof RemoteConsoleCommandSender||sender instanceof Player&&sender.hasPermission("minegeo.admin"))){sender.sendMessage("MineGeo: administrator permission required.");return true;}
try {
if(args.length==0||args[0].equalsIgnoreCase("help")) {sender.sendMessage(new String[]{"MineGeo commands:","/minegeo website <http(s)://address:port>","/minegeo server <hostname[:port]> — capture server address","/minegeo link <dashboard token>","/minegeo area <world> <minX> <minZ> <maxX> <maxZ>","/minegeo export <map name> — saves a local export","/minegeo upload — uploads the latest completed export","/minegeo contribute <map ID> — send up to 12 panoramas to its owner","/minegeo status | cancel | reload"});return true;}
switch(args[0].toLowerCase(Locale.ROOT)) {
@@ -62,7 +65,7 @@ public final class MineGeoPlugin extends JavaPlugin {
}catch(Exception ex){sender.sendMessage("MineGeo: "+ex.getMessage());}return true;
}
private void requireIdle(){if(busy)throw new IllegalArgumentException("An operation is running. Use /minegeo status or /minegeo cancel.");}
private void report(CommandSender sender,String message){status=message;getLogger().info(message);if(sender instanceof Player&&isEnabled())Bukkit.getScheduler().runTask(this,()->sender.sendMessage("MineGeo: "+message));}
private void report(CommandSender sender,String message){status=message;getLogger().info(message);if(!(sender instanceof ConsoleCommandSender)&&isEnabled())Bukkit.getScheduler().runTask(this,()->sender.sendMessage("MineGeo: "+message));}
private void fail(CommandSender sender,String phase,Exception ex){report(sender,cancelled.get()?"Cancelled. Completed exports are kept.":phase+" failed: "+ex.getMessage());if(!cancelled.get())getLogger().log(Level.WARNING,phase+" failure",ex);}
private void upload(CommandSender sender,String targetMap)throws Exception {
requireIdle();if(latest==null||!Files.isRegularFile(latest.resolve("manifest.json")))throw new IOException("No completed export. Run /minegeo export My World, wait for 'Export complete', then /minegeo upload. Files: "+getDataFolder().toPath().resolve("exports").toAbsolutePath());

View File

@@ -1,12 +1,11 @@
name: MineGeo
version: 0.1.1
version: 0.1.2
main: dev.minegeo.plugin.MineGeoPlugin
api-version: '1.21'
description: Bounded terrain export for MineGeo panorama guessing
commands:
minegeo:
description: Link, select, export, upload, status or cancel MineGeo maps
permission: minegeo.admin
permissions:
minegeo.admin:
default: op

View File

@@ -1,4 +1,5 @@
import { createServer } from "node:http";
import { connect } from "node:net";
import { spawn } from "node:child_process";
import { mkdir, copyFile, writeFile, readFile } from "node:fs/promises";
import { createHash } from "node:crypto";
@@ -17,7 +18,7 @@ await copyFile(
await writeFile(`${directory}/eula.txt`, "eula=true\n");
await writeFile(
`${directory}/server.properties`,
'server-ip=127.0.0.1\nserver-port=25580\nonline-mode=true\nlevel-type=minecraft:flat\ngenerator-settings={"layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}],"biome":"minecraft:plains"}\ngenerate-structures=false\nview-distance=2\nsimulation-distance=2\nspawn-protection=0\nmax-players=1\n',
'server-ip=127.0.0.1\nserver-port=25580\nenable-rcon=true\nrcon.port=25581\nrcon.password=minegeo-fixture\nonline-mode=true\nlevel-type=minecraft:flat\ngenerator-settings={"layers":[{"block":"minecraft:bedrock","height":1},{"block":"minecraft:dirt","height":2},{"block":"minecraft:grass_block","height":1}],"biome":"minecraft:plains"}\ngenerate-structures=false\nview-distance=2\nsimulation-distance=2\nspawn-protection=0\nmax-players=1\n',
);
let manifest: any,
received = Buffer.alloc(0),
@@ -164,8 +165,52 @@ const command = async (text: string, pattern: RegExp) => {
child.stdin.write(text + "\n");
await pending;
};
const rconCommand = (text: string, expected: RegExp) => new Promise<void>((resolve, reject) => {
const socket = connect(25581, "127.0.0.1");
let buffer = Buffer.alloc(0);
let authenticated = false;
const timeout = setTimeout(() => finish(new Error(`RCON timed out waiting for ${expected}`)), 10000);
const finish = (error?: Error) => {
clearTimeout(timeout);
socket.destroy();
if (error) reject(error);
else resolve();
};
const packet = (id: number, type: number, body: string) => {
const payload = Buffer.from(body);
const frame = Buffer.alloc(payload.length + 14);
frame.writeInt32LE(payload.length + 10, 0);
frame.writeInt32LE(id, 4);
frame.writeInt32LE(type, 8);
payload.copy(frame, 12);
return frame;
};
socket.on("connect", () => socket.write(packet(1, 3, "minegeo-fixture")));
socket.on("error", finish);
socket.on("data", (chunk) => {
buffer = Buffer.concat([buffer, chunk]);
while (buffer.length >= 4 && buffer.length >= buffer.readInt32LE(0) + 4) {
const length = buffer.readInt32LE(0);
const frame = buffer.subarray(0, length + 4);
buffer = buffer.subarray(length + 4);
const id = frame.readInt32LE(4);
const type = frame.readInt32LE(8);
if (id === -1) return finish(new Error("RCON authentication failed"));
if (id === 1 && type === 2 && !authenticated) {
authenticated = true;
socket.write(packet(2, 2, text));
} else if (id === 2 && expected.test(frame.toString("utf8", 12, length + 2))) {
return finish();
}
}
});
});
try {
await wait(/Done \(/, 0, 180000);
await command("minegeo help", /MineGeo commands:/);
await command("minegeo status", /MineGeo: Idle/);
await command("minegeo unknown", /Unknown command\. Use \/minegeo help/);
await rconCommand("minegeo help", /MineGeo commands:/);
await command("minegeo upload", /No completed export/);
await command(
"minegeo area world 100000 100000 100032 100032",
@@ -266,6 +311,8 @@ try {
{
passed: true,
checks: [
"local console help, status and errors produce visible output",
"remote console help produces visible output",
"missing export guidance",
"empty area failure",
"real Paper export without a player",