Connect-Links im Discord anklickbar, und Mods bekommen einen eigenen Knopf
Deploy / check (push) Has been cancelled
Deploy / deploy (push) Has been cancelled

Im Kanal stand woertlich das da:

    [https://cfx.re/join/4odv68](https://cfx.re/join/4odv68)

Der Link war sein eigener Linktext. Discord macht aus der nackten URL zwischen
den eckigen Klammern zuerst selbst einen Link und kommt mit der
Masked-Link-Schreibweise dann nicht mehr durch — heraus kommen die Klammern im
Klartext. Der Kommentar an der Stelle behauptete ausserdem, so seien steam://
und Konsorten klickbar. Waren sie nie.

Jetzt sind es Link-Knoepfe unter dem Embed. Die Frage nach der Schreibweise
stellt sich damit gar nicht mehr, und ein Knopf sieht auch nach einem aus.
Discord nimmt dort nur http(s) an; fivem:// und steam:// stehen deshalb weiter
im Embed, aber als Codeblock zum Kopieren statt als toter Link.

Dazu ein eigenes Feld fuer den Mod-Download. Vorher landete der beim
Connect-Link, weil es kein anderes gab — und dann fuehrte "Verbinden" zu einer
ZIP-Datei. Farming Simulator liefert die Modliste ohnehin mit, also steht die
Anzahl jetzt auch im Embed und als Chip auf der Seite.

Beim Testen aufgefallen: eine neue Spalte hat jeden Aufrufer umgebracht, der
sie noch nicht kannte — better-sqlite3 wirft bei einem fehlenden benannten
Parameter. createGameserver und updateGameserver fuellen die Felder jetzt
einmal auf, statt das an jeder Aufrufstelle nachzuziehen.

Geprueft: was in einen Knopf darf und was nicht (https, http, fivem://,
steam://, leer, null, Leerzeichen, Satz mit Link drin), beide Knoepfe in einer
Reihe, nur einer, gar keiner — eine leere Reihe weist Discord ab —, https
landet im Knopf und nicht doppelt auch im Embed, fivem:// umgekehrt, Mods bei
47/0/null, und der Aufruf ohne die neue Spalte. Seite im laufenden Frontend
angesehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-08 22:52:08 +02:00
co-authored by Claude Opus 5
parent 8981eb91b8
commit eec170ff41
8 changed files with 91 additions and 18 deletions
+46 -6
View File
@@ -1,7 +1,7 @@
// Game-Server-Monitor v3 (DiscordGSM-Stil): gamedig-Queries für 300+ Spiele,
// pro Server ein Live-Embed (Spieler-Balken, Map, Player-Liste, Connect-Link),
// Down/Up-Alerts mit 2-Fails-Schwelle. Verwaltet im Setup-Tab "Server".
import { ActivityType, EmbedBuilder, Events } from 'discord.js';
import { ActionRowBuilder, ActivityType, ButtonBuilder, ButtonStyle, EmbedBuilder, Events } from 'discord.js';
import { GameDig, games as gamedigGames } from 'gamedig';
import { getSetting, listGameservers, setGameserverMessage, recordPlayerSample, prunePlayerHistory } from '../db.js';
import { brandFooter, botStatusText } from '../runtime-settings.js';
@@ -75,7 +75,7 @@ export const lastResults = new Map(); // id → Query-Ergebnis + checkedAt
export async function queryServer(server) {
const base = {
...server, online: false, players: null, max: null, map: null,
playerNames: [], ping: null, version: null,
playerNames: [], ping: null, version: null, mods: null,
};
const started = Date.now();
try {
@@ -118,6 +118,9 @@ export async function queryServer(server) {
max: state.maxplayers ?? null,
map: state.map || null,
version: state.version || null,
// Nur Farming Simulator liefert die Modliste mit; anderswo bleibt
// das Feld leer statt auf 0 zu stehen und eine Aussage zu machen.
mods: Array.isArray(state.raw?.mods) ? state.raw.mods.length : null,
playerNames: (state.players ?? []).map((p) => p?.name).filter(Boolean).slice(0, 15),
};
} catch {
@@ -125,6 +128,38 @@ export async function queryServer(server) {
}
}
/** Nur http(s) darf in einen Link-Knopf — Discord weist alles andere ab */
export function istWebLink(url) {
return /^https?:\/\/\S+$/i.test(String(url ?? '').trim());
}
/**
* Die klickbaren Links unter dem Embed.
*
* Vorher stand der Connect-Link als `[url](url)` im Embed-Feld. Discord macht
* aus der nackten URL zwischen den Klammern selbst einen Link und kommt mit
* der Masked-Link-Schreibweise dann nicht mehr durch — im Kanal standen die
* eckigen Klammern wörtlich da. Ein Knopf lässt diese Frage gar nicht erst
* aufkommen und ist obendrein als Knopf erkennbar.
*/
export function serverKnoepfe(r) {
const knoepfe = [
['🎮 Verbinden', r.connect_url],
['📦 Mods laden', r.mods_url],
]
.filter(([, url]) => istWebLink(url))
.map(([label, url]) => new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel(label)
.setURL(url.trim()));
return knoepfe.length > 0 ? [new ActionRowBuilder().addComponents(knoepfe)] : [];
}
/** Embed + Knöpfe — das, was der Monitor in den Kanal schreibt */
export function buildServerNachricht(r) {
return { embeds: [buildServerEmbed(r)], components: serverKnoepfe(r) };
}
/** GSM-Style-Embed für ein Query-Ergebnis */
export function buildServerEmbed(r) {
const icon = GAME_ICONS[r.type] ?? '🎮';
@@ -152,9 +187,14 @@ export function buildServerEmbed(r) {
}
if (r.map) embed.addFields({ name: 'Map', value: r.map, inline: true });
if (r.version) embed.addFields({ name: 'Version', value: String(r.version).slice(0, 60), inline: true });
if (r.connect_url) {
// steam:// & Co. sind in Embeds klickbar (Link-Buttons erlauben nur https)
embed.addFields({ name: 'Connect', value: `[${r.connect_url}](${r.connect_url})`, inline: false });
if (r.mods != null) embed.addFields({ name: 'Mods', value: String(r.mods), inline: true });
// Connect und Mods stehen als Knopf darunter, wenn es https ist. Alles
// andere (fivem://, steam://) macht Discord nicht klickbar — dann lieber
// als Codeblock zum Kopieren, statt einen toten Link vorzugaukeln.
for (const [titel, url] of [['Connect', r.connect_url], ['Mods', r.mods_url]]) {
if (url && !istWebLink(url)) {
embed.addFields({ name: titel, value: `\`\`\`\n${url}\n\`\`\`` });
}
}
if (r.address) embed.addFields({ name: 'Adresse', value: `\`\`\`\n${r.address}\n\`\`\`` });
if (r.online && r.playerNames.length > 0) {
@@ -241,7 +281,7 @@ export async function monitorTick(client) {
if (!channel?.isTextBased()) return;
for (const r of results) {
const payload = { embeds: [buildServerEmbed(r)] };
const payload = buildServerNachricht(r);
try {
if (r.message_id) {
const existing = await channel.messages.fetch(r.message_id).catch(() => null);