@rithien/discord_bridge 0.3.4 → 0.3.5

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.3.5 - rithien fork
4
+
5
+ - **Fixed lost JOIN/LEAVE (and other actions) after a transient controller disconnect.** `onControllerConnectionEvent` flushed the offline `messageQueue` only on `connect`, but clusterio emits `resume` after a brief link drop (`connect` only happens on a new session, e.g. controller restart). Actions buffered during the drop window stayed stuck in the queue until the next full `connect` — symptom: missing `joined`/`left` messages on Discord, especially after chat inactivity (an idle host↔controller link drops unnoticed and resumes). Now flushes on `connect` **or** `resume` (same fix already shipped in comfy_adapter as FIX31 #2). This was the discord-bridge analog of #2 that was never ported.
6
+ - **Added Discord gateway lifecycle handlers** (`error`, `shardError`, `shardDisconnect`, `shardReconnecting`, `shardResume`, `invalidated`). Previously none were attached, so client/shard errors were swallowed (no log trace) and an `invalidated` session left the bot dead until a controller restart. `invalidated` now re-logs in via `connect()` (guarded against a reconnect loop); the rest add observability for diagnosing gateway drops. (discord.js v14 already auto-reconnects shards and `channel.send` goes over REST, so this is mainly recovery + visibility, not the primary fix.)
7
+ - **Wrapped `channel.send` in `handleInstanceAction` in try/catch** — a transient REST/permission/rate-limit error no longer silently drops the action or throws an unhandled rejection out of the event handler (consistent with the existing `.catch` in `onHostConnectionEvent`/`onInstanceStatusChanged`). Losses are now logged.
8
+
3
9
  ## v0.3.4 - rithien fork
4
10
 
5
11
  - `discordMessage` no longer assumes `message.member` is present (it can be `null` in discord.js v14 — author left the guild, partial/cache miss, gateway didn't deliver the member); falls back to the always-present `message.author`, so Discord→Factorio chat is no longer silently dropped with a `TypeError` (FIX31 #31).
package/controller.js CHANGED
@@ -12,6 +12,7 @@ class ControllerPlugin extends BaseControllerPlugin {
12
12
  this.channelByInstance = new Map();
13
13
  this.instancesByChannel = new Map();
14
14
  this.fallbackChannel = null;
15
+ this.reconnecting = false; // guard przed pętlą reloginu po evencie "invalidated"
15
16
 
16
17
  this.controller.handle(InstanceActionEvent, this.handleInstanceAction.bind(this));
17
18
  await this.connect();
@@ -44,6 +45,39 @@ class ControllerPlugin extends BaseControllerPlugin {
44
45
  this.discordMessage(message).catch(err => { this.logger.error(`Unexpected error:\n${err.stack}`); });
45
46
  });
46
47
 
48
+ // Handlery cyklu życia gatewaya. Wcześniej żaden nie był podpięty — błędy klienta/shardu
49
+ // były połykane (brak śladu w logach), a "invalidated" zostawiał bota martwego do restartu
50
+ // kontrolera. discord.js v14 sam rekonektuje shardy (channel.send i tak idzie przez REST,
51
+ // odporny na blipy gatewaya) — te handlery służą głównie OBSERWOWALNOŚCI + recovery z invalidated.
52
+ this.client.on("error", (err) => {
53
+ this.logger.error(`Discord client error:\n${err?.stack ?? err}`);
54
+ });
55
+ this.client.on("shardError", (err) => {
56
+ this.logger.error(`Discord shard websocket error:\n${err?.stack ?? err}`);
57
+ });
58
+ this.client.on("shardDisconnect", (event, shardId) => {
59
+ this.logger.warn(`Discord shard ${shardId} disconnected (code ${event?.code}) — discord.js spróbuje reconnect`);
60
+ });
61
+ this.client.on("shardReconnecting", (shardId) => {
62
+ this.logger.info(`Discord shard ${shardId} reconnecting`);
63
+ });
64
+ this.client.on("shardResume", (shardId) => {
65
+ this.logger.info(`Discord shard ${shardId} resumed`);
66
+ });
67
+ this.client.on("invalidated", () => {
68
+ // Sesja nieodwracalnie unieważniona — discord.js zatrzymuje klienta i NIE wznawia sam.
69
+ // Bez reloginu bot zostaje martwy do restartu kontrolera. connect() robi destroy + nowy
70
+ // login; flaga reconnecting blokuje pętlę gdyby invalidated przyszło wielokrotnie.
71
+ this.logger.error("Discord session invalidated — ponowne logowanie");
72
+ if (this.reconnecting) {
73
+ return;
74
+ }
75
+ this.reconnecting = true;
76
+ this.connect()
77
+ .catch(err => { this.logger.error(`Relogin po invalidated nie powiódł się:\n${err?.stack ?? err}`); })
78
+ .finally(() => { this.reconnecting = false; });
79
+ });
80
+
47
81
  this.logger.info("Logging in to Discord");
48
82
  try {
49
83
  await this.client.login(token);
@@ -277,7 +311,14 @@ class ControllerPlugin extends BaseControllerPlugin {
277
311
  }
278
312
  const hostName = this.getHostName(instance.config.get("instance.assigned_host"));
279
313
  const message = this.formatMessage(template, hostName, instanceName, content);
280
- await channel.send({ content: message, allowedMentions: { parse: [] } });
314
+ // try/catch: przejściowy błąd REST/permission/rate-limit nie może cicho zgubić akcji ani
315
+ // rzucić unhandled rejection z handlera eventu (spójnie z .catch w onHostConnectionEvent /
316
+ // onInstanceStatusChanged). Strata jest teraz widoczna w logach.
317
+ try {
318
+ await channel.send({ content: message, allowedMentions: { parse: [] } });
319
+ } catch (err) {
320
+ this.logger.error(`Failed to send ${action} to Discord:\n${err?.stack ?? err}`);
321
+ }
281
322
  }
282
323
  }
283
324
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "main.js": "static/main.42d03a9854919d3e8189.js",
3
- "discord_bridge.js": "static/discord_bridge.3b69d59522327ecb3b8a.js",
3
+ "discord_bridge.js": "static/discord_bridge.19d9629215eba402e78a.js",
4
4
  "static/info_js.js": "static/info_js.131defcd9652f0e24d4a.js",
5
- "static/package_json.js": "static/package_json.04ae25900d51f777e4ec.js"
5
+ "static/package_json.js": "static/package_json.84f9beada3e5dfdb2a77.js"
6
6
  }
