From c6bd95e5f0108d2b67d8ef038a60e2e330b72366 Mon Sep 17 00:00:00 2001 From: scorp Date: Tue, 30 Jun 2026 08:53:29 +0200 Subject: [PATCH 1/4] fix: refactor minecraft waypoints message Signed-off-by: scorp --- src/minecraft_coords_table.js | 89 +++++++++++++++++++++++++++++++++++ src/robo_scorp.js | 29 +++++++----- 2 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 src/minecraft_coords_table.js diff --git a/src/minecraft_coords_table.js b/src/minecraft_coords_table.js new file mode 100644 index 0000000..5d88f8c --- /dev/null +++ b/src/minecraft_coords_table.js @@ -0,0 +1,89 @@ +const DISCORD_MESSAGE_LIMIT = 2000; +const CODEBLOCK_FENCE_LENGTH = '```\n'.length + '\n```'.length; +const MAX_TABLE_LENGTH = DISCORD_MESSAGE_LIMIT - CODEBLOCK_FENCE_LENGTH; + +const NO_WAYPOINTS_MESSAGE = 'No publicly shared waypoints available.'; + +const COLUMNS = ['owner', 'name', 'world', 'coords']; +const HEADERS = ['Owner', 'Name', 'World', '(x, y, z)']; + +function getDateString() { + const date = new Date(); + const pad = (n) => String(n).padStart(2, '0'); + return `${pad(date.getDate())}/${pad(date.getMonth() + 1)}/${date.getFullYear()}`; +} + +function parseWaypointsCsv(csv) { + const [, ...rows] = csv.split('\n').filter((line) => line.length > 0); + return rows.map((row) => { + const [owner, name, world, x, y, z] = row.split(','); + return { owner, name, world, x, y, z }; + }); +} + +// Mirrors the table's previous merged-cell look: blank out an Owner/World +// cell when it repeats the value directly above it. +function collapseRepeatedColumns(waypoints) { + let lastOwner = ''; + let lastWorld = ''; + return waypoints.map((waypoint) => { + const row = { + owner: waypoint.owner === lastOwner ? '' : waypoint.owner, + name: waypoint.name, + world: waypoint.world === lastWorld ? '' : waypoint.world, + coords: `(${waypoint.x}, ${waypoint.y}, ${waypoint.z})`, + }; + lastOwner = waypoint.owner; + lastWorld = waypoint.world; + return row; + }); +} + +function buildTableLines(rows) { + const widths = COLUMNS.map((column, i) => Math.max(HEADERS[i].length, ...rows.map((row) => row[column].length))); + const buildSeparator = () => `+${widths.map((width) => '-'.repeat(width + 2)).join('+')}+`; + const buildRow = (cells) => `| ${cells.map((cell, i) => cell.padEnd(widths[i])).join(' | ')} |`; + + const separator = buildSeparator(); + const headerLines = [separator, buildRow(HEADERS), separator]; + const dataLines = rows.map((row) => buildRow(COLUMNS.map((column) => row[column]))); + + return { headerLines, dataLines, separator }; +} + +// Packs the table into as few codeblocks as fit Discord's message length +// limit, repeating the header in every continuation block. +function packIntoCodeblocks(headerLines, dataLines, separator, titleLine) { + const chunks = []; + let current = [titleLine, ...headerLines]; + let hasRows = false; + + dataLines.forEach((dataLine) => { + const candidateLength = [...current, dataLine, separator].join('\n').length; + if (hasRows && candidateLength > MAX_TABLE_LENGTH) { + chunks.push([...current, separator].join('\n')); + current = [...headerLines]; + hasRows = false; + } + current.push(dataLine); + hasRows = true; + }); + + chunks.push([...current, separator].join('\n')); + return chunks.map((chunk) => `\`\`\`\n${chunk}\n\`\`\``); +} + +function buildCoordsMessages(csv) { + const waypoints = parseWaypointsCsv(csv); + if (waypoints.length === 0) { + return [`\`\`\`\n${NO_WAYPOINTS_MESSAGE}\n\`\`\``]; + } + + const rows = collapseRepeatedColumns(waypoints); + const { headerLines, dataLines, separator } = buildTableLines(rows); + const titleLine = `Publicly shared waypoints (updated: ${getDateString()})`; + + return packIntoCodeblocks(headerLines, dataLines, separator, titleLine); +} + +module.exports = { buildCoordsMessages }; diff --git a/src/robo_scorp.js b/src/robo_scorp.js index ac996a9..01c322d 100644 --- a/src/robo_scorp.js +++ b/src/robo_scorp.js @@ -1,6 +1,7 @@ const { Client, GatewayIntentBits, Events } = require('discord.js'); const { MessageGenerator } = require('./message_generation/message_generator'); const { TcpServer } = require('./tcp_server'); +const { buildCoordsMessages } = require('./minecraft_coords_table'); const logger = require('./logger'); class RoboScorp { @@ -89,21 +90,23 @@ class RoboScorp { } #createTcpServer() { - this.#tcpServer = new TcpServer(process.env.TCP_CONNECTION_PORT, (tcpMessage) => { - this.#discordClient.channels - .fetch(process.env.MINECRAFT_COORDS_CHANNEL_ID) - .then((channel) => { - channel.messages - .fetch(process.env.MINECRAFT_COORDS_MESSAGE_ID) - .then((discordMessage) => { - const codeblock = '```\n'; - discordMessage.edit(codeblock + tcpMessage + codeblock); - }) - .catch(logger.error, RoboScorp.LogLabel.Discord); - }) - .catch(logger.error, RoboScorp.LogLabel.Discord); + this.#tcpServer = new TcpServer(process.env.TCP_CONNECTION_PORT, (csvMessage) => { + this.#updateCoordsChannel(csvMessage).catch((err) => logger.error(err, RoboScorp.LogLabel.Discord)); }); } + + async #updateCoordsChannel(csvMessage) { + const channel = await this.#discordClient.channels.fetch(process.env.MINECRAFT_COORDS_CHANNEL_ID); + + const previousMessages = await channel.messages.fetch({ limit: 100 }); + const ownMessages = previousMessages.filter((message) => message.author.id === this.#discordClient.user.id); + await Promise.all(ownMessages.map((message) => message.delete())); + + const coordsMessages = buildCoordsMessages(csvMessage); + for (const messageContent of coordsMessages) { + await channel.send(messageContent); + } + } } module.exports = { RoboScorp }; From 0162a8e2fd956fb9b5629d50695a832d731456a6 Mon Sep 17 00:00:00 2001 From: scorp Date: Wed, 1 Jul 2026 15:54:02 +0200 Subject: [PATCH 2/4] fix: fix null error Signed-off-by: scorp --- src/minecraft_coords_table.js | 10 ++++++---- src/tcp_server.js | 8 +++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/minecraft_coords_table.js b/src/minecraft_coords_table.js index 5d88f8c..75c3ac4 100644 --- a/src/minecraft_coords_table.js +++ b/src/minecraft_coords_table.js @@ -15,10 +15,12 @@ function getDateString() { function parseWaypointsCsv(csv) { const [, ...rows] = csv.split('\n').filter((line) => line.length > 0); - return rows.map((row) => { - const [owner, name, world, x, y, z] = row.split(','); - return { owner, name, world, x, y, z }; - }); + return rows + .filter((row) => row.split(',').length >= 6) + .map((row) => { + const [owner, name, world, x, y, z] = row.split(','); + return { owner, name, world, x, y, z }; + }); } // Mirrors the table's previous merged-cell look: blank out an Owner/World diff --git a/src/tcp_server.js b/src/tcp_server.js index 1f6ee76..0488fd1 100644 --- a/src/tcp_server.js +++ b/src/tcp_server.js @@ -18,8 +18,14 @@ class TcpServer { } #onClientConnection = (sock) => { + const chunks = []; + sock.on('data', (data) => { - const dataString = data.toString('utf8'); + chunks.push(data); + }); + + sock.on('end', () => { + const dataString = Buffer.concat(chunks).toString('utf8'); if (dataString.length > 0) { logger.debug(`Received message: ${dataString}`, TcpServer.LogLabel); this.#onMessageFunction(dataString); From 029ab503312bc5fa60a40b1ee2161830948afeb5 Mon Sep 17 00:00:00 2001 From: scorp Date: Wed, 1 Jul 2026 17:22:02 +0200 Subject: [PATCH 3/4] fix Signed-off-by: scorp --- src/robo_scorp.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/robo_scorp.js b/src/robo_scorp.js index 01c322d..787c54e 100644 --- a/src/robo_scorp.js +++ b/src/robo_scorp.js @@ -100,11 +100,13 @@ class RoboScorp { const previousMessages = await channel.messages.fetch({ limit: 100 }); const ownMessages = previousMessages.filter((message) => message.author.id === this.#discordClient.user.id); - await Promise.all(ownMessages.map((message) => message.delete())); + await Promise.allSettled(ownMessages.map((message) => message.delete())); const coordsMessages = buildCoordsMessages(csvMessage); for (const messageContent of coordsMessages) { - await channel.send(messageContent); + await channel.send(messageContent).catch((err) => { + logger.error(`Failed to send coords message (${messageContent.length} chars): ${err}`, RoboScorp.LogLabel.Discord); + }); } } } From 1c36e3b484cb9770964c232d39e57e2b1b15fdba Mon Sep 17 00:00:00 2001 From: scorp Date: Wed, 1 Jul 2026 17:31:21 +0200 Subject: [PATCH 4/4] test Signed-off-by: scorp --- src/robo_scorp.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/robo_scorp.js b/src/robo_scorp.js index 787c54e..5080683 100644 --- a/src/robo_scorp.js +++ b/src/robo_scorp.js @@ -96,6 +96,7 @@ class RoboScorp { } async #updateCoordsChannel(csvMessage) { + logger.info(csvMessage); const channel = await this.#discordClient.channels.fetch(process.env.MINECRAFT_COORDS_CHANNEL_ID); const previousMessages = await channel.messages.fetch({ limit: 100 }); @@ -103,6 +104,7 @@ class RoboScorp { await Promise.allSettled(ownMessages.map((message) => message.delete())); const coordsMessages = buildCoordsMessages(csvMessage); + logger.info(coordsMessages); for (const messageContent of coordsMessages) { await channel.send(messageContent).catch((err) => { logger.error(`Failed to send coords message (${messageContent.length} chars): ${err}`, RoboScorp.LogLabel.Discord);