@arr0wing/koishi-plugin-minecraft-sync-msg 3.0.2

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Twiyin0
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/lib/index.d.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { Context, Schema } from 'koishi';
2
+ import { wsConf, rconConf } from './values';
3
+ export declare const name = "minecraft-sync-msg";
4
+ declare class MinecraftSyncMsg {
5
+ private ctx;
6
+ private config;
7
+ private ws;
8
+ private rcon;
9
+ private isDisposing;
10
+ private reconnectAttempts;
11
+ private reconnectIntervalId;
12
+ private pl_fork;
13
+ private enUS;
14
+ private zhCN;
15
+ constructor(ctx: Context, config: MinecraftSyncMsg.Config);
16
+ private initialize;
17
+ private setupRcon;
18
+ private connectToRcon;
19
+ private setupWebSocket;
20
+ private connectWebSocket;
21
+ private bindWebSocketEvents;
22
+ private handleWsOpen;
23
+ private handleWsMessage;
24
+ private handleWsClose;
25
+ private handleWsError;
26
+ private reconnectWebSocket;
27
+ private clearReconnectInterval;
28
+ private setupMessageHandler;
29
+ private isValidChannel;
30
+ private isMessageCommand;
31
+ private isRconCommand;
32
+ private handleMessageCommand;
33
+ private handleRconCommand;
34
+ private sendRconCommand;
35
+ private extractAndRemoveColor;
36
+ private broadcastToChannels;
37
+ private setupDisposeHandler;
38
+ }
39
+ declare namespace MinecraftSyncMsg {
40
+ interface Config extends wsConf, rconConf {
41
+ sendToChannel: string[];
42
+ sendprefix: string;
43
+ cmdprefix: string;
44
+ hideConnect: boolean;
45
+ locale: string;
46
+ }
47
+ const Config: Schema<Config>;
48
+ const usage = "\n \u63D2\u4EF6\u4F7F\u7528\u8BE6\u60C5\u8BF7\u770B [v2.x](https://blog.iin0.cn/views/myblog/mc/wskoishitomc.html) \n *** \u6CE8\u610F *** \n * \u547D\u4EE4\u53D1\u9001\u524D\u7F00(\u4E0D\u80FD\u4E3A\u7A7A)\u548C\u6D88\u606F\u53D1\u9001\u524D\u7F00(\u53EF\u4EE5\u4E3A\u7A7A)\u4E0D\u80FD\u76F8\u540C\n * forge\u7AEF\u4E0D\u652F\u6301PlayerCommandPreprocessEvent\u4E8B\u4EF6\n * * \u539F\u7248\u7AEF\u4EC5\u652F\u6301\u804A\u5929\u3001\u52A0\u5165\u3001\u79BB\u5F00\u4E8B\u4EF6\n * sendToChannel\u7684\u683C\u5F0F\u4E3A{platform}:{groupId},\u5982\uFF1A`discord:123456`\n * v2.1.0-beta\u53EF\u4EE5\u901A\u8FC7`\u672C\u5730\u5316`\u81EA\u5B9A\u4E49\u5BF9\u5E94\u4E8B\u4EF6\u53D1\u9001\u683C\u5F0F\n - action\u8282\u70B9\u7684{0}\u662F\u73A9\u5BB6\u540D\u79F0{1}\u662F\u6D88\u606F\n - message\u8282\u70B9\u4E2D\u7684{0}\u662F\u5E73\u53F0{1}\u662F\u7528\u6237\u540D\n ";
49
+ }
50
+ export default MinecraftSyncMsg;
package/lib/index.js ADDED
@@ -0,0 +1,639 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ var __export = (target, all) => {
7
+ for (var name2 in all)
8
+ __defProp(target, name2, { get: all[name2], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ default: () => src_default,
24
+ name: () => name
25
+ });
26
+ module.exports = __toCommonJS(src_exports);
27
+ var import_koishi3 = require("koishi");
28
+ var import_ws2 = require("ws");
29
+ var import_rcon_client = require("rcon-client");
30
+
31
+ // src/values.tsx
32
+ var import_koishi = require("koishi");
33
+ var mcEvent = /* @__PURE__ */ ((mcEvent2) => {
34
+ mcEvent2[mcEvent2["AsyncPlayerChatEvent"] = 1] = "AsyncPlayerChatEvent";
35
+ mcEvent2[mcEvent2["PlayerCommandPreprocessEvent"] = 2] = "PlayerCommandPreprocessEvent";
36
+ mcEvent2[mcEvent2["PlayerDeathEvent"] = 4] = "PlayerDeathEvent";
37
+ mcEvent2[mcEvent2["PlayerJoinEvent"] = 8] = "PlayerJoinEvent";
38
+ mcEvent2[mcEvent2["PlayerQuitEvent"] = 16] = "PlayerQuitEvent";
39
+ mcEvent2[mcEvent2["PlayerAchievementEvent"] = 32] = "PlayerAchievementEvent";
40
+ return mcEvent2;
41
+ })(mcEvent || {});
42
+ var wsConf = import_koishi.Schema.object({
43
+ wsServer: import_koishi.Schema.union(["客户端", "服务端"]).default("客户端").description("Websocket端选择"),
44
+ wsHost: import_koishi.Schema.string().default("127.0.0.1").description("websocket服务器的地址(服务器监听地址)"),
45
+ wsPort: import_koishi.Schema.number().default(8080).description("websocket服务器的端口(服务器监听端口)"),
46
+ Token: import_koishi.Schema.string().description("websocket服务器的验证Token"),
47
+ serverName: import_koishi.Schema.string().description("鹊桥配置文件中对应的server_name"),
48
+ joinMsg: import_koishi.Schema.string().default("[客户端] 连接成功!").description("连接服务的成功时发送的消息(&颜色单词&可以设置颜色)"),
49
+ event: import_koishi.Schema.bitset(mcEvent).description("选择需要监听的事件"),
50
+ maxReconnectCount: import_koishi.Schema.number().default(20).description("[仅客户端生效]客户端最大重连次数"),
51
+ maxReconnectInterval: import_koishi.Schema.number().default(6e4).description("[仅客户端生效]客户端单次重连时间(ms)")
52
+ }).collapse().description("Websocket配置");
53
+ var rconConf = import_koishi.Schema.object({
54
+ rconEnable: import_koishi.Schema.boolean().default(true).description("开启RCON功能"),
55
+ rconServerHost: import_koishi.Schema.string().default("127.0.0.1").description("rcon服务器地址"),
56
+ rconServerPort: import_koishi.Schema.number().default(25575).description("rcon服务器地址端口"),
57
+ rconPassword: import_koishi.Schema.string().role("secret").description("rcon服务器的密码(推荐设置)"),
58
+ alluser: import_koishi.Schema.boolean().default(false).description("所有用户可用(开启后下面的配置失效)"),
59
+ superuser: import_koishi.Schema.array(String).description("超级用户的ID,可以使用RCON所有命令"),
60
+ commonCmd: import_koishi.Schema.array(String).default(["list", "spigot:tps"]).description("普通用户可以使用的命令"),
61
+ cannotCmd: import_koishi.Schema.array(String).default(["restart", "stop"]).description("不能使用的命令")
62
+ }).collapse().description("RCON配置");
63
+ function getSubscribedEvents(binaryInput) {
64
+ const subscribedEvents = [];
65
+ const eventValues = Object.values(mcEvent).filter((value) => typeof value === "number");
66
+ const eventNames = Object.keys(mcEvent).filter((key) => isNaN(Number(key)));
67
+ for (let i = 0; i < eventValues.length; i++) {
68
+ if ((binaryInput & eventValues[i]) !== 0) {
69
+ subscribedEvents.push(eventNames[i]);
70
+ }
71
+ }
72
+ return subscribedEvents;
73
+ }
74
+ __name(getSubscribedEvents, "getSubscribedEvents");
75
+ var eventMap = {
76
+ AsyncPlayerChatEvent: "AsyncPlayerChatEvent",
77
+ ServerMessageEvent: "AsyncPlayerChatEvent",
78
+ ServerChatEvent: "AsyncPlayerChatEvent",
79
+ NeoServerChatEvent: "AsyncPlayerChatEvent",
80
+ MinecraftPlayerChatEvent: "AsyncPlayerChatEvent",
81
+ BaseChatEvent: "AsyncPlayerChatEvent",
82
+ PlayerChatEvent: "AsyncPlayerChatEvent",
83
+ PlayerCommandPreprocessEvent: "PlayerCommandPreprocessEvent",
84
+ ServerCommandMessageEvent: "PlayerCommandPreprocessEvent",
85
+ CommandEvent: "PlayerCommandPreprocessEvent",
86
+ NeoCommandEvent: "PlayerCommandPreprocessEvent",
87
+ BasePlayerCommandEvent: "PlayerCommandPreprocessEvent",
88
+ PlayerCommandEvent: "PlayerCommandPreprocessEvent",
89
+ PlayerDeathEvent: "PlayerDeathEvent",
90
+ NeoPlayerDeathEvent: "PlayerDeathEvent",
91
+ ServerLivingEntityAfterDeathEvent: "PlayerDeathEvent",
92
+ BaseDeathEvent: "PlayerDeathEvent",
93
+ PlayerJoinEvent: "PlayerJoinEvent",
94
+ ServerPlayConnectionJoinEvent: "PlayerJoinEvent",
95
+ PlayerLoggedInEvent: "PlayerJoinEvent",
96
+ NeoPlayerLoggedInEvent: "PlayerJoinEvent",
97
+ MinecraftPlayerJoinEvent: "PlayerJoinEvent",
98
+ BaseJoinEvent: "PlayerJoinEvent",
99
+ PlayerQuitEvent: "PlayerQuitEvent",
100
+ ServerPlayConnectionDisconnectEvent: "PlayerQuitEvent",
101
+ PlayerLoggedOutEvent: "PlayerQuitEvent",
102
+ NeoPlayerLoggedOutEvent: "PlayerQuitEvent",
103
+ MinecraftPlayerQuitEvent: "PlayerQuitEvent",
104
+ BaseQuitEvent: "PlayerQuitEvent",
105
+ VelocityDisconnectEvent: "PlayerQuitEvent",
106
+ VelocityCommandExecuteEvent: "PlayerCommandPreprocessEvent",
107
+ VelocityLoginEvent: "PlayerJoinEvent",
108
+ VelocityPlayerChatEvent: "AsyncPlayerChatEvent",
109
+ PlayerAchievementEvent: "PlayerAchievementEvent"
110
+ };
111
+ function getListeningEvent(input) {
112
+ if (typeof input === "string") {
113
+ input = [input];
114
+ }
115
+ const uniqueEvents = /* @__PURE__ */ new Set();
116
+ for (const event of input) {
117
+ if (eventMap[event]) {
118
+ uniqueEvents.add(eventMap[event]);
119
+ }
120
+ }
121
+ return Array.from(uniqueEvents)[0];
122
+ }
123
+ __name(getListeningEvent, "getListeningEvent");
124
+
125
+ // src/mcwss.tsx
126
+ var import_koishi2 = require("koishi");
127
+ var import_ws = require("ws");
128
+
129
+ // src/locale/zh-CN.yml
130
+ var zh_CN_default = { "minecraft-sync-msg": { connection: { connectedToWS: "Websocket服务器连接成功", disconnectedFromWS: "与Websocket服务器断开连接!", connectionErrorWS: "与Websocket服务器通信错误" }, action: { PlayerJoinEvent: "[加入]{0} 加入了服务器!", PlayerCommandPreprocessEvent: "[指令]{0} 执行了指令: {1}", PlayerDeathEvent: "[死亡]{0} 与山长眠!", AsyncPlayerChatEvent: "[聊天]{0} 说:{1}", PlayerQuitEvent: "[离开]{0} 离开了服务器!", PlayerAchievementEvent: "[成就]{0} 获得了成就:{1}" }, message: { MCReceivePrefix: "[{0}]({2}){1}: " } } };
131
+
132
+ // src/locale/en-US.yml
133
+ var en_US_default = { "minecraft-sync-msg": { connection: { connectedToWS: "Websocket connection up!", disconnectedFromWS: "Websocket connection down!", connectionErrorWS: "Websocket connection error!" }, action: { PlayerJoinEvent: "[join] Player {0} joined the game", PlayerCommandPreprocessEvent: "[command] Player {0} executed command: {1}", PlayerDeathEvent: "[death] Player {0} meet the god", AsyncPlayerChatEvent: "[chat] Player {0} said: {1}", PlayerQuitEvent: "[quit] Player {0} left the game", PlayerAchievementEvent: "[achievement]{0} has achieved: {1}" }, message: { MCReceivePrefix: "[{0}]({2}){1}: " } } };
134
+
135
+ // src/mcwss.tsx
136
+ var mcWss = class {
137
+ static {
138
+ __name(this, "mcWss");
139
+ }
140
+ conf;
141
+ logger = new import_koishi2.Logger("Minecraft-sync-msg-Wss");
142
+ wss;
143
+ ctx;
144
+ connectedClients = /* @__PURE__ */ new Set();
145
+ constructor(ctx, cfg) {
146
+ this.conf = cfg;
147
+ this.ctx = ctx;
148
+ ctx.on("ready", async () => {
149
+ this.ctx.i18n.define("zh-CN", zh_CN_default);
150
+ this.ctx.i18n.define("en-US", en_US_default);
151
+ this.wss = new import_ws.WebSocketServer({ host: cfg.wsHost, port: cfg.wsPort });
152
+ ctx.logger.info(`Websocket服务器已启动 ws://${cfg.wsHost}:${cfg.wsPort}`);
153
+ this.setupWebSocketHandlers();
154
+ });
155
+ this.setupMessageHandler();
156
+ ctx.on("dispose", async () => {
157
+ if (this.wss) {
158
+ this.wss.close();
159
+ }
160
+ this.connectedClients.clear();
161
+ });
162
+ }
163
+ setupWebSocketHandlers() {
164
+ this.wss.on("connection", (ws, req) => {
165
+ let msgData = {
166
+ "api": "broadcast",
167
+ data: {
168
+ "message": [
169
+ {
170
+ text: extractAndRemoveColor(this.conf.joinMsg).output,
171
+ color: extractAndRemoveColor(this.conf.joinMsg).color ? extractAndRemoveColor(this.conf.joinMsg).color : "gold"
172
+ }
173
+ ]
174
+ }
175
+ };
176
+ ws.send(JSON.stringify(msgData));
177
+ this.ctx.logger.success("客户端连接成功!");
178
+ const headers = req.headers;
179
+ if (!this.verifyHeaders(headers).valid) {
180
+ this.ctx.logger.error("请求头验证失败!");
181
+ if (!this.conf.hideConnect) this.ctx.bots.forEach(async (bot) => {
182
+ const channels = this.conf.sendToChannel.filter((str) => str.includes(`${bot.platform}`)).map((str) => str.replace(`${bot.platform}:`, ""));
183
+ bot.broadcast(channels, "Websocket请求头验证失败!", 0);
184
+ });
185
+ ws.close(1008, "Invalid header!");
186
+ return;
187
+ }
188
+ if (!this.conf.hideConnect) this.ctx.bots.forEach(async (bot) => {
189
+ const channels = this.conf.sendToChannel.filter((str) => str.includes(`${bot.platform}`)).map((str) => str.replace(`${bot.platform}:`, ""));
190
+ if (!this.conf.hideConnect) bot.broadcast(channels, this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectedToWS`], {}), 0);
191
+ });
192
+ this.connectedClients.add(ws);
193
+ ws.on("message", async (buffer) => {
194
+ this.ctx.logger.info(`收到来自客户端的消息: ${buffer.toString()}`);
195
+ const data = JSON.parse(buffer.toString());
196
+ let eventName = data.event_name ? getListeningEvent(data.event_name) : "";
197
+ if (!getSubscribedEvents(this.conf.event).includes(eventName)) return;
198
+ let sendMsg = import_koishi2.h.unescape(data.message ? data.message : data.command ? data.command : "").replaceAll("&amp;", "&").replaceAll(/<\/?template>/gi, "").replaceAll(/§./g, "");
199
+ sendMsg = sendMsg.replaceAll(/<json.*\/>/gi, "<json消息>");
200
+ const imageMatch = sendMsg.match(/(https?|file):\/\/.*\.(jpg|jpeg|webp|ico|gif|jfif|bmp|png)/gi);
201
+ const sendImage = imageMatch?.[0];
202
+ if (sendImage) {
203
+ sendMsg = sendMsg.replace(sendImage, `<img src="${sendImage}" />`);
204
+ }
205
+ if (eventName === "PlayerAchievementEvent" && data.player) {
206
+ sendMsg = this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.action.${eventName}`], [data.player?.nickname, data.achievement.text]);
207
+ } else
208
+ sendMsg = this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.action.${eventName}`], [data.player?.nickname, sendMsg]);
209
+ if (data.server_name)
210
+ this.ctx.bots.forEach(async (bot) => {
211
+ const channels = this.conf.sendToChannel.filter((str) => str.includes(`${bot.platform}`)).map((str) => str.replace(`${bot.platform}:`, ""));
212
+ sendMsg ? bot.broadcast(channels, sendMsg, 0) : "";
213
+ });
214
+ });
215
+ ws.on("error", (err) => {
216
+ ws?.close();
217
+ if (!this.conf.hideConnect) this.ctx.bots.forEach(async (bot) => {
218
+ const channels = this.conf.sendToChannel.filter((str) => str.includes(`${bot.platform}`)).map((str) => str.replace(`${bot.platform}:`, ""));
219
+ bot.broadcast(channels, this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectionErrorWS`], {}), 0);
220
+ });
221
+ this.ctx.logger.error(this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectionErrorWS`], {}), err);
222
+ });
223
+ ws.on("close", () => {
224
+ this.connectedClients.delete(ws);
225
+ if (!this.conf.hideConnect) this.ctx.bots.forEach(async (bot) => {
226
+ const channels = this.conf.sendToChannel.filter((str) => str.includes(`${bot.platform}`)).map((str) => str.replace(`${bot.platform}:`, ""));
227
+ bot.broadcast(channels, this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.disconnectedFromWS`], {}), 0);
228
+ });
229
+ this.ctx.logger.error(this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.disconnectedFromWS`], {}));
230
+ });
231
+ });
232
+ }
233
+ setupMessageHandler() {
234
+ let imgurl = "<unknown image url>";
235
+ this.ctx.on("message", async (session) => {
236
+ if (session.content.includes("<img") && import_koishi2.h.select(session.content, "img")[0]?.type === "img" && import_koishi2.h.select(session.content, "img")[0]?.attrs?.src) {
237
+ imgurl = import_koishi2.h.select(session.content, "img")[0].attrs.src;
238
+ }
239
+ if (this.conf.sendToChannel.includes(`${session.platform}:${session.channelId}`) || session.platform === "sandbox") {
240
+ if (session.content.startsWith(this.conf.sendprefix) && session.content !== this.conf.sendprefix) {
241
+ let msg = session.content.replaceAll("&amp;", "&").replaceAll(/<\/?template>/gi, "").replace(this.conf.sendprefix, "").replaceAll(/<json.*\/>/gi, "<json消息>").replaceAll(/<video.*\/>/gi, "<视频消息>").replaceAll(/<audio.*\/>/gi, "<音频消息>").replaceAll(/<img.*\/>/gi, `[[CICode,url=${imgurl}]]`).replaceAll(/<at.*\/>/gi, `@[${import_koishi2.h.select(session.content, "at")[0]?.attrs?.name ? import_koishi2.h.select(session.content, "at")[0]?.attrs?.name : import_koishi2.h.select(session.content, "at")[0]?.attrs?.id}]`);
242
+ const data = await session.bot.internal.getGroupMemberInfo(session.guildId, session.userId);
243
+ if (this.connectedClients.size > 0) {
244
+ let msgData = {
245
+ "api": "broadcast",
246
+ data: {
247
+ "message": [
248
+ {
249
+ // text: `(${session.platform})[${session.event.user.name}] ` + extractAndRemoveColor(msg).output,
250
+ text: this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], ["minecraft-sync-msg.message.MCReceivePrefix"], [session.platform, data.card || data.nickname, session.userId]).map((element) => element.attrs?.content).join("") + extractAndRemoveColor(msg).output,
251
+ color: extractAndRemoveColor(msg).color ? extractAndRemoveColor(msg).color : "white"
252
+ }
253
+ ]
254
+ }
255
+ };
256
+ let sent = false;
257
+ this.connectedClients.forEach((client) => {
258
+ if (client.readyState === import_ws.WebSocket.OPEN) {
259
+ try {
260
+ client.send(JSON.stringify(msgData));
261
+ sent = true;
262
+ } catch (err) {
263
+ this.ctx.logger.error(`聊天消息发送到WebSocket客户端失败: ${err}`);
264
+ }
265
+ }
266
+ });
267
+ if (!sent) {
268
+ session.send(this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectionErrorWS`], {}));
269
+ }
270
+ } else {
271
+ session.send(this.ctx.i18n.render([this.conf.locale ? this.conf.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectionErrorWS`], {}));
272
+ }
273
+ }
274
+ }
275
+ });
276
+ }
277
+ verifyHeaders(headers) {
278
+ const authHeader = headers["authorization"];
279
+ const selfNameHeader = headers["x-self-name"];
280
+ const clientOriginHeader = headers["x-client-origin"];
281
+ if (!authHeader) {
282
+ this.logger.error("Missing authorization header");
283
+ return { valid: false };
284
+ }
285
+ const token = authHeader.split(" ")[1];
286
+ if (token !== this.conf.Token) {
287
+ this.logger.error("Token is invalid!");
288
+ return { valid: false };
289
+ }
290
+ if (!selfNameHeader) {
291
+ this.logger.error("Missing x-self-name header");
292
+ return { valid: false };
293
+ }
294
+ if (selfNameHeader !== this.conf.serverName) {
295
+ this.logger.error("Invalid x-self-name");
296
+ return { valid: false };
297
+ }
298
+ const clientOrigin = clientOriginHeader;
299
+ return { valid: true, clientOrigin };
300
+ }
301
+ };
302
+ function extractAndRemoveColor(input) {
303
+ const regex = /&(\w+)&/;
304
+ const match = input.match(regex);
305
+ if (match) {
306
+ const color = match[1];
307
+ const output = input.replace(regex, "");
308
+ return { output, color };
309
+ }
310
+ return { output: input, color: "" };
311
+ }
312
+ __name(extractAndRemoveColor, "extractAndRemoveColor");
313
+ ((mcWss2) => {
314
+ mcWss2.Config = import_koishi2.Schema.intersect([
315
+ wsConf,
316
+ import_koishi2.Schema.object({
317
+ sendToChannel: import_koishi2.Schema.array(String).description("消息发送到目标群组格式{paltform}:{groupId}"),
318
+ sendprefix: import_koishi2.Schema.string().default(".#").description("消息发送前缀(不可与命令发送前缀相同)"),
319
+ hideConnect: import_koishi2.Schema.boolean().default(true).description("关闭连接成功/失败提示"),
320
+ locale: import_koishi2.Schema.union(["zh-CN", "en-US"]).default("zh-CN").description("本地化语言选择,zh_CN为中文,en-US为英文")
321
+ }).description("基础配置")
322
+ ]);
323
+ })(mcWss || (mcWss = {}));
324
+ var mcwss_default = mcWss;
325
+
326
+ // src/index.tsx
327
+ var name = "minecraft-sync-msg";
328
+ var logger = new import_koishi3.Logger("minecraft-sync-msg");
329
+ var MinecraftSyncMsg = class _MinecraftSyncMsg {
330
+ constructor(ctx, config) {
331
+ this.ctx = ctx;
332
+ this.config = config;
333
+ this.initialize();
334
+ }
335
+ static {
336
+ __name(this, "MinecraftSyncMsg");
337
+ }
338
+ ws;
339
+ rcon;
340
+ isDisposing = false;
341
+ reconnectAttempts = 0;
342
+ reconnectIntervalId = null;
343
+ pl_fork;
344
+ enUS;
345
+ zhCN;
346
+ initialize() {
347
+ this.setupRcon();
348
+ this.setupWebSocket();
349
+ this.setupMessageHandler();
350
+ this.setupDisposeHandler();
351
+ this.ctx.i18n.define("zh-CN", zh_CN_default);
352
+ this.ctx.i18n.define("en-US", en_US_default);
353
+ }
354
+ setupRcon() {
355
+ if (!this.config.rconEnable) return;
356
+ this.rcon = new import_rcon_client.Rcon({
357
+ host: this.config.rconServerHost,
358
+ port: this.config.rconServerPort,
359
+ password: this.config.rconPassword
360
+ });
361
+ this.connectToRcon().catch((err) => {
362
+ logger.error("RCON服务器连接失败:", err);
363
+ });
364
+ }
365
+ async connectToRcon() {
366
+ try {
367
+ await this.rcon.connect();
368
+ logger.info("已连接到RCON服务器");
369
+ } catch (err) {
370
+ logger.error("连接到RCON服务器时发生错误:", err);
371
+ throw err;
372
+ }
373
+ }
374
+ setupWebSocket() {
375
+ if (this.config.wsServer === "服务端") {
376
+ this.pl_fork = this.ctx.plugin(mcwss_default, this.config);
377
+ return;
378
+ } else
379
+ this.connectWebSocket();
380
+ }
381
+ connectWebSocket() {
382
+ const headers = {
383
+ "x-self-name": this.config.serverName,
384
+ "Authorization": `Bearer ${this.config.Token}`,
385
+ "x-client-origin": "NOTkoishi"
386
+ };
387
+ this.ws = new import_ws2.WebSocket(`ws://${this.config.wsHost}:${this.config.wsPort}/minecraft/ws`, {
388
+ headers
389
+ });
390
+ this.bindWebSocketEvents();
391
+ }
392
+ bindWebSocketEvents() {
393
+ if (!this.ws) return;
394
+ this.ws.on("open", () => this.handleWsOpen());
395
+ this.ws.on("message", (buffer) => this.handleWsMessage(buffer));
396
+ this.ws.on("close", () => this.handleWsClose());
397
+ this.ws.on("error", (err) => this.handleWsError(err));
398
+ }
399
+ handleWsOpen() {
400
+ logger.info(this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectedToWS`], {}));
401
+ if (!this.config.hideConnect) {
402
+ this.broadcastToChannels(this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectedToWS`], {}));
403
+ }
404
+ const msgData = {
405
+ "api": "broadcast",
406
+ "data": {
407
+ "message": [
408
+ {
409
+ "text": this.extractAndRemoveColor(this.config.joinMsg).output,
410
+ "color": this.extractAndRemoveColor(this.config.joinMsg).color || "gold"
411
+ }
412
+ ]
413
+ }
414
+ };
415
+ this.ws?.send(JSON.stringify(msgData));
416
+ }
417
+ handleWsMessage(buffer) {
418
+ const dataStr = buffer.toString();
419
+ let data;
420
+ try {
421
+ data = JSON.parse(dataStr);
422
+ } catch (err) {
423
+ logger.error("Failed to parse WebSocket message:", err);
424
+ return;
425
+ }
426
+ const eventName = data.event_name ? getListeningEvent(data.event_name) : "";
427
+ if (!getSubscribedEvents(this.config.event).includes(eventName)) return;
428
+ let sendMsg = import_koishi3.h.unescape(data.message ? data.message : data.command ? data.command : "").replaceAll("&amp;", "&").replaceAll(/<\/?template>/gi, "").replaceAll(/§./g, "");
429
+ sendMsg = sendMsg.replaceAll(/<json.*\/>/gi, "<json消息>");
430
+ const imageMatch = sendMsg.match(/(https?|file):\/\/.*\.(jpg|jpeg|webp|ico|gif|jfif|bmp|png)/gi);
431
+ const sendImage = imageMatch?.[0];
432
+ if (sendImage) {
433
+ sendMsg = sendMsg.replace(sendImage, `<img src="${sendImage}" />`);
434
+ }
435
+ if (eventName === "PlayerAchievementEvent" && data.player) {
436
+ sendMsg = this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.action.${eventName}`], [data.player?.nickname, data.achievement.text]);
437
+ } else
438
+ sendMsg = this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.action.${eventName}`], [data.player?.nickname, sendMsg]);
439
+ if (data.server_name && sendMsg) {
440
+ this.broadcastToChannels(import_koishi3.h.parse(sendMsg));
441
+ }
442
+ }
443
+ handleWsClose() {
444
+ if (this.isDisposing) return;
445
+ if (!this.config.hideConnect) {
446
+ this.broadcastToChannels(this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.connection.disconnectedFromWS`], {}));
447
+ }
448
+ logger.error(this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.connection.disconnectedFromWS`], {}));
449
+ this.ws = void 0;
450
+ this.reconnectWebSocket();
451
+ }
452
+ handleWsError(err) {
453
+ if (this.isDisposing) return;
454
+ if (!this.config.hideConnect) {
455
+ this.broadcastToChannels(this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectionErrorWS`], {}));
456
+ }
457
+ logger.error(this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], [`minecraft-sync-msg.connection.connectionErrorWS`], {}), err);
458
+ }
459
+ async reconnectWebSocket() {
460
+ this.clearReconnectInterval();
461
+ this.reconnectIntervalId = setInterval(async () => {
462
+ if (this.reconnectAttempts >= this.config.maxReconnectCount) {
463
+ logger.error(`已达到最大重连次数 (${this.config.maxReconnectCount} 次),停止重连。`);
464
+ this.clearReconnectInterval();
465
+ return;
466
+ }
467
+ this.reconnectAttempts++;
468
+ logger.info(`尝试第 ${this.reconnectAttempts} 次重连...`);
469
+ try {
470
+ const headers = {
471
+ "x-self-name": this.config.serverName,
472
+ "Authorization": `Bearer ${this.config.Token}`,
473
+ "x-client-origin": "koishi"
474
+ };
475
+ const ws = new import_ws2.WebSocket(`ws://${this.config.wsHost}:${this.config.wsPort}/minecraft/ws`, {
476
+ headers
477
+ });
478
+ ws.on("open", () => {
479
+ logger.info("WebSocket 重连成功");
480
+ this.clearReconnectInterval();
481
+ this.ws = ws;
482
+ this.bindWebSocketEvents();
483
+ });
484
+ ws.on("error", (err) => {
485
+ logger.error("重连时发生错误:", err);
486
+ ws?.close();
487
+ });
488
+ ws.on("close", () => {
489
+ if (!this.isDisposing) {
490
+ logger.info("WebSocket 再次断开,将继续尝试重连...");
491
+ }
492
+ });
493
+ } catch (err) {
494
+ logger.error("创建WebSocket时发生错误:", err);
495
+ if (this.reconnectAttempts >= this.config.maxReconnectCount) {
496
+ this.clearReconnectInterval();
497
+ }
498
+ }
499
+ }, this.config.maxReconnectInterval);
500
+ }
501
+ clearReconnectInterval() {
502
+ if (this.reconnectIntervalId) {
503
+ clearInterval(this.reconnectIntervalId);
504
+ this.reconnectIntervalId = null;
505
+ }
506
+ this.reconnectAttempts = 0;
507
+ }
508
+ setupMessageHandler() {
509
+ this.ctx.on("message", async (session) => {
510
+ if (!this.isValidChannel(session)) return;
511
+ if (this.isMessageCommand(session)) {
512
+ await this.handleMessageCommand(session);
513
+ }
514
+ if (this.isRconCommand(session)) {
515
+ await this.handleRconCommand(session);
516
+ }
517
+ });
518
+ }
519
+ isValidChannel(session) {
520
+ return this.config.sendToChannel.includes(`${session.platform}:${session.channelId}`) || session.platform === "sandbox";
521
+ }
522
+ isMessageCommand(session) {
523
+ return session.content.startsWith(this.config.sendprefix) && session.content !== this.config.sendprefix;
524
+ }
525
+ isRconCommand(session) {
526
+ return this.config.rconEnable && this.config.cmdprefix && session.content.startsWith(this.config.cmdprefix) && session.content !== this.config.cmdprefix;
527
+ }
528
+ async handleMessageCommand(session) {
529
+ let imgurl = "<unknown image url>";
530
+ if (session.content.includes("<img") && import_koishi3.h.select(session.content, "img")[0]?.type === "img" && import_koishi3.h.select(session.content, "img")[0]?.attrs?.src) {
531
+ imgurl = import_koishi3.h.select(session.content, "img")[0].attrs.src;
532
+ }
533
+ let msg = session.content.replaceAll("&amp;", "&").replaceAll(/<\/?template>/gi, "").replace(this.config.sendprefix, "").replaceAll(/<json.*\/>/gi, "<json消息>").replaceAll(/<video.*\/>/gi, "<视频消息>").replaceAll(/<audio.*\/>/gi, "<音频消息>").replaceAll(/<img.*\/>/gi, `[[CICode,url=${imgurl}]]`).replaceAll(/<at.*\/>/gi, `@[${import_koishi3.h.select(session.content, "at")[0]?.attrs?.name ? import_koishi3.h.select(session.content, "at")[0]?.attrs?.name : import_koishi3.h.select(session.content, "at")[0]?.attrs?.id}]`);
534
+ try {
535
+ const { output, color } = this.extractAndRemoveColor(msg);
536
+ const username = await session.bot.internal.getGroupMemberInfo(session.guildId, session.userId);
537
+ const msgData = {
538
+ "api": "broadcast",
539
+ "data": {
540
+ "message": [
541
+ {
542
+ "text": this.ctx.i18n.render([this.config.locale ? this.config.locale : "zh-CN"], ["minecraft-sync-msg.message.MCReceivePrefix"], [session.platform, username.card || username.nickname, session.userId]).map((element) => element.attrs.content).join("") + output,
543
+ "color": color || "white"
544
+ }
545
+ ]
546
+ }
547
+ };
548
+ this.ws?.send(JSON.stringify(msgData));
549
+ } catch (err) {
550
+ logger.error("[minecraft-sync-msg] 消息发送到WebSocket服务端失败", err);
551
+ }
552
+ }
553
+ async handleRconCommand(session) {
554
+ const cmd = session.content.replaceAll("&amp;", "§").replaceAll("&", "§").replaceAll(this.config.cmdprefix, "");
555
+ let response;
556
+ if (this.config.alluser) {
557
+ response = await this.sendRconCommand(cmd);
558
+ } else {
559
+ if (this.config.superuser.includes(session.userId)) {
560
+ response = cmd.includes(this.config.cannotCmd) ? "危险命令,禁止使用" : await this.sendRconCommand(cmd);
561
+ response = response || "该命令无反馈";
562
+ } else if (cmd.includes(this.config.commonCmd)) {
563
+ response = this.config.cannotCmd.includes(cmd) ? "危险命令,禁止使用" : await this.sendRconCommand(cmd);
564
+ response = response || "该命令无反馈";
565
+ } else {
566
+ response = "无权使用该命令";
567
+ }
568
+ }
569
+ session.send(response?.replaceAll(/§./g, "") || "");
570
+ }
571
+ async sendRconCommand(command) {
572
+ try {
573
+ const response = await this.rcon.send(command);
574
+ return response;
575
+ } catch (err) {
576
+ logger.error("发送RCON命令时发生错误:", err);
577
+ throw err;
578
+ }
579
+ }
580
+ extractAndRemoveColor(input) {
581
+ const regex = /&(\w+)&/;
582
+ const match = input.match(regex);
583
+ if (match) {
584
+ const color = match[1];
585
+ const output = input.replace(regex, "");
586
+ return { output, color };
587
+ }
588
+ return { output: input, color: "" };
589
+ }
590
+ broadcastToChannels(message) {
591
+ this.ctx.bots.forEach((bot) => {
592
+ const channels = this.config.sendToChannel.filter((str) => str.includes(`${bot.platform}`)).map((str) => str.replace(`${bot.platform}:`, ""));
593
+ bot.broadcast(channels, message, 0);
594
+ });
595
+ }
596
+ setupDisposeHandler() {
597
+ this.ctx.on("dispose", async () => {
598
+ this.ctx.registry.delete(mcwss_default);
599
+ this.ctx.registry.delete(_MinecraftSyncMsg);
600
+ await new Promise(() => {
601
+ this.ws?.close();
602
+ this.ws?.removeAllListeners();
603
+ this.ws ? this.clearReconnectInterval() : void 0;
604
+ });
605
+ this.ws = null;
606
+ this.isDisposing = true;
607
+ });
608
+ this.isDisposing = false;
609
+ }
610
+ };
611
+ ((MinecraftSyncMsg2) => {
612
+ MinecraftSyncMsg2.Config = import_koishi3.Schema.intersect([
613
+ wsConf,
614
+ rconConf,
615
+ import_koishi3.Schema.object({
616
+ sendToChannel: import_koishi3.Schema.array(String).description("消息发送到目标群组格式{platform}:{groupId}"),
617
+ sendprefix: import_koishi3.Schema.string().default(".#").description("消息发送前缀(不可与命令发送前缀相同,可以为空)"),
618
+ cmdprefix: import_koishi3.Schema.string().default("./").description("命令发送前缀(不可与消息发送前缀相同)"),
619
+ hideConnect: import_koishi3.Schema.boolean().default(true).description("关闭连接成功/失败提示"),
620
+ locale: import_koishi3.Schema.union(["zh-CN", "en-US"]).default("zh-CN").description("本地化语言选择,zh_CN为中文,en-US为英文")
621
+ }).description("基础配置")
622
+ ]);
623
+ MinecraftSyncMsg2.usage = `
624
+ 插件使用详情请看 [v2.x](https://blog.iin0.cn/views/myblog/mc/wskoishitomc.html)
625
+ *** 注意 ***
626
+ * 命令发送前缀(不能为空)和消息发送前缀(可以为空)不能相同
627
+ * forge端不支持PlayerCommandPreprocessEvent事件
628
+ * * 原版端仅支持聊天、加入、离开事件
629
+ * sendToChannel的格式为{platform}:{groupId},如:\`discord:123456\`
630
+ * v2.1.0-beta可以通过\`本地化\`自定义对应事件发送格式
631
+ - action节点的{0}是玩家名称{1}是消息
632
+ - message节点中的{0}是平台{1}是用户名
633
+ `;
634
+ })(MinecraftSyncMsg || (MinecraftSyncMsg = {}));
635
+ var src_default = MinecraftSyncMsg;
636
+ // Annotate the CommonJS export names for ESM import in node:
637
+ 0 && (module.exports = {
638
+ name
639
+ });
package/lib/mcwss.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { Context, Schema } from 'koishi';
2
+ import { IncomingMessage } from 'http';
3
+ import { wsConf } from './values';
4
+ declare class mcWss {
5
+ private conf;
6
+ private logger;
7
+ private wss;
8
+ private ctx;
9
+ private connectedClients;
10
+ constructor(ctx: Context, cfg: mcWss.Config);
11
+ private setupWebSocketHandlers;
12
+ private setupMessageHandler;
13
+ verifyHeaders(headers: IncomingMessage['headers']): {
14
+ valid: boolean;
15
+ clientOrigin?: string;
16
+ };
17
+ }
18
+ export declare function extractAndRemoveColor(input: string): {
19
+ output: string;
20
+ color: string;
21
+ };
22
+ declare namespace mcWss {
23
+ interface Config extends wsConf {
24
+ sendToChannel: string[];
25
+ sendprefix: string;
26
+ hideConnect: boolean;
27
+ locale: string | any;
28
+ }
29
+ const Config: Schema<Config>;
30
+ }
31
+ export default mcWss;
@@ -0,0 +1,73 @@
1
+ import { Schema } from 'koishi';
2
+ export declare enum mcEvent {
3
+ AsyncPlayerChatEvent = 1,
4
+ PlayerCommandPreprocessEvent = 2,
5
+ PlayerDeathEvent = 4,
6
+ PlayerJoinEvent = 8,
7
+ PlayerQuitEvent = 16,
8
+ PlayerAchievementEvent = 32
9
+ }
10
+ export interface wsConf {
11
+ wsServer: boolean | string;
12
+ wsHost: string;
13
+ wsPort: number;
14
+ Token: string;
15
+ serverName: string;
16
+ joinMsg: string;
17
+ event: mcEvent;
18
+ maxReconnectCount: number;
19
+ maxReconnectInterval: number;
20
+ }
21
+ export declare const wsConf: Schema<Schemastery.ObjectS<{
22
+ wsServer: Schema<"客户端" | "服务端", "客户端" | "服务端">;
23
+ wsHost: Schema<string, string>;
24
+ wsPort: Schema<number, number>;
25
+ Token: Schema<string, string>;
26
+ serverName: Schema<string, string>;
27
+ joinMsg: Schema<string, string>;
28
+ event: Schema<number | readonly ("AsyncPlayerChatEvent" | "PlayerCommandPreprocessEvent" | "PlayerDeathEvent" | "PlayerJoinEvent" | "PlayerQuitEvent" | "PlayerAchievementEvent")[], number>;
29
+ maxReconnectCount: Schema<number, number>;
30
+ maxReconnectInterval: Schema<number, number>;
31
+ }>, Schemastery.ObjectT<{
32
+ wsServer: Schema<"客户端" | "服务端", "客户端" | "服务端">;
33
+ wsHost: Schema<string, string>;
34
+ wsPort: Schema<number, number>;
35
+ Token: Schema<string, string>;
36
+ serverName: Schema<string, string>;
37
+ joinMsg: Schema<string, string>;
38
+ event: Schema<number | readonly ("AsyncPlayerChatEvent" | "PlayerCommandPreprocessEvent" | "PlayerDeathEvent" | "PlayerJoinEvent" | "PlayerQuitEvent" | "PlayerAchievementEvent")[], number>;
39
+ maxReconnectCount: Schema<number, number>;
40
+ maxReconnectInterval: Schema<number, number>;
41
+ }>>;
42
+ export interface rconConf {
43
+ rconEnable: boolean;
44
+ rconServerHost: string;
45
+ rconServerPort: number;
46
+ rconPassword: string;
47
+ alluser: boolean;
48
+ superuser: string[];
49
+ commonCmd: string[];
50
+ cannotCmd: string[];
51
+ }
52
+ export declare const rconConf: Schema<Schemastery.ObjectS<{
53
+ rconEnable: Schema<boolean, boolean>;
54
+ rconServerHost: Schema<string, string>;
55
+ rconServerPort: Schema<number, number>;
56
+ rconPassword: Schema<string, string>;
57
+ alluser: Schema<boolean, boolean>;
58
+ superuser: Schema<string[], string[]>;
59
+ commonCmd: Schema<string[], string[]>;
60
+ cannotCmd: Schema<string[], string[]>;
61
+ }>, Schemastery.ObjectT<{
62
+ rconEnable: Schema<boolean, boolean>;
63
+ rconServerHost: Schema<string, string>;
64
+ rconServerPort: Schema<number, number>;
65
+ rconPassword: Schema<string, string>;
66
+ alluser: Schema<boolean, boolean>;
67
+ superuser: Schema<string[], string[]>;
68
+ commonCmd: Schema<string[], string[]>;
69
+ cannotCmd: Schema<string[], string[]>;
70
+ }>>;
71
+ export declare const eventList: string[];
72
+ export declare function getSubscribedEvents(binaryInput: number): string[];
73
+ export declare function getListeningEvent(input: string | string[]): string;
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@arr0wing/koishi-plugin-minecraft-sync-msg",
3
+ "description": "搭配鹊桥v0.3.x的mc消息互联插件 (更新前请停止插件)",
4
+ "version": "3.0.2",
5
+ "main": "lib/index.js",
6
+ "typings": "lib/index.d.ts",
7
+ "homepage": "https://blog.iin0.cn/views/myblog/mc/koishiandmc.html",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/Twiyin0/koishi-plugin-minecraft-sync-msg.git"
11
+ },
12
+ "contributors": [
13
+ "Twiyin0 <twiyin0@outlook.com>"
14
+ ],
15
+ "koishi": {
16
+ "browser": true,
17
+ "description": {
18
+ "en": "A simple websocket communication for MC message interconnection (please stop the plugin before updating)",
19
+ "zh": "使用鹊桥插件/MOD桥接的Websocket消息互联插件(更新前请停止插件)"
20
+ }
21
+ },
22
+ "files": [
23
+ "lib",
24
+ "dist"
25
+ ],
26
+ "license": "MIT",
27
+ "keywords": [
28
+ "chatbot",
29
+ "koishi",
30
+ "plugin",
31
+ "minecraft",
32
+ "mc互联",
33
+ "mc"
34
+ ],
35
+ "peerDependencies": {
36
+ "koishi": "^4.14.0"
37
+ },
38
+ "dependencies": {
39
+ "rcon-client": "^4.2.5",
40
+ "ws": "^8.18.2"
41
+ }
42
+ }
package/readme.md ADDED
@@ -0,0 +1,194 @@
1
+ # koishi-plugin-minecraft-sync-msg
2
+
3
+ [![npm](https://img.shields.io/npm/v/koishi-plugin-minecraft-sync-msg?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-minecraft-sync-msg)
4
+
5
+ `v2.x` mc插件已更换为 [鹊桥](https://github.com/17TheWord/QueQiao) 同时支持插件服与MOD服
6
+ `v1.x` mc插件已停止维护,但可以继续使用 [chatSocketServer-spigot](https://github.com/Twiyin0/chatSocketServer-spigot) 仅支持插件服(1.13~1.21)
7
+ 使用详情查看
8
+ * [音铃的博客(v2.x)](https://blog.iin0.cn/views/myblog/mc/wskoishitomc.html)
9
+ * [音铃的博客(v1.x)](https://blog.iin0.cn/views/myblog/mc/koishiandmc.html)
10
+
11
+ **注意**
12
+ rcon并非完全跟控制台一样所有命令都会有反馈
13
+
14
+ # CHANGELOG
15
+ ## v3.0.2
16
+ ### 新增
17
+ * 新增连接消息相关本地化(不包含logger)
18
+ `connectedToWS`连上WS发送的消息(兼容WSserver)
19
+ `disconnectedFromWS`断开WS发送的消息(兼容WSserver)
20
+ `connectionErrorWS`WS连接出错发送的消息(兼容WSserver)
21
+ * 对于WSserver的用户而言消息发送未完全本地化,因为WSclient和WSserver的处理方式不兼容同一套本地化,后续会想办法分开
22
+
23
+ ## v3.0.1
24
+ ### 修改
25
+ * 修改了本地化节点`minecraft-sync-msg.message.MCReceivePrefix`,{0}为聊天平台,{1}为平台用户名,{2}为用户id(为了区分相同用户名)
26
+
27
+ ## v3.0.0
28
+ ### 注意
29
+ **此版本后需要配合鹊桥v0.3.x才能正常使用**
30
+ ### 新增
31
+ * 新增成就上报,但是没有做本地化 **该事件暂不建议在koishi端订阅**
32
+ * 新增执行命令返回,但是这个所有命令都会返回,需要在鹊桥端优化 **该事件暂不建议在koishi端订阅**
33
+ ### 修复
34
+ * 修复了插件重载或重启后整个koishi重启的问题
35
+
36
+ ## v3.0.0-beta.2
37
+ ### 注意
38
+ **此版本后需要配合鹊桥v0.3.x才能正常使用**
39
+ ### 修复
40
+ * 修复了mc消息无法发送到群聊的问题
41
+ * 优化了重启无限重连的问题
42
+
43
+ ## v3.0.0-beta.1
44
+ ### 注意
45
+ **此版本后需要配合鹊桥v0.3.x才能正常使用**
46
+ ### 修复
47
+ * 修复RCON命令发送逻辑,感谢大佬[GoFightNow](https://github.com/GoFightNow)
48
+
49
+ ## v2.1.0-beta.2
50
+ ### 注意
51
+ **此版本后需要配合鹊桥v0.3.x才能正常使用**
52
+ ### 修复
53
+ * 修复消息发送api,感谢大佬[@ajchen02](https://github.com/ajchen02)
54
+ * 优化插件重启后重复链接的bug
55
+
56
+ ## v2.1.0-beta.1
57
+ ### 修复
58
+ * 修复图片发消息至Minecraft时会有报错
59
+ * 修复订阅事件不生效的BUG
60
+
61
+ ## v2.1.0-beta
62
+ ### 新增
63
+ * 新增了本地化,现在可以对事件发送消息进行自定义了
64
+ * 新增插件提示,有些需要注意的地方强调了一下
65
+
66
+ ### 修改
67
+ * 修改了mc发送到平台时的限制(之前限制了语音和视频)
68
+
69
+ ## v2.0.8
70
+ ### 优化
71
+ * 简化at元素
72
+ * 修复RCON禁止的指令不生效
73
+
74
+ ## v2.0.7
75
+ ### 优化
76
+ * 简化一些消息元素
77
+ * 作为服务端使用时支持[chatImage](https://github.com/kitUIN/ChatImage) MOD
78
+
79
+ ## v2.0.6
80
+ ### 修复
81
+ * 修复了作为服务端时koishi消息无响应的问题
82
+ * 修复服务端部分类型消息无处理的问题
83
+
84
+ ## v2.0.5
85
+ ### 修复
86
+ * 修复了作为服务端时koishi插件钩子报错的问题
87
+
88
+ ## v2.0.4
89
+ ### 修复
90
+ * 修复了一下重连导致多个客户端连接的问题
91
+ * 重构了我的垢使代码
92
+
93
+ ## v2.0.4-beta
94
+ ### 修复
95
+ * 修复了一下重连导致多个客户端连接的问题
96
+
97
+ ## v2.0.3
98
+ ### 更新
99
+ 感谢[@17TheWord](https://github.com/17TheWord)大佬的支持
100
+ * 映射了NeoForge的事件,感谢[@17TheWord](https://github.com/17TheWord)大佬的PR
101
+
102
+ ## v2.0.2
103
+ ### 修复
104
+ * 修复重连无法接收消息的问题(log可能会跳多次重连,但这些都不重要,mc的消息只会发送一条)
105
+ ### 新增
106
+ 对使用[chatImage](https://github.com/kitUIN/ChatImage) MOD的用户进行了一些玩法优化
107
+
108
+ ## v2.0.1
109
+ ### 修复
110
+ * 修复v端无法使用的问题
111
+ * 修复无法重连,或者重连任继续重连的问题
112
+
113
+ ## v2.0.0
114
+ 经本人测试该插件整体趋向稳定,可以作为正式版发布,非常感谢koishi开发群各位大佬的支持以及各位用户的反馈
115
+ ### 更新
116
+ * 已支持koishi作为ws服务端给鹊桥连接
117
+ * 加入了原版端的聊天、加入以及离开的事件的映射
118
+ ### 修复
119
+ * 修复了ws作为客户端无法重连的问题
120
+
121
+ ## v2.0.0-beta.5
122
+ ### 修复
123
+ * 删除debug的命令,防止误操作
124
+
125
+ ## v2.0.0-beta.4
126
+ ### 修复
127
+ * 修复事件无法正常订阅的问题
128
+
129
+ ## v2.0.0-beta.3
130
+ ### 修复
131
+ * 修复开启插件会报错`TypeError: input is not iterable`的问题
132
+
133
+ ## v2.0.0-beta.2
134
+ ### 更新
135
+ * 由于fabric与forge的监听事件与spigot不同,因此对其进行了映射,不过需要注意的是`forge`不支持`PlayerCommandPreprocessEvent`事件,即使订阅了该事件也无法监听
136
+
137
+ ## v2.0.0-beta.1
138
+ 教程更新为[v2.0](https://twiyin0.github.io/blogs/myblog/mc/wskoishitomc.html)
139
+ ### 更新
140
+ * 更新了使用的协议,使用Websocket协议连接mc服务器
141
+ * mc服务端插件更换为[鹊桥](https://github.com/17TheWord/QueQiao)支持插件服与MOD服
142
+ ### 优化
143
+ * 优化了配置项的排列
144
+
145
+ ## v1.1.0
146
+ ### 新增
147
+ * 新增配置项,可以配置是否使用socket发送消息(关闭此项可以仅用RCON)
148
+ ### 修复
149
+ * 修复插件重载配置后进行多次连接的问题(每次重载都能重新连接socket啦)
150
+
151
+ ## v1.0.2
152
+ ### 新增
153
+ * 为了适配mc插件端新增token配置项,用于验证身份,如果你的插件版本还没更新,这个配置项可以留空
154
+
155
+ ## v1.0.0
156
+ ### 修改
157
+ * 开放消息发送前缀与命令发送前缀修改配置项
158
+ * 转为正式版(ver 1.0.0, 出bug的概率小)
159
+
160
+ ## v0.3.0
161
+ ### 修复
162
+ * 修复机器人只能广播一次消息的问题,非常感谢开发群的各位大佬
163
+ ### 修改
164
+ * 将RCON密码配置项改为不看见格式
165
+
166
+ ## v0.2.1
167
+ ### 修复
168
+ * 加入配置项`toGBK`可以选择是否将消息转为GBK格式
169
+ * 目前支持`GBK`与`UTF8`两种格式接收mc服务器的聊天消息
170
+
171
+ ## v0.2.0
172
+ ### 更新
173
+ * 加入新的配置项`chatOnly`开启则只接收聊天消息
174
+ * (如果你的chatSocketServer版本为v1.0.1)群友发送tps/TPS/服务器信息/server_info触发mc插件对应响应
175
+ ### 修复
176
+ * 修改了`无响应`为`无反馈`,实际上是有相应的,只是没有反馈
177
+ * 聊天前缀、命令前缀只能在对应频道(群聊)或者koishi沙盒触发
178
+ ### 一点小建议
179
+ * 由于RCON不像控制这么灵活,因此rcon发送的命令简易在前面加上插件名(父级)
180
+ 示例:
181
+ /tps ==> /spigot:tps
182
+ /status ==> /cmi:cmi status
183
+
184
+ ## v0.1.2
185
+ ### 修复
186
+ * 修复超管不启用的小问题
187
+
188
+ ## v0.1.1
189
+ ### 修改
190
+ * 将replace改成replaceAll替换全局
191
+
192
+ ## v0.1.0
193
+ ### 发布
194
+ 插件发布啦!