@@ -123,7 +123,7 @@ __webpack_require__.d(exports, {
123
123
  /******/ // This function allow to reference async chunks
124
124
  /******/ __webpack_require__.u = (chunkId) => {
125
125
  /******/ // return url for filenames based on template
126
- /******/ return "static/" + chunkId + "." + {"info_js":"131defcd9652f0e24d4a","package_json":"04ae25900d51f777e4ec"}[chunkId] + ".js";
126
+ /******/ return "static/" + chunkId + "." + {"info_js":"131defcd9652f0e24d4a","package_json":"84f9beada3e5dfdb2a77"}[chunkId] + ".js";
127
127
  /******/ };
128
128
  /******/ })();
129
129
  /******/
@@ -15,7 +15,7 @@
15
15
  \**********************/
16
16
  (module) {
17
17
 
18
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@rithien/discord_bridge","version":"0.3.4","description":"Clusterio plugin bridging chat between instances and Discord, with per-instance channel routing","main":"info.js","scripts":{"test":"echo \\"Error: no test specified\\" && exit 1","prepare":"webpack-cli --env production"},"keywords":["clusterio","clusterio-plugin","factorio","discord"],"author":"rithien <jacek@zaluzje.bialystok.pl>","license":"MIT","peerDependencies":{"@clusterio/lib":"^2.0.0-alpha.14"},"devDependencies":{"@clusterio/lib":"^2.0.0-alpha.14","@clusterio/web_ui":"^2.0.0-alpha.14","webpack":"^5.88.2","webpack-cli":"^5.1.4","webpack-merge":"^5.9.0"},"dependencies":{"discord.js":"^14.14.1"},"publishConfig":{"access":"public"}}');
18
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@rithien/discord_bridge","version":"0.3.5","description":"Clusterio plugin bridging chat between instances and Discord, with per-instance channel routing","main":"info.js","scripts":{"test":"echo \\"Error: no test specified\\" && exit 1","prepare":"webpack-cli --env production"},"keywords":["clusterio","clusterio-plugin","factorio","discord"],"author":"rithien <jacek@zaluzje.bialystok.pl>","license":"MIT","peerDependencies":{"@clusterio/lib":"^2.0.0-alpha.14"},"devDependencies":{"@clusterio/lib":"^2.0.0-alpha.14","@clusterio/web_ui":"^2.0.0-alpha.14","webpack":"^5.88.2","webpack-cli":"^5.1.4","webpack-merge":"^5.9.0"},"dependencies":{"discord.js":"^14.14.1"},"publishConfig":{"access":"public"}}');
19
19
 
20
20
  /***/ }
21
21
 
package/instance.js CHANGED
@@ -24,7 +24,12 @@ class InstancePlugin extends BaseInstancePlugin {
24
24
  }
25
25
 
26
26
  onControllerConnectionEvent(event) {
27
- if (event === "connect") {
27
+ // clusterio emituje connect/drop/resume/close. Po krótkim lagu sieci leci "resume"
28
+ // (wznowienie sesji), NIE "connect" (ten tylko przy nowej sesji, np. restart kontrolera).
29
+ // Bez wariantu "resume" akcje z okna dropu (JOIN/LEAVE/CHAT) zostawały uwięzione w kolejce
30
+ // aż do następnego pełnego connect — objaw: gubione join/leave, zwłaszcza po bezczynności
31
+ // (ciche łącze pada niezauważone). Analog naprawionego FIX31 #2 w comfy_adapter.
32
+ if ((event === "connect" || event === "resume") && this.messageQueue.length > 0) {
28
33
  for (let [action, content] of this.messageQueue) {
29
34
  this.sendChat(action, content);
30
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rithien/discord_bridge",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Clusterio plugin bridging chat between instances and Discord, with per-instance channel routing",
5
5
  "main": "info.js",
6
6
  "scripts": {