koishi-plugin-mc-status-bot
Version:
690 lines (681 loc) • 22.1 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply,
inject: () => inject,
name: () => name
});
module.exports = __toCommonJS(src_exports);
var import_koishi = require("koishi");
// src/model/server.ts
var initDataBase = /* @__PURE__ */ __name((ctx) => {
return ctx.model.extend(
"mcServerList",
{
id: {
type: "integer",
length: 100
},
name: {
type: "string",
length: 20,
nullable: false
},
serverId: {
type: "string",
length: 20,
nullable: false
},
ip: {
type: "string",
length: 20,
nullable: false,
initial: ""
},
port: {
type: "integer",
length: 5,
initial: 25565
},
enableQuery: {
type: "boolean",
initial: false
}
},
{
primary: "id",
autoInc: true
}
);
}, "initDataBase");
// src/utils/ping.ts
var import_varint = __toESM(require("varint"));
var import_net = __toESM(require("net"));
var import_promises = __toESM(require("dns/promises"));
var DEFAULT_PORT = 25565;
var defaultProtocol = 767;
var FALLBACK_DNS_SERVERS = ["223.5.5.5", "8.8.8.8", "1.1.1.1"];
function mcPing(options, cb) {
const pingPromise = ping(options);
if (cb) {
pingPromise.then((d) => {
cb(null, d);
}).catch((err) => {
cb(err, null);
});
}
return pingPromise;
}
__name(mcPing, "mcPing");
function encodeString(str) {
const strBuf = Buffer.from(str, "utf8");
const lenBuf = Buffer.from(import_varint.default.encode(strBuf.length));
return Buffer.concat([lenBuf, strBuf]);
}
__name(encodeString, "encodeString");
function encodeVarInt(value) {
return Buffer.from(import_varint.default.encode(value));
}
__name(encodeVarInt, "encodeVarInt");
function createPacket(id, data) {
const idBuf = encodeVarInt(id);
const packetData = Buffer.concat([idBuf, data]);
const lengthBuf = encodeVarInt(packetData.length);
return Buffer.concat([lengthBuf, packetData]);
}
__name(createPacket, "createPacket");
var isIpAddress = /* @__PURE__ */ __name((host) => import_net.default.isIP(host) !== 0, "isIpAddress");
var stripTrailingDot = /* @__PURE__ */ __name((name2) => name2.replace(/\.$/, ""), "stripTrailingDot");
function pickSrvRecord(records) {
const sorted = [...records].sort((a, b) => a.priority - b.priority);
const minPriority = sorted[0].priority;
const candidates = sorted.filter((r) => r.priority === minPriority);
const totalWeight = candidates.reduce((sum, r) => sum + r.weight, 0);
if (totalWeight === 0) {
return candidates[Math.floor(Math.random() * candidates.length)];
}
let cursor = Math.random() * totalWeight;
for (const record of candidates) {
cursor -= record.weight;
if (cursor <= 0) return record;
}
return candidates[candidates.length - 1];
}
__name(pickSrvRecord, "pickSrvRecord");
async function lookupMinecraftSrv(host) {
const name2 = `_minecraft._tcp.${host}`;
try {
const records = await import_promises.default.resolveSrv(name2);
if (records?.length) return records;
} catch {
}
try {
const resolver = new import_promises.Resolver();
resolver.setServers(FALLBACK_DNS_SERVERS);
const records = await resolver.resolveSrv(name2);
if (records?.length) return records;
} catch {
}
return null;
}
__name(lookupMinecraftSrv, "lookupMinecraftSrv");
async function resolveMcAddress(host, port, enableSRV = true) {
const serverName = host;
if (!enableSRV || isIpAddress(host) || port !== DEFAULT_PORT) {
return { host, port, serverName };
}
const records = await lookupMinecraftSrv(host);
if (!records?.length) {
return { host, port, serverName };
}
const best = pickSrvRecord(records);
return {
host: stripTrailingDot(best.name),
port: best.port,
serverName
};
}
__name(resolveMcAddress, "resolveMcAddress");
async function ping(options) {
const inputHost = options.host || "localhost";
const inputPort = options.port || DEFAULT_PORT;
const protocolVersion = options.protocol || defaultProtocol;
const closeTimeout = options.closeTimeout || 20 * 1e3;
const enableSRV = options.enableSRV !== false;
const { host, port, serverName } = await resolveMcAddress(
inputHost,
inputPort,
enableSRV
);
return new Promise((resolve, reject) => {
const client = import_net.default.createConnection({
host,
port
});
let buffer = Buffer.alloc(0);
let startTime = null;
const onData = /* @__PURE__ */ __name((chunk) => {
buffer = Buffer.concat([buffer, chunk]);
try {
const length = import_varint.default.decode(buffer);
const lengthSize = import_varint.default.decode.bytes;
if (buffer.length < lengthSize + length) return;
const offset1 = lengthSize;
const packetId = import_varint.default.decode(buffer.slice(offset1));
const offset2 = offset1 + import_varint.default.decode.bytes;
const stringLength = import_varint.default.decode(buffer.slice(offset2));
const offset3 = offset2 + import_varint.default.decode.bytes;
if (buffer.length < offset3 + stringLength) return;
const jsonString = buffer.slice(offset3, offset3 + stringLength).toString();
const status = JSON.parse(jsonString);
const latency = Date.now() - startTime;
client.end();
resolve({
...status,
latency
});
} catch {
}
}, "onData");
client.setTimeout(closeTimeout, () => {
client.destroy();
reject(new Error("连接超时"));
});
client.on("error", reject);
client.on("data", onData);
client.once("connect", () => {
const handshakeData = Buffer.concat([
encodeVarInt(protocolVersion),
encodeString(serverName),
Buffer.from([port >> 8 & 255, port & 255]),
Buffer.from([1])
// next state: status
]);
const handshakePacket = createPacket(0, handshakeData);
const requestPacket = createPacket(0, Buffer.alloc(0));
startTime = Date.now();
client.write(Buffer.concat([handshakePacket, requestPacket]));
});
});
}
__name(ping, "ping");
// src/utils/enableQuery.ts
var import_minecraft_server_util = require("minecraft-server-util");
var onEnableQuery = /* @__PURE__ */ __name(async (host, port) => {
let startTime = Date.now();
return (0, import_minecraft_server_util.queryFull)(host, port, {
sessionID: Math.floor(Math.random() * 1e6),
enableSRV: true,
timeout: 1e4
}).then((res) => {
return {
...res,
enableQuery: true,
latency: Date.now() - startTime
};
});
}, "onEnableQuery");
var queryResultFormat = /* @__PURE__ */ __name((queryResult, options) => {
let str = options.description;
const moth = queryResult.motd.raw;
str += `描述:${options.hasForMotd ? removeMinecraftFormatting(moth) : moth}
`;
str += `延迟:${queryResult.latency}ms
`;
str += `版本:${queryResult.version}
`;
str += `在线人数:${queryResult.players.online}/${queryResult.players.max}
`;
str += `当前在线:${queryResult.players.list.join(",")}
`;
return str;
}, "queryResultFormat");
// src/utils/index.ts
var varkeys = [
"name",
"list",
"update",
"add",
"delete",
"remove",
"inset",
"upsert",
"set",
"help"
];
var initMcBot = class _initMcBot {
static {
__name(this, "initMcBot");
}
ctx;
static instance;
constructor(ctx) {
this.ctx = ctx;
}
static getInstance(ctx) {
if (!_initMcBot.instance) {
if (!ctx) {
throw new Error("实例初始化失败:缺少ctx");
}
_initMcBot.instance = new _initMcBot(ctx);
}
return _initMcBot.instance;
}
findServer(findArg) {
return this.ctx.database.get("mcServerList", findArg);
}
delteByName(findArg) {
return this.ctx.database.remove("mcServerList", findArg);
}
upsert(server) {
return this.ctx.database.upsert("mcServerList", [server]);
}
async pingServerList(findArg, config) {
const serverList = await this.findServer(findArg);
const promiseList = [];
for (let item of serverList) {
if (item.enableQuery) {
promiseList.push(
onEnableQuery(item.ip, item.port || 25565).then((result2) => result2).catch((err) => {
throw {
rejected: true,
message: err?.message || String(err),
code: err?.code,
errno: err?.errno,
enableQuery: true
};
})
);
} else {
promiseList.push(
mcPing({
host: item.ip,
port: item.port || 25565
}).then((result2) => result2).catch((err) => {
throw {
rejected: true,
message: err?.message || String(err),
code: err?.code,
errno: err?.errno,
enableQuery: false
};
})
);
}
}
const settledResults = await Promise.allSettled(promiseList);
const result = settledResults.map((settledResult, index) => {
const server = serverList[index];
const baseResult = {
name: server.name || "",
address: `${server.ip}:${server.port || 25565}`,
enableQuery: server.enableQuery || false
};
if (settledResult.status === "fulfilled") {
const value = settledResult.value;
const result2 = {
...baseResult,
...value,
latency: value.latency
};
if (value.enableQuery && "motd" in value) {
const queryValue = value;
result2.description = queryValue.motd?.raw || "";
if (typeof queryValue.version === "string") {
result2.version = { name: queryValue.version };
}
if (queryValue.players?.list) {
result2.players = {
...queryValue.players,
sample: queryValue.players.list.map((name2) => ({ name: name2 }))
};
}
}
return result2;
} else {
const reason = settledResult.reason;
return {
...baseResult,
rejected: true,
message: reason?.message || reason?.toString() || "未知错误",
code: reason?.code,
errno: reason?.errno
};
}
});
return result;
}
async pingOneServer(opt) {
return mcPing({
host: opt.ip,
port: opt.port
});
}
};
function removeMinecraftFormatting(text) {
if (typeof text !== "string") return "";
text = text.replace(/§[0-9a-fk-or]/gi, "");
text = text.replace(/<[^>]*>|{[^}]*}|%[^%]+%/g, "");
text = text.replace(
/[\u0000-\u001F\u007F\u0080-\u009F\u200B-\u200D\uFEFF]/g,
""
);
return text.trim();
}
__name(removeMinecraftFormatting, "removeMinecraftFormatting");
// src/utils/mcFormat.ts
var transformText = /* @__PURE__ */ __name((obj, opt) => {
let str = "";
const handlDescriptionFormatting = opt && opt.descriptionFormatting ? removeMinecraftFormatting : (t) => t;
if (obj.favicon) str += `<image src="${obj.favicon}"/>`;
if (obj.name) str = str + `${obj.name}[${obj.address}]
`;
if (obj.description && obj.description.text) {
str = str + `描述:${handlDescriptionFormatting(obj.description.text)}
`;
} else if (typeof obj.description === "string") {
str = str + `描述:${handlDescriptionFormatting(obj.description)}
`;
}
if (obj.latency) str = str + `延迟:${obj.latency}ms
`;
if (obj.version && obj.version.name)
str = str + `版本:${obj.version.name}
`;
if (obj.players) {
const players = obj.players;
if (players.max) str += `在线人数:${players.online}/${players.max}
`;
if (players.sample)
str += `当前在线:${players.sample.map((item) => `${item.name}`).join(",")}
`;
}
if (obj.modinfo && obj.modinfo.modList && obj.modinfo.modList.length) {
str = str + `mod数:${obj.modinfo.modList.length}
`;
} else if (obj.forgeData && obj.forgeData.mods && obj.forgeData.mods.length) {
str = str + `mod数:${obj.forgeData.mods.length}
`;
}
if (obj.rejected) {
if (obj.message) str = str + `错误信息:${obj.message}
`;
if (obj.code) str = str + `请求失败:${obj.code}
`;
if (obj.errno) str = str + `错误代码:${obj.errno}
`;
if (!obj.message && !obj.code && !obj.errno) {
str = str + `服务器地址错误或者服务器未启动
`;
}
}
return str;
}, "transformText");
var mcFormat = /* @__PURE__ */ __name((name2, address, server, options) => {
if (!server) return "";
return transformText(
{
name: name2,
address,
...server
},
options
);
}, "mcFormat");
var serverListFormat = /* @__PURE__ */ __name((serverList) => {
const list = serverList.map((item) => {
let str = "";
str += `服务器名称: ${item.name}
`;
str += `服务器ip: ${item.ip}
`;
str += `服务器端口: ${item.port}
`;
str += `Query查询: ${item.enableQuery ? "是" : "否"}
`;
return str;
});
return list.join("***********************\n");
}, "serverListFormat");
// src/command/index.ts
var getUniqueId = /* @__PURE__ */ __name((session) => {
return session.event.channel.id || session.event.user.id;
}, "getUniqueId");
var registerCommands = /* @__PURE__ */ __name((ctx, config) => {
const mcBot = initMcBot.getInstance();
const { groupKeep } = config;
const mcComand = ctx.command("mc", {
authority: 1
});
mcComand.option("name", "[服务器名称]").action(async (_, arg) => {
try {
const uniqueId = getUniqueId(_.session);
const findArg = groupKeep ? { serverId: uniqueId } : {};
if (arg) {
const server = await mcBot.findServer({
name: arg,
...findArg
});
const oneServer = server[0];
if (!oneServer) return `未找到${arg}服务器`;
if (oneServer.enableQuery) {
try {
const queryResult = await onEnableQuery(oneServer.ip, oneServer.port);
const resultText = queryResultFormat(queryResult, {
description: `${oneServer.name}[${oneServer.ip}:${oneServer.port}]`,
hasForMotd: config.descriptionFormatting
});
return resultText;
} catch (err) {
console.log(err, "enableQuery error");
return `出现错误:${err.message}`;
}
} else {
const address = `${oneServer.ip}:${oneServer.port}`;
let resultText = "";
await mcBot.pingOneServer(oneServer).then((res) => {
resultText = mcFormat(
oneServer.name,
address,
{
...res,
latency: res.latency
},
config
);
}).catch((err) => {
resultText = mcFormat(oneServer.name, address, {
rejected: true,
...err
});
});
return resultText;
}
} else {
const data = await mcBot.pingServerList(findArg, config);
if (!data.length) return "您还没添加任何服务器";
let serverStrlist = [];
data.forEach((item) => {
const { name: name2, address, latency, ...res } = item;
serverStrlist.push(
mcFormat(
name2,
address,
{
...res,
latency
},
config
)
);
});
return serverStrlist.join("***********************\n");
}
} catch (e) {
console.log(e, "出现错误");
}
});
mcComand.subcommand(".set").option("name", "<名称>").option("address", "<地址:端口>").option("enableQuery", "[true/false] 是否启用Query协议").action(async (_, name2, address, enableQuery) => {
const uniqueId = getUniqueId(_.session);
const serverId = groupKeep ? uniqueId : "";
if (!config.adminUsers.includes(String(_.session.event.user.id)))
return "您没有操作服务器的权限";
if (varkeys.includes(name2)) return "服务器名称不合法";
if (_.session.content)
try {
if (!name2 || !address) return "请提供服务器名称和地址:端口";
const [ip, port] = address.split(":");
let formatPort = port ? Number(port) : 25565;
if (!formatPort || formatPort < 0 || formatPort > 65536) {
return "服务器端口不合法";
}
const qeruyPrams = {
name: name2
};
if (groupKeep) qeruyPrams.serverId = serverId;
const findServer = await mcBot.findServer(qeruyPrams);
let server = null;
const parmas = {
name: name2,
ip,
port: formatPort,
enableQuery: Boolean(enableQuery)
};
if (groupKeep) {
parmas.serverId = serverId;
}
if (findServer.length) {
parmas.id = findServer[0].id;
}
server = await mcBot.upsert(parmas);
if (!server) {
return "操作失败";
} else if (server.inserted) {
return "新增成功";
} else if (server.matched) {
return "修改成功";
}
} catch (err) {
console.log(err, "出现错误");
}
});
mcComand.subcommand(".del").option("name", "<名称>").action(async (_, name2) => {
try {
if (!config.adminUsers.includes(String(_.session.event.user.id)))
return "您没有操作服务器的权限";
if (!name2) return "请提供服务器名称";
const uniqueId = getUniqueId(_.session);
const findArg = { name: name2 };
if (groupKeep) findArg.serverId = uniqueId;
const { matched } = await mcBot.delteByName(findArg);
if (matched) {
return "删除成功";
} else {
return "没有此名称的服务器";
}
} catch (err) {
console.log(err, "出现错误");
}
});
mcComand.subcommand(".list").action(async (_) => {
try {
const uniqueId = getUniqueId(_.session);
const prams = {};
if (groupKeep) prams.serverId = uniqueId;
const data = await mcBot.findServer(prams);
return serverListFormat(data);
} catch (err) {
console.log(err, "出现错误");
}
});
mcComand.subcommand(".ping").option("address", "<地址:端口>").option("enableQuery", "[true/false] 是否启用Query协议").action(async (_, address, enableQuery) => {
try {
if (!address) return "请提供服务器的地址:端口";
const [ip, port] = address.split(":");
let formatPort = 25565;
if (port) formatPort = Number(port);
if (enableQuery) {
const queryResult = await onEnableQuery(ip, Number(port));
const resultText = queryResultFormat(queryResult, {
description: "",
hasForMotd: config.descriptionFormatting
});
return resultText;
} else {
let resultText = "";
await mcBot.pingOneServer({ ip, port: formatPort }).then((res) => {
resultText = mcFormat(
"",
address,
{
...res,
latency: res.latency
},
config
);
}).catch((err) => {
resultText = mcFormat("", address, {
rejected: true,
...err
});
});
return resultText;
}
} catch (err) {
console.log(err, "出现错误");
return `出现错误:${err.message}`;
}
});
}, "registerCommands");
// src/index.ts
var name = "mc-status-bot";
var inject = ["database"];
var Config = import_koishi.Schema.object({
adminUsers: import_koishi.Schema.array(import_koishi.Schema.string()).description(
"配置能够操作mc服务器的用户id"
),
groupKeep: import_koishi.Schema.boolean().default(true).description("开启后在哪里添加的服务器就只能在哪里看到(开启/关闭已有的服务器需要重新添加才生效)"),
descriptionFormatting: import_koishi.Schema.boolean().default(true).description("启用后,将移除描述中的颜色代码、插件标签等非纯文本内容。")
});
async function apply(ctx, config) {
initDataBase(ctx);
initMcBot.getInstance(ctx);
registerCommands(ctx, config);
}
__name(apply, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
name
});