Node.js 开发
MSL 基于 Node.js 运行,插件可以引入任何 Node.js 模块来扩展功能。本章将介绍如何在插件中使用 Node.js 模块以及相关的最佳实践。
DANGER
该部分已超出Minecraft的范畴,用于扩展插件功能。本文所提到的其他库的使用方法不确定完全准确,请按照您个人的需求、开发习惯、团队要求等进行调整。
引入 Node.js 模块
使用 plugin_require
在插件中,使用 plugin_require 函数引入 Node.js 模块:
javascript
// 引入 Node.js 内置模块
const fs = plugin_require("fs");
const path = plugin_require("path");
const http = plugin_require("http");
const crypto = plugin_require("crypto");
// 使用模块
const data = fs.readFileSync("./data.txt", "utf-8");
plugin_log("INFO", `文件内容: ${data}`);引入第三方模块
首先在 MSL 项目根目录安装 npm 包:
bash
npm install axios然后在插件中使用:
javascript
const axios = plugin_require("axios");
// 发送 HTTP 请求
async function sendWebhook(url, data) {
try {
const response = await axios.post(url, data);
plugin_log("INFO", `Webhook 发送成功: ${response.status}`);
} catch (error) {
plugin_log("ERROR", `Webhook 发送失败: ${error.message}`);
}
}
// 示例:玩家加入时发送通知
plugin_onEvent("playerJoin", (time, player) => {
sendWebhook("https://your-webhook-url", {
event: "playerJoin",
player: player,
time: time
});
});内置模块示例
文件系统操作 (fs)
javascript
const fs = plugin_require("fs");
const path = plugin_require("path");
// 读取配置文件
function loadConfig() {
const configPath = path.join(__dirname, "config.json");
try {
const data = fs.readFileSync(configPath, "utf-8");
return JSON.parse(data);
} catch (error) {
plugin_log("ERROR", `读取配置失败: ${error.message}`);
return {};
}
}
// 写入日志文件
function writeLog(message) {
const logPath = path.join(__dirname, "plugin.log");
const timestamp = new Date().toISOString();
const logLine = `[${timestamp}] ${message}\n`;
fs.appendFileSync(logPath, logLine);
}
// 使用示例
plugin_onEvent("playerJoin", (time, player) => {
writeLog(`玩家 ${player} 加入了服务器`);
});HTTP 请求 (http/https)
javascript
const http = plugin_require("http");
const https = plugin_require("https");
// 发送 GET 请求
function httpGet(url) {
return new Promise((resolve, reject) => {
const client = url.startsWith("https") ? https : http;
client.get(url, (res) => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => resolve(data));
}).on("error", reject);
});
}
// 发送 POST 请求
function httpPost(url, body) {
return new Promise((resolve, reject) => {
const urlObj = new URL(url);
const client = urlObj.protocol === "https:" ? https : http;
const options = {
hostname: urlObj.hostname,
port: urlObj.port || (urlObj.protocol === "https:" ? 443 : 80),
path: urlObj.pathname + urlObj.search,
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body)
}
};
const req = client.request(options, (res) => {
let data = "";
res.on("data", chunk => data += chunk);
res.on("end", () => resolve(data));
});
req.on("error", reject);
req.write(body);
req.end();
});
}
// 使用示例
plugin_onEvent("playerJoin", async (time, player) => {
try {
// 查询玩家信息
const response = await httpGet(`https://api.mojang.com/users/profiles/minecraft/${player}`);
const data = JSON.parse(response);
plugin_log("INFO", `玩家 UUID: ${data.id}`);
} catch (error) {
plugin_log("ERROR", `查询玩家信息失败: ${error.message}`);
}
});加密 (crypto)
javascript
const crypto = plugin_require("crypto");
// 生成随机字符串
function generateToken(length = 32) {
return crypto.randomBytes(length).toString("hex");
}
// 计算哈希
function hashPassword(password) {
return crypto.createHash("sha256").update(password).digest("hex");
}
// 使用示例
plugin_onEvent("playerJoin", (time, player) => {
const token = generateToken(16);
plugin_log("INFO", `为 ${player} 生成令牌: ${token}`);
});定时任务
MSL 沙箱环境已经封装了 setTimeout 和 setInterval,它们会在插件卸载时自动清除:
javascript
// 定时任务
const intervalId = setInterval(() => {
plugin_log("INFO", "定时任务执行中...");
plugin_executeCommand("time query daytime");
}, 60000); // 每分钟执行一次
// 延迟任务
setTimeout(() => {
plugin_log("INFO", "延迟任务执行");
}, 5000);
// 注意:插件卸载时,这些定时器会自动清除第三方模块示例
使用 axios 发送 HTTP 请求
bash
npm install axiosjavascript
const axios = plugin_require("axios");
// Discord Webhook 通知
async function sendDiscordWebhook(webhookUrl, content) {
try {
await axios.post(webhookUrl, {
content: content,
username: "Minecraft Server",
avatar_url: "https://example.com/avatar.png"
});
plugin_log("INFO", "Discord 通知发送成功");
} catch (error) {
plugin_log("ERROR", `Discord 通知发送失败: ${error.message}`);
}
}
// 玩家加入通知
plugin_onEvent("playerJoin", (time, player) => {
sendDiscordWebhook(
"https://discord.com/api/webhooks/xxx/xxx",
`🎮 **${player}** 加入了服务器`
);
});使用 moment.js 处理时间
bash
npm install momentjavascript
const moment = plugin_require("moment");
// 格式化时间
function formatTime(date) {
return moment(date).format("YYYY-MM-DD HH:mm:ss");
}
// 计算时长
function getPlayDuration(startTime) {
const duration = moment.duration(moment().diff(startTime));
return `${duration.hours()}小时 ${duration.minutes()}分钟`;
}
// 使用示例
const playerJoinTimes = {};
plugin_onEvent("playerJoin", (time, player) => {
playerJoinTimes[player] = moment();
plugin_log("INFO", `${player} 在 ${formatTime(new Date())} 加入`);
});
plugin_onEvent("playerQuit", (time, player) => {
if (playerJoinTimes[player]) {
const duration = getPlayDuration(playerJoinTimes[player]);
plugin_log("INFO", `${player} 游玩了 ${duration}`);
delete playerJoinTimes[player];
}
});使用 sqlite3 存储数据
bash
npm install sqlite3javascript
const sqlite3 = plugin_require("sqlite3").verbose();
const path = plugin_require("path");
// 创建数据库连接
const dbPath = path.join(__dirname, "..", "..", "data", "plugin.db");
const db = new sqlite3.Database(dbPath);
// 初始化表
db.serialize(() => {
db.run(`
CREATE TABLE IF NOT EXISTS player_stats (
player TEXT PRIMARY KEY,
join_count INTEGER DEFAULT 0,
last_join TEXT
)
`);
});
// 更新玩家统计
function updatePlayerStats(player) {
db.run(`
INSERT INTO player_stats (player, join_count, last_join)
VALUES (?, 1, datetime('now'))
ON CONFLICT(player) DO UPDATE SET
join_count = join_count + 1,
last_join = datetime('now')
`, [player], (err) => {
if (err) {
plugin_log("ERROR", `更新玩家统计失败: ${err.message}`);
}
});
}
// 查询玩家统计
function getPlayerStats(player) {
return new Promise((resolve, reject) => {
db.get("SELECT * FROM player_stats WHERE player = ?", [player], (err, row) => {
if (err) reject(err);
else resolve(row);
});
});
}
// 使用示例
plugin_onEvent("playerJoin", async (time, player) => {
updatePlayerStats(player);
try {
const stats = await getPlayerStats(player);
if (stats) {
plugin_log("INFO", `${player} 第 ${stats.join_count} 次加入服务器`);
plugin_executeCommand(`tellraw ${player} {"text":"欢迎回来!这是你第 ${stats.join_count} 次加入","color":"yellow"}`);
}
} catch (error) {
plugin_log("ERROR", `查询玩家统计失败: ${error.message}`);
}
});使用 ws 实现 WebSocket
bash
npm install wsjavascript
const WebSocket = plugin_require("ws");
// 创建 WebSocket 客户端
let ws = null;
function connectWebSocket(url) {
ws = new WebSocket(url);
ws.on("open", () => {
plugin_log("INFO", "WebSocket 连接已建立");
});
ws.on("message", (data) => {
try {
const message = JSON.parse(data.toString());
handleWebSocketMessage(message);
} catch (error) {
plugin_log("ERROR", `解析 WebSocket 消息失败: ${error.message}`);
}
});
ws.on("error", (error) => {
plugin_log("ERROR", `WebSocket 错误: ${error.message}`);
});
ws.on("close", () => {
plugin_log("WARN", "WebSocket 连接已关闭,5秒后重连...");
setTimeout(() => connectWebSocket(url), 5000);
});
}
function handleWebSocketMessage(message) {
switch (message.type) {
case "command":
plugin_executeCommand(message.data);
break;
case "broadcast":
plugin_executeCommand(`say ${message.data}`);
break;
}
}
// 发送消息
function sendWebSocketMessage(data) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify(data));
}
}
// 使用示例
plugin_onEvent("serverDone", () => {
connectWebSocket("wss://your-websocket-server.com");
});
plugin_onEvent("playerJoin", (time, player) => {
sendWebSocketMessage({
type: "playerJoin",
player: player,
time: time
});
});最佳实践
1. 错误处理
javascript
const fs = plugin_require("fs");
function safeReadFile(path) {
try {
return fs.readFileSync(path, "utf-8");
} catch (error) {
plugin_log("ERROR", `读取文件失败: ${error.message}`);
return null;
}
}2. 资源清理
javascript
const db = plugin_require("sqlite3").verbose();
const dbConnection = new db.Database("data.db");
// 注意:MSL 会在插件卸载时自动清除定时器
// 但数据库连接等资源需要手动管理(如果需要)3. 异步操作
javascript
const axios = plugin_require("axios");
// 使用 async/await
async function fetchData(url) {
try {
const response = await axios.get(url);
return response.data;
} catch (error) {
plugin_log("ERROR", `请求失败: ${error.message}`);
return null;
}
}
// 在事件中使用
plugin_onEvent("playerJoin", async (time, player) => {
const data = await fetchData(`https://api.example.com/player/${player}`);
if (data) {
plugin_log("INFO", `获取到玩家数据: ${JSON.stringify(data)}`);
}
});4. 模块缓存
javascript
// 在插件顶部引入模块,避免重复引入
const fs = plugin_require("fs");
const path = plugin_require("path");
const axios = plugin_require("axios");
// 后续直接使用这些模块5. 配置管理
javascript
const fs = plugin_require("fs");
const path = plugin_require("path");
// 加载插件配置
function loadPluginConfig() {
const configPath = path.join(__dirname, "..", "..", "plugins", "myplugin-config.json");
try {
if (fs.existsSync(configPath)) {
const data = fs.readFileSync(configPath, "utf-8");
return JSON.parse(data);
}
} catch (error) {
plugin_log("ERROR", `加载配置失败: ${error.message}`);
}
return {}; // 返回默认配置
}
const config = loadPluginConfig();注意事项
1. 模块路径
plugin_require 使用 Node.js 的标准模块解析机制:
- 内置模块:直接使用名称,如
"fs"、"http" - 第三方模块:使用包名,如
"axios"、"moment" - 本地模块:不支持相对路径引入
2. 异步操作
Node.js 的异步操作(如文件读写、网络请求)不会阻塞 MSL 主进程,但要注意:
- 回调函数中的错误需要自行捕获
- 长时间运行的操作可能影响其他插件
3. 安全性
使用 plugin_require 时要注意:
- 不要引入可能存在安全漏洞的包
- 不要在插件中存储敏感信息(如密码、密钥)
- 对外部输入进行验证和清理
下一步
- API 参考 - 查看完整的 API 文档
- API 参考 - plugin_require - 查看 plugin_require 详细文档