From a27aa94108a07855693e50f0fea6317dcb3810a5 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Mon, 12 Oct 2026 19:57:17 +0900 Subject: [PATCH] 1.0.4 RC --- controller.ts | 56 +++++-------------------------- index.ts | 20 +++++------ instance.ts | 93 ++++++++++++++++++++++++++++++++++----------------- 3 files changed, 81 insertions(+), 88 deletions(-) diff --git a/controller.ts b/controller.ts index 4634165..148a50c 100644 --- a/controller.ts +++ b/controller.ts @@ -3,12 +3,6 @@ import {ChatEvent} from './index.js'; import * as Discord from 'discord.js'; const MAX_DISCORD_MESSAGE_LENGTH = 1950; -const ACTION_VERBS: Record = { - JOIN: 'joined the game', - LEAVE: 'left the game', - KICK: 'was kicked from the game', - BAN: 'was banned from the game', -}; export default async function(context: ControllerPluginContext) { const {controller, logger, plugin} = context; @@ -50,11 +44,11 @@ export default async function(context: ControllerPluginContext) { logger.info('[Chat Relay] Logged in Discord successfully.'); }; - const sendMessage = async (instanceName: string, message: string) => { + const sendMessage = async (instance_name: string, datetime: string, message: string) => { if (!client) return; const mapping = controller.config.get('chat_relay.discord_channel_mapping') as Record; - const channelId = mapping?.[instanceName]; + const channelId = mapping?.[instance_name]; if (!channelId) return; let channel: Discord.Channel | null; @@ -73,23 +67,20 @@ export default async function(context: ControllerPluginContext) { return; } - let nrcMsg = message; + let msg = message; if (controller.config.get('chat_relay.datetime_on_message')) { - const now = new Date(); - const p = (n: number) => String(n).padStart(2, '0'); - const stamp = `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())} ${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`; - nrcMsg = `${stamp} ${nrcMsg}`; + msg = `${datetime} ${msg}`; } const send = (content: string) => channel.send({content, allowedMentions: {parse: []}}); - if (nrcMsg.length <= MAX_DISCORD_MESSAGE_LENGTH) { - await send(nrcMsg); + if (msg.length <= MAX_DISCORD_MESSAGE_LENGTH) { + await send(msg); return; } - let remaining = nrcMsg; + let remaining = msg; while (remaining.length > 0) { let chunk = remaining.slice(0, MAX_DISCORD_MESSAGE_LENGTH); @@ -107,37 +98,8 @@ export default async function(context: ControllerPluginContext) { }; controller.handle(ChatEvent, async (request: ChatEvent) => { - const {instanceId, instanceName, action, content} = request; - - if (action === 'CHAT' || action === 'SHOUT') { - const nrc = content.replace(/\[special-item=.*?\]/g, '').replace(/<@/g, '<@\u200c>'); - const index = nrc.indexOf(':'); - const username = index === -1 ? nrc.trim() : nrc.slice(0, index); - const msg = index === -1 ? '' : nrc.slice(index + 1).trim(); - await sendMessage(instanceName, `**\`${username}\`**: ${msg}`); - return; - } - - const verb = ACTION_VERBS[action]; - const text = content.trim(); - - if (!verb) { - await sendMessage(instanceName, `unknown action ${action} **\`${text}\`**`); - return; - } - - const name = text.toLowerCase().endsWith(verb) - ? text.slice(0, -verb.length).trim() - : text; - - const instance = await controller.instances.get(instanceId); - - if (!instance) { - await sendMessage(instanceName, `**\`${name}\`** ${verb}`); - return; - } - - await sendMessage(instanceName, `**\`${name}\`** ${verb} (${instance.playersOnline.size})`); + const {instance_name, datetime, message} = request; + await sendMessage(instance_name, datetime, message); }); controller.config.on('fieldChanged', (field) => { diff --git a/index.ts b/index.ts index 6fdb0b6..fc423d9 100644 --- a/index.ts +++ b/index.ts @@ -11,21 +11,19 @@ export class ChatEvent { static permission = null; constructor( - public instanceId: number, - public instanceName: string, - public action: string, - public content: string, + public instance_name: string, + public datetime: string, + public message: string, ) {} - static jsonSchema = Type.Object({ - 'instanceId': Type.Integer(), - 'instanceName': Type.String(), - 'action': Type.String(), - 'content': Type.String(), + static json_schema = Type.Object({ + 'instance_name': Type.String(), + 'datetime': Type.String(), + 'message': Type.String(), }); - static fromJSON(json: Static) { - return new this(json.instanceId, json.instanceName, json.action, json.content); + static fromJSON(json: Static) { + return new this(json.instance_name, json.datetime, json.message); } } diff --git a/instance.ts b/instance.ts index fe3e106..83e5d98 100644 --- a/instance.ts +++ b/instance.ts @@ -1,43 +1,76 @@ import type {InstancePluginContext} from '@clusterio/host'; import {ChatEvent} from './index.js'; +interface ChatMessage { + action: string; + content: string; +} + +const ACTION_VERBS: Record = { + JOIN: 'joined the game', + LEAVE: 'left the game', + KICK: 'was kicked from the game', + BAN: 'was banned from the game', +}; + export default async function(context: InstancePluginContext) { - const {instance, host, plugin} = context; - let queue: [string, string][] = []; + const {instance, host, plugin, logger} = context; - instance.hooks.controllerConnectionEvent.attach(plugin.name, async (event) => { - if (event !== 'connect' || queue.length === 0) { - return; - } + const queue: ChatMessage[] = []; + let flushing = false; - const pending = queue; - queue = []; - const failed: [string, string][] = []; - - for (const [action, content] of pending) { - try { - await instance.sendTo('controller', new ChatEvent(instance.id, instance.name, action, content)); - } catch (err) { - failed.push([action, content]); - } + const flush = async () => { + if (flushing || queue.length === 0 || !host.connector.connected) { + return; } + flushing = true; + try { + while (queue.length > 0 && host.connector.connected) { + try { + await instance.sendTo('controller', new ChatEvent(instance.name, queue[0].datetime, queue[0].message)); + queue.shift(); + } catch (err) { + logger.warn(`[Chat Relay] Failed to send chat to controller: ${err}`); + break; + } + } + } finally { + flushing = false; + } + }; - queue = failed; - }); + instance.hooks.controllerConnectionEvent.attach(plugin.name, async (event) => { + if (event === 'connect') { + await flush(); + } + }); - instance.hooks.output.attach(plugin.name, async (output) => { - if (output.type !== 'action') { + instance.hooks.output.attach(plugin.name, async (output) => { + if (output.type !== 'action') { return; } - if (host.connector.connected) { - try { - await instance.sendTo('controller', new ChatEvent(instance.id, instance.name, output.action, output.message)); - } catch (err) { - queue.push([output.action, output.message]); - } - } else { - queue.push([output.action, output.message]); - } - }); + const now = new Date(); + const p = (n: number) => String(n).padStart(2, '0'); + const dt = `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())} ${p(now.getHours())}${p(now.getMinutes())}${p(now.getSeconds())}`; + + if (output.action === 'CHAT' || output.action === 'SHOUT') { + const message = output.message.trim().replace(/\[special-item=.*?\]/g, '').replace(/<@/g, '<@\u200c>'); + const message_index = message.indexOf(':'); + const message_username = message_index === -1 ? message.trim() : message.slice(0, message_index); + const message_content = index === -1 ? '' : message.slice(index + 1).trim(); + + queue.push({datetime: dt, message: `**\`${message_username}\`**: ${message_content}`}); + } else if (ACTION_VERBS[output.action]) { + const name = text.toLowerCase().endsWith(ACTION_VERBS[output.action]) + ? text.slice(0, -ACTION_VERBS[output.action].length).trim() + : text; + + queue.push({datetime: dt, message: `**\`${name}\`** ${ACTION_VERBS[output.action]} (${instance.playersOnline.size})`}); + } else { + queue.push({datetime: dt, message: `unknown action ${output.action}`}); + } + + await flush(); + }); }