cubyz-discord-relay 2.4.0 → 2.4.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/README.md CHANGED
@@ -14,7 +14,7 @@
14
14
  - Provides a `/list` Discord command to show the players currently online
15
15
  - Cleans Cubyz markdown-style usernames and censors configurable words
16
16
  - Automatic reconnection with exponential backoff and retry limits
17
- - Optional integration advertises your server on [cubyzlist.site](https://cubyzlist.site)
17
+ - Optional integration advertises your server on [servers.ashframe.net](https://servers.ashframe.net)
18
18
 
19
19
  ## Prerequisites
20
20
 
@@ -79,15 +79,20 @@ npm install
79
79
  - `connection.reconnect`: enable/disable automatic reconnect attempts
80
80
  - `connection.maxRetries`: maximum reconnect attempts (`0` = infinite)
81
81
  - `connection.retryDelayMs`: initial delay before retrying (milliseconds)
82
- - `integration.cubyzlistSite.enabled`: toggle advertising to the community server list
83
- - `integration.cubyzlistSite.serverName`: required display name shown on cubyzlist.site, must be unique
84
- - `integration.cubyzlistSite.serverIp`: public hostname or IP that players should connect to
85
- - `integration.cubyzlistSite.serverPort`: public port exposed for players (defaults to `47649` if omitted)
86
- - `integration.cubyzlistSite.iconUrl`: optional URL for the list thumbnail
87
- - `integration.cubyzlistSite.customClientDownloadUrl`: optional link to a custom client build
88
82
 
89
83
  > First run convenience: if `config.json` is missing, the application writes a fresh template in your working directory and exits so you can fill it in before retrying.
90
84
 
85
+ ### CubyzListSite Integration
86
+
87
+ - `integration.cubyzlistSite.enabled`: toggle advertising to the community server list
88
+ - `integration.cubyzlistSite.serverName`: required display name shown on servers.ashframe.net, must be unique; if missing while enabled, the integration logs a warning and stays disabled
89
+ - `integration.cubyzlistSite.serverIp`: required public hostname or IP that players should connect to; if missing while enabled, the integration logs a warning and stays disabled
90
+ - `integration.cubyzlistSite.description`: optional short description shown on servers.ashframe.net
91
+ - `integration.cubyzlistSite.serverPort`: optional public port exposed for players
92
+ - `integration.cubyzlistSite.iconUrl`: optional URL for the list thumbnail
93
+ - `integration.cubyzlistSite.discordServer`: optional Discord invite or server URL
94
+ - `integration.cubyzlistSite.customClientDownloadUrl`: optional link to a custom client build
95
+
91
96
  ### Usage
92
97
 
93
98
  ```bash
@@ -31,7 +31,9 @@
31
31
  "serverName": "My Cubyz Server",
32
32
  "serverIp": "play.yourserver.com",
33
33
  "serverPort": 47649,
34
+ "description": "A friendly public survival server",
34
35
  "iconUrl": "https://github.com/PixelGuys/Cubyz/blob/master/assets/cubyz/logo.png?raw=true",
36
+ "discordServer": "https://discord.gg/your-server",
35
37
  "customClientDownloadUrl": "https://github.com/PixelGuys/Cubyz/releases/tag/0.0.0"
36
38
  }
37
39
  }
package/dist/config.js CHANGED
@@ -2,6 +2,7 @@ import { access, copyFile, mkdir, readFile } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
4
  import { fileURLToPath } from "node:url";
5
+ import { createLogger } from "./logger.js";
5
6
  const DEFAULT_EVENTS = ["join", "leave", "death", "chat"];
6
7
  const SUPPORTED_EVENTS = [...DEFAULT_EVENTS];
7
8
  const DEFAULT_CENSORLIST = [];
@@ -32,8 +33,10 @@ const DEFAULT_CUBYZLIST_SITE = {
32
33
  enabled: false,
33
34
  serverName: "",
34
35
  serverIp: "",
35
- serverPort: 47649,
36
+ description: undefined,
37
+ serverPort: undefined,
36
38
  iconUrl: undefined,
39
+ discordServer: undefined,
37
40
  customClientDownloadUrl: undefined,
38
41
  };
39
42
  const CONFIG_TEMPLATE_PATH = fileURLToPath(new URL("../config.example.json", import.meta.url));
@@ -65,6 +68,18 @@ function coercePort(value, fallback) {
65
68
  }
66
69
  return fallback;
67
70
  }
71
+ function coerceOptionalPort(value) {
72
+ if (value === undefined || value === null || value === "") {
73
+ return undefined;
74
+ }
75
+ if (typeof value === "number" &&
76
+ Number.isInteger(value) &&
77
+ value > 0 &&
78
+ value <= 65535) {
79
+ return value;
80
+ }
81
+ return undefined;
82
+ }
68
83
  function coerceString(value, fallback) {
69
84
  return typeof value === "string" && value.trim().length > 0
70
85
  ? value.trim()
@@ -124,8 +139,10 @@ function applyDefaults(partial) {
124
139
  : DEFAULT_CUBYZLIST_SITE.enabled,
125
140
  serverName: coerceString(partial.integration?.cubyzlistSite?.serverName, DEFAULT_CUBYZLIST_SITE.serverName),
126
141
  serverIp: coerceString(partial.integration?.cubyzlistSite?.serverIp, DEFAULT_CUBYZLIST_SITE.serverIp),
127
- serverPort: coercePort(partial.integration?.cubyzlistSite?.serverPort, DEFAULT_CUBYZLIST_SITE.serverPort),
142
+ description: coerceString(partial.integration?.cubyzlistSite?.description, DEFAULT_CUBYZLIST_SITE.description ?? "") || undefined,
143
+ serverPort: coerceOptionalPort(partial.integration?.cubyzlistSite?.serverPort),
128
144
  iconUrl: coerceString(partial.integration?.cubyzlistSite?.iconUrl, DEFAULT_CUBYZLIST_SITE.iconUrl ?? "") || undefined,
145
+ discordServer: coerceString(partial.integration?.cubyzlistSite?.discordServer, DEFAULT_CUBYZLIST_SITE.discordServer ?? "") || undefined,
129
146
  customClientDownloadUrl: coerceString(partial.integration?.cubyzlistSite?.customClientDownloadUrl, DEFAULT_CUBYZLIST_SITE.customClientDownloadUrl ?? "") || undefined,
130
147
  };
131
148
  const integration = {
@@ -172,6 +189,25 @@ function applyDefaults(partial) {
172
189
  integration,
173
190
  };
174
191
  }
192
+ function finalizeConfig(config) {
193
+ const cubyzlist = config.integration.cubyzlistSite;
194
+ if (!cubyzlist.enabled) {
195
+ return config;
196
+ }
197
+ const missingFields = [];
198
+ if (cubyzlist.serverName.length === 0) {
199
+ missingFields.push("integration.cubyzlistSite.serverName");
200
+ }
201
+ if (cubyzlist.serverIp.length === 0) {
202
+ missingFields.push("integration.cubyzlistSite.serverIp");
203
+ }
204
+ if (missingFields.length === 0) {
205
+ return config;
206
+ }
207
+ createLogger(config.logLevel)("warn", `[CubyzListSite] Disabled integration because required config field(s) are missing: ${missingFields.join(", ")}`);
208
+ cubyzlist.enabled = false;
209
+ return config;
210
+ }
175
211
  async function ensureConfigFile(resolvedPath) {
176
212
  try {
177
213
  await access(resolvedPath);
@@ -285,25 +321,33 @@ export function validateConfig(config) {
285
321
  if (typeof cubyzlist.enabled !== "boolean") {
286
322
  throw new Error('Configuration error: "integration.cubyzlistSite.enabled" must be a boolean.');
287
323
  }
288
- // Only validate required fields if enabled
324
+ if (typeof cubyzlist.serverName !== "string") {
325
+ throw new Error('Configuration error: "integration.cubyzlistSite.serverName" must be a string.');
326
+ }
327
+ if (typeof cubyzlist.serverIp !== "string") {
328
+ throw new Error('Configuration error: "integration.cubyzlistSite.serverIp" must be a string.');
329
+ }
330
+ if (cubyzlist.description !== undefined &&
331
+ (typeof cubyzlist.description !== "string" || cubyzlist.description === "")) {
332
+ throw new Error('Configuration error: "integration.cubyzlistSite.description" must be a non-empty string or undefined.');
333
+ }
289
334
  if (cubyzlist.enabled) {
290
- if (typeof cubyzlist.serverName !== "string" ||
291
- cubyzlist.serverName === "") {
292
- throw new Error('Configuration error: "integration.cubyzlistSite.serverName" must be a non-empty string when enabled.');
293
- }
294
- if (typeof cubyzlist.serverIp !== "string" || cubyzlist.serverIp === "") {
295
- throw new Error('Configuration error: "integration.cubyzlistSite.serverIp" must be a non-empty string when enabled.');
296
- }
297
- if (typeof cubyzlist.serverPort !== "number" ||
298
- !Number.isInteger(cubyzlist.serverPort) ||
299
- cubyzlist.serverPort <= 0 ||
300
- cubyzlist.serverPort > 65535) {
301
- throw new Error('Configuration error: "integration.cubyzlistSite.serverPort" must be an integer between 1 and 65535.');
335
+ if (cubyzlist.serverPort !== undefined &&
336
+ (typeof cubyzlist.serverPort !== "number" ||
337
+ !Number.isInteger(cubyzlist.serverPort) ||
338
+ cubyzlist.serverPort <= 0 ||
339
+ cubyzlist.serverPort > 65535)) {
340
+ throw new Error('Configuration error: "integration.cubyzlistSite.serverPort" must be an integer between 1 and 65535 or undefined.');
302
341
  }
303
342
  if (cubyzlist.iconUrl !== undefined &&
304
343
  (typeof cubyzlist.iconUrl !== "string" || cubyzlist.iconUrl === "")) {
305
344
  throw new Error('Configuration error: "integration.cubyzlistSite.iconUrl" must be a non-empty string or undefined.');
306
345
  }
346
+ if (cubyzlist.discordServer !== undefined &&
347
+ (typeof cubyzlist.discordServer !== "string" ||
348
+ cubyzlist.discordServer === "")) {
349
+ throw new Error('Configuration error: "integration.cubyzlistSite.discordServer" must be a non-empty string or undefined.');
350
+ }
307
351
  if (cubyzlist.customClientDownloadUrl !== undefined &&
308
352
  (typeof cubyzlist.customClientDownloadUrl !== "string" ||
309
353
  cubyzlist.customClientDownloadUrl === "")) {
@@ -319,7 +363,7 @@ export async function loadConfig(configPath) {
319
363
  if ("cubyzLogPath" in parsedUnknown) {
320
364
  throw new Error("Configuration error: detected legacy log-based settings. Update the configuration file to use the bot connection schema.");
321
365
  }
322
- const config = applyDefaults(parsedUnknown);
366
+ const config = finalizeConfig(applyDefaults(parsedUnknown));
323
367
  validateConfig(config);
324
368
  return config;
325
369
  }
@@ -6,7 +6,7 @@ import type { BaseIntegration, IntegrationStatusContext } from "./base.js";
6
6
  * Integration that sends server status updates to the Cubyz list site
7
7
  * via TCP socket connection to api.ashframe.net:5001.
8
8
  * Sends periodic updates every 5 minutes to keep the listing fresh.
9
- * @link https://cubyzlist.site
9
+ * @link https://servers.ashframe.net
10
10
  */
11
11
  export declare class CubyzListSiteIntegration implements BaseIntegration {
12
12
  readonly name = "CubyzListSite";
@@ -18,6 +18,7 @@ export declare class CubyzListSiteIntegration implements BaseIntegration {
18
18
  private readonly UPDATE_INTERVAL_MS;
19
19
  private isReady;
20
20
  private readonly config;
21
+ private readonly version;
21
22
  private readonly logger;
22
23
  constructor(config: Config);
23
24
  private log;
@@ -4,7 +4,7 @@ import { createLogger } from "../logger.js";
4
4
  * Integration that sends server status updates to the Cubyz list site
5
5
  * via TCP socket connection to api.ashframe.net:5001.
6
6
  * Sends periodic updates every 5 minutes to keep the listing fresh.
7
- * @link https://cubyzlist.site
7
+ * @link https://servers.ashframe.net
8
8
  */
9
9
  export class CubyzListSiteIntegration {
10
10
  name = "CubyzListSite";
@@ -16,9 +16,11 @@ export class CubyzListSiteIntegration {
16
16
  UPDATE_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
17
17
  isReady = false;
18
18
  config;
19
+ version;
19
20
  logger;
20
21
  constructor(config) {
21
22
  this.config = config.integration.cubyzlistSite;
23
+ this.version = config.cubyz.version;
22
24
  this.logger = createLogger(config.logLevel);
23
25
  }
24
26
  log(level, ...args) {
@@ -90,14 +92,26 @@ export class CubyzListSiteIntegration {
90
92
  const payload = {
91
93
  server_id: this.config.serverName,
92
94
  player_count: this.currentPlayers.size,
95
+ player_list: Array.from(this.currentPlayers),
93
96
  status: this.currentStatus,
94
- gamemode: this.gamemode,
95
- ip: this.config.serverIp +
96
- (this.config.serverPort ? `:${this.config.serverPort}` : ""),
97
- icon: this.config.iconUrl ?? "",
98
- client_download: this.config.customClientDownloadUrl ?? "",
99
- script_version: "1.4",
97
+ ip: this.config.serverIp,
98
+ version: this.version,
99
+ api_version: "1",
100
100
  timestamp: Math.floor(Date.now() / 1000),
101
+ ...(this.config.serverPort != null
102
+ ? { port: this.config.serverPort }
103
+ : {}),
104
+ ...(this.gamemode != null ? { gamemode: this.gamemode } : {}),
105
+ ...(this.config.description != null
106
+ ? { description: this.config.description }
107
+ : {}),
108
+ ...(this.config.iconUrl != null ? { icon: this.config.iconUrl } : {}),
109
+ ...(this.config.discordServer != null
110
+ ? { discord_server: this.config.discordServer }
111
+ : {}),
112
+ ...(this.config.customClientDownloadUrl != null
113
+ ? { client_download: this.config.customClientDownloadUrl }
114
+ : {}),
101
115
  };
102
116
  const dataString = JSON.stringify(payload);
103
117
  return new Promise((resolve, reject) => {
package/dist/types.d.ts CHANGED
@@ -16,8 +16,10 @@ export interface CubyzListSiteConfig {
16
16
  enabled: boolean;
17
17
  serverName: string;
18
18
  serverIp: string;
19
- serverPort: number;
19
+ description?: string;
20
+ serverPort?: number;
20
21
  iconUrl?: string;
22
+ discordServer?: string;
21
23
  customClientDownloadUrl?: string;
22
24
  }
23
25
  export interface IntegrationConfig {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cubyz-discord-relay",
3
- "version": "2.4.0",
3
+ "version": "2.4.2",
4
4
  "description": "Bot that relays messages between a Cubyz server and a Discord channel.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -40,4 +40,4 @@
40
40
  "tsx": "^4.20.6",
41
41
  "typescript": "^5.9.3"
42
42
  }
43
- }
43
+ }