From d85e7177dea8e7ca819ef7d2195e2005c1b65188 Mon Sep 17 00:00:00 2001 From: PHIDIAS Date: Sun, 15 Jun 2025 01:12:41 +0900 Subject: [PATCH] . --- controller.js | 174 +++++++++++++++++++++------------------------- info.js | 40 +++++------ package.json | 11 +-- webpack.config.js | 4 +- 4 files changed, 105 insertions(+), 124 deletions(-) diff --git a/controller.js b/controller.js index eb56771..864dfea 100644 --- a/controller.js +++ b/controller.js @@ -1,174 +1,159 @@ "use strict"; const Discord = require("discord.js"); -const { BaseControllerPlugin } = require("@clusterio/controller"); -const { InstanceActionEvent } = require("./info.js"); +const fetch = require("node-fetch"); +const {BaseControllerPlugin} = require("@clusterio/controller"); +const {InstanceActionEvent} = require("./info.js"); + +const MAX_DISCORD_MESSAGE_LENGTH = 1950; +const MIN_CONFIDENCE_SCORE = 10.0; class LibreTranslateAPI { - constructor(url, apiKey) { - if (!url) throw new Error('url is required for LibreTranslate API'); - if (!apiKey) throw new Error('API key is required for LibreTranslate API'); + constructor(url, apiKey, logger = console) { + if (!url || !apiKey) this.logger.error('[Chat Sync] LibreTranslate API configuration is incomplete.'); + try {new URL(url);} catch {this.logger.error('[Chat Sync] LibreTranslate url is invalid');} this.url = url.endsWith('/') ? url : url + '/'; this.apiKey = apiKey; - this.allowedLanguages = []; + this.logger = logger; } + async handleResponse(response) { + if (!response.ok) this.logger.error(`[Chat Sync] API Request got HTTP ${response.status}`); + return response.json(); + } + async init() { try { - const response = await fetch(`${this.url}languages?api_key=${this.apiKey}`, {method: 'GET'}); - const data = await response.json(); - this.allowedLanguages = data[0].targets; + this.allowedLanguages = (await this.handleResponse(await fetch(`${this.url}languages?api_key=${this.apiKey}`, {method: 'GET'})))[0].targets || []; } catch (error) { - console.error('Failed to initialize languages:', error); - throw error; + this.logger.error('[Chat Sync] failed to initialize languages:', error); } } async translateRequest(q, source, target) { - const params = new URLSearchParams(); - params.append('q', q); - params.append('api_key', this.apiKey); - params.append('source', source); - params.append('target', target); - - const response = await fetch(`${this.url}translate`, {method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: params}); - const data = await response.json(); - return data.translatedText; + try { + return (await this.handleResponse(await fetch(`${this.url}translate`, {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({q: q, api_key: this.apiKey, source: source, target: target})}))).translatedText; + } catch (error) { + this.logger.error('[Chat Sync] Translation failed:', error); + } } async detectLanguage(q) { - const params = new URLSearchParams(); - params.append('q', q); - params.append('api_key', this.apiKey); - - const response = await fetch(`${this.url}detect`, {method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: params}); - const data = await response.json(); - return data[0]; + try { + return (await this.handleResponse(await fetch(`${this.url}detect`, {method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({q: q, api_key: this.apiKey})})))[0]; + } catch (error) { + this.logger.error('[Chat Sync] Detection failed:', error); + } } async translate(query, targetLanguages = ['zh-Hant', 'en']) { console.log(query); - const result = {action: false, passage: []}; try { const detection = await this.detectLanguage(query); - - if (detection.confidence > 10.0) { + + if (!detection || typeof detection !== 'object' || !detection.confidence || !detection.language) { + this.logger.warn('[Chat Sync] Invalid language detection result:', detection); + return result; + } + + if (detection.confidence > MIN_CONFIDENCE_SCORE) { for (const targetLang of targetLanguages) { if (!((detection.language === 'zh-Hans' || detection.language === 'zh-Hant') && (targetLang === 'zh-Hans' || targetLang === 'zh-Hant')) && detection.language !== targetLang && this.allowedLanguages.includes(detection.language) && this.allowedLanguages.includes(targetLang)) { result.action = true; const translated = await this.translateRequest(query, detection.language, targetLang); - result.passage.push(translated); + result.passage.push(`[${detection.language} -> ${targetLang}] ${translated}`); } } } - + return result; } catch (error) { - console.error('Translation failed:', error); - throw error; + this.logger.error('[Chat Sync] translation failed:', error); } } } class ControllerPlugin extends BaseControllerPlugin { async init() { - this.controller.config.on('fieldChanged', (field, curr, prev) => { - if (field === 'chat_sync.discord_bot_token') this.connect().catch(err => {this.logger.error(`Unexpected error:\n${err.stack}`);}); - }); - + this.controller.config.on('fieldChanged', (field, curr, prev) => {if (field === 'ClusterChatSync.discord_bot_token') this.connect().catch(err => {this.logger.error(`[Chat Sync] Discord bot token:\n${err.stack}`);});}); this.controller.handle(InstanceActionEvent, this.handleInstanceAction.bind(this)); this.client = null; - - if (this.controller.config.get('chat_sync.use_libretranslate')) { - this.translator = new LibreTranslateAPI(this.controller.config.get('chat_sync.libretranslate_url'), this.controller.config.get('chat_sync.libretranslate_key')); - await this.translator.init(); - this.translator_language = this.controller.config.get('chat_sync.libretranslate_language').trim().split(/\s+/); - } - await this.connect(); } - async connect() { + async clientDestroy() { if (this.client) { this.client.destroy(); this.client = null; } + } - let token = this.controller.config.get('chat_sync.discord_bot_token'); + async connect() { + await this.clientDestroy() - if (!token) { - this.logger.warn('chat sync bot token not configured, so chat is offline'); + if (!this.controller.config.get('ClusterChatSync.discord_bot_token')) { + this.logger.error('[Chat Sync] Discord bot token not configured'); return; } - this.client = new Discord.Client({ - intents: [ - Discord.GatewayIntentBits.Guilds, - Discord.GatewayIntentBits.GuildMessages, - Discord.GatewayIntentBits.MessageContent, - ], - }); - - this.logger.info('chat sync is logging in to Discord'); + this.client = new Discord.Client({intents: [Discord.GatewayIntentBits.Guilds, Discord.GatewayIntentBits.GuildMessages, Discord.GatewayIntentBits.MessageContent]}); + this.logger.info('[Chat Sync] Logging into Discord'); try { - await this.client.login(this.controller.config.get('chat_sync.discord_bot_token')); + await this.client.login(this.controller.config.get('ClusterChatSync.discord_bot_token')); } catch (err) { - this.logger.error(`chat sync have error logging in to discord, chat is offline:\n${err.stack}`); - this.client.destroy(); - this.client = null; + this.logger.error(`[Chat Sync] Discord login error:\n${err.stack}`); + await this.clientDestroy() return; } - this.logger.info('chat sync have successfully logged in'); + this.logger.info('[Chat Sync] have successfully logged in'); + + if (this.controller.config.get('ClusterChatSync.use_libretranslate')) { + this.translator = new LibreTranslateAPI(this.controller.config.get('ClusterChatSync.libretranslate_url'), this.controller.config.get('ClusterChatSync.libretranslate_key'), this.logger); + await this.translator.init(); + this.translator_language = this.controller.config.get('ClusterChatSync.libretranslate_language').trim().split(/\s+/); + } } async onShutdown() { - if (this.client) { - this.client.destroy(); - this.client = null; - } + await this.clientDestroy() } - async sendMessage(nrc_msg) { - const channel_id = this.controller.config.get('chat_sync.discord_channel_mapping')[request.instanceName]; - let channel = null; - + async sendMessage(request, nrc_msg) { + const channel_id = this.controller.config.get('ClusterChatSync.discord_channel_mapping')[request.instanceName]; if (!channel_id) return; try { - channel = await this.client.channels.fetch(channel_id); - } catch (err) { - if (err.code !== 10003) throw err; - } + let channel = await this.client.channels.fetch(channel_id); - if (channel === null) { - this.logger.error(`chat sync discord hannel ID ${channel_id} was not found`); - return; - } + if (channel === null) { + this.logger.error(`[Chat Sync] Discord Channel ID ${channel_id} not found.`); + return; + } + } catch (err) {if (err.code !== 10003) this.logger.error(`[Chat Sync] Discord channel fetch error:\n${err.stack}`);} - if (this.controller.config.get("chat_sync.datetime_on_message")) { + if (this.controller.config.get("ClusterChatSync.datetime_on_message")) { let now = new Date(); - let dt = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`; - nrc_msg = `${dt} ${nrc_msg}` + nrc_msg = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')} ${nrc_msg}` } - if (nrc_msg.length <= 1950) { - await channel.send(nrc_msg, { allowedMentions: { parse: [] }}); + if (nrc_msg.length <= MAX_DISCORD_MESSAGE_LENGTH) { + await channel.send(nrc_msg, {allowedMentions: {parse: []}}); } else { while (nrc_msg.length > 0) { - let nrc_cmsg = nrc_msg.slice(0, 1950); + let nrc_cmsg = nrc_msg.slice(0, MAX_DISCORD_MESSAGE_LENGTH); let nrc_lindex = nrc_cmsg.lastIndexOf(' '); if (nrc_lindex !== -1) { nrc_cmsg = nrc_cmsg.slice(0, nrc_lindex); nrc_msg = nrc_msg.slice(nrc_lindex).trim(); } else { - nrc_msg = nrc_msg.slice(1950).trim(); + nrc_msg = nrc_msg.slice(MAX_DISCORD_MESSAGE_LENGTH).trim(); } - await channel.send(nrc_cmsg, { allowedMentions: { parse: [] }}); + await channel.send(nrc_cmsg, {allowedMentions: {parse: []}}); } } } @@ -179,15 +164,16 @@ class ControllerPlugin extends BaseControllerPlugin { const nrc_index = nrc.indexOf(":"); const nrc_username = nrc.substring(0, nrc_index); const nrc_message = nrc.substring(nrc_index + 1).trim(); - - if (this.controller.config.get('chat_sync.use_libretranslate')) { + await this.sendMessage(request, `**\`${nrc_username}\`**: ${nrc_message}`) + + if (this.controller.config.get('ClusterChatSync.use_libretranslate')) { const result = await this.translator.translate(nrc_message, this.translator_language); - this.sendChat(`[color=255,255,255]\`${nrc_username}\`: ${result}[/color]`); - // await sendMessage(`**\`${nrc_username}\`**: ${result}`) - } - - await sendMessage(`**\`${nrc_username}\`**: ${nrc_message}`) + if (result && result.action) { + await this.sendMessage(request, `**\`${nrc_username}\`**: ${result.passage}`) + return `[color=255,255,255]\`${nrc_username}\`: ${result}[/color]`; + } + } } } } diff --git a/info.js b/info.js index 3de1214..8c58de6 100644 --- a/info.js +++ b/info.js @@ -5,7 +5,7 @@ class InstanceActionEvent { static type = "event"; static src = "instance"; static dst = "controller"; - static plugin = "chat_sync"; + static plugin = "ClusterChatSync"; constructor(instanceName, action, content) { this.instanceName = instanceName; @@ -29,26 +29,24 @@ class InstanceActionEvent { } const plugin = { - name: "chat_sync", - title: "Chat Sync", + name: "ClusterChatSync", + title: "Cluster Chat Sync", description: "One way chat sync.", instanceEntrypoint: "instance", controllerEntrypoint: "controller", controllerConfigFields: { - "chat_sync.discord_bot_token": { - title: "Discord Bot Token", + "ClusterChatSync.discord_bot_token": { + title: "Discord Bot Token", description: "API Token", - type: "string", - optional: true, + type: "string" }, - "chat_sync.datetime_on_message": { + "ClusterChatSync.datetime_on_message": { title: "Message Datetime", description: "Append datetime in front", type: "boolean", - initialValue: true, - optional: true, + initialValue: true }, - "chat_sync.discord_channel_mapping": { + "ClusterChatSync.discord_channel_mapping": { title: "Channels", description: "Putting the discord channel id and instance relations here", type: "object", @@ -56,33 +54,29 @@ const plugin = { "S1": "123" }, }, - "chat_sync.use_libretranslate": { + "ClusterChatSync.use_libretranslate": { title: "Translate Message", description: "Using self host or paid service of libretranslate", type: "boolean", - initialValue: false, - optional: true, + initialValue: false }, - "chat_sync.libretranslate_url": { + "ClusterChatSync.libretranslate_url": { title: "Translate Server URL", description: "Including http protocol, and the port if needed", type: "string", - initialValue: "http://localhost:5000", - optional: true, + initialValue: "http://localhost:5000" }, - "chat_sync.libretranslate_key": { + "ClusterChatSync.libretranslate_key": { title: "Translate Server API Key", description: "The API key for the translate server", type: "string", - initialValue: "123456", - optional: true, + initialValue: "123456" }, - "chat_sync.libretranslate_language": { + "ClusterChatSync.libretranslate_language": { title: "Translate Server Target Language", description: "Put a space between each language, using ISO 639-1 codes", type: "string", - initialValue: "zh-Hants en", - optional: true, + initialValue: "zh-Hants en" }, }, diff --git a/package.json b/package.json index 63db423..cb1b057 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@phidias0303/clusterio_chat_sync", - "version": "1.0.1", + "version": "1.0.2", "description": "One way chat sync", "main": "info.js", "scripts": { @@ -30,12 +30,13 @@ "devDependencies": { "@clusterio/lib": "^2.0.0-alpha.21", "@clusterio/web_ui": "^2.0.0-alpha.21", - "webpack": "^5.88.2", - "webpack-cli": "^5.1.4", - "webpack-merge": "^5.9.0" + "webpack": "^5.99.9", + "webpack-cli": "^6.0.1", + "webpack-merge": "^6.0.1" }, "dependencies": { - "discord.js": "^14.14.1" + "discord.js": "^14.19.3", + "node-fetch": "^3.3.2" }, "publishConfig": { "access": "public" diff --git a/webpack.config.js b/webpack.config.js index d0c5063..7689684 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -13,8 +13,8 @@ module.exports = (env = {}) => merge(common(env), { }, plugins: [ new webpack.container.ModuleFederationPlugin({ - name: "chat_sync", - library: { type: "var", name: "plugin_chat_sync" }, + name: "ClusterChatSync", + library: { type: "var", name: "plugin_ClusterChatSync" }, exposes: { "./": "./info.js", "./package.json": "./package.json",