Server-Embed aufgeraeumt: volle Reihen statt halber
Discord setzt drei Inline-Felder in eine Reihe. Es waren fuenf — Status, Spieler, Map, Version, Mods —, also drei plus zwei, und die zweite Reihe stand halb leer. Bei einem Server, der weniger meldet, stand ein einzelnes Feld allein herum. Neu sortiert nach dem, was man wissen will: Der Zustand ist die Ueberschrift, kein Datenpunkt. Er steht jetzt in der Beschreibung, eine Zeile mit Trennpunkten: online, Schloss, Ping. Das spart eine ganze Feldreihe und liest sich als Satz. Der Ping ist damit aus der Fusszeile raus, wo er zwischen Marke und Intervall stand. Die Auslastung ist die Zahl, wegen der man hinschaut — sie bekommt die volle Breite statt eines Drittels, und der Balken wurde von zehn auf sechzehn Zeichen breiter. Map, Version und Mods bilden genau eine Reihe. Fehlt eins davon, fuellen unsichtbare Felder auf, damit die Reihe nicht schief steht. Adresse und alles, was Discord nicht klickbar macht — fivem://, steam://, ts3server:// —, standen als je eigener Codeblock untereinander. Das war eine Wand aus Kaesten. Jetzt ein Block "Zum Kopieren" mit allem drin. Nebenbei: fuer den Namen im Spiel hatte ich Discords Kleintext-Auszeichnung genommen. Die rendert in Embeds nicht verlaesslich, und dann staende das Steuerzeichen woertlich da. Kursiv tut es auch. Geprueft: drei Zusammenstellungen im Klartext durchgespielt (LS mit allem, FiveM mit wenig, offline), und die Auffuellung mit einem, zwei und drei Feldern sowie ohne. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+60
-36
@@ -217,58 +217,82 @@ export function buildServerNachricht(r) {
|
||||
}
|
||||
|
||||
/** GSM-Style-Embed für ein Query-Ergebnis */
|
||||
// Discord setzt drei Inline-Felder in eine Reihe. Bleiben eins oder zwei
|
||||
// übrig, steht die letzte Reihe halb leer und alles wirkt schief. Ein Feld
|
||||
// aus unsichtbaren Zeichen füllt die Lücke — das ist der übliche Kniff, und
|
||||
// billiger als eine Layout-Entscheidung pro Kombination.
|
||||
const LEER = '';
|
||||
function dreierReihe(felder) {
|
||||
const drin = felder.filter(Boolean);
|
||||
if (drin.length === 0) return [];
|
||||
while (drin.length % 3 !== 0) drin.push({ name: LEER, value: LEER, inline: true });
|
||||
return drin;
|
||||
}
|
||||
|
||||
/** Auslastung als Balken — breiter als die alten zehn Zeichen */
|
||||
function spielerBalken(players, max, breite = 16) {
|
||||
const anteil = Math.max(0, Math.min(1, players / max));
|
||||
const voll = Math.round(anteil * breite);
|
||||
return `\`${'█'.repeat(voll)}${'░'.repeat(breite - voll)}\` **${players}**/${max} · ${Math.round(anteil * 100)} %`;
|
||||
}
|
||||
|
||||
export function buildServerEmbed(r) {
|
||||
const icon = GAME_ICONS[r.type] ?? '🎮';
|
||||
const zugang = zugangStand(r, r);
|
||||
const embed = new EmbedBuilder()
|
||||
.setColor(r.online ? GREEN : RED)
|
||||
.setTitle(`${icon} ${r.name}${zugang.zu ? ' 🔒' : ''}`)
|
||||
.addFields({
|
||||
name: 'Status',
|
||||
value: `${r.online ? '🟢 **Online**' : '🔴 **Offline**'}${zugang.grund ? `\n🔒 ${zugang.grund}` : ''}`,
|
||||
inline: true,
|
||||
})
|
||||
.setTitle(`${icon} ${r.name}${zugang.zu ? ' 🔒' : ''}`)
|
||||
// Intervall aus den Werten lesen, nicht festschreiben — sonst behauptet
|
||||
// das Embed weiter „alle 2 min", nachdem im Panel etwas anderes steht.
|
||||
.setFooter({
|
||||
text: `${brandFooter('STATUS')}${r.ping != null ? ` • ${r.ping}ms` : ''}`
|
||||
+ ` • alle ${tuning('monitor_interval')} min`,
|
||||
})
|
||||
.setFooter({ text: `${brandFooter('STATUS')} • alle ${tuning('monitor_interval')} min` })
|
||||
.setTimestamp();
|
||||
if (r.image_url) embed.setThumbnail(r.image_url);
|
||||
// Wie der Server sich im Spiel nennt. Nur wenn es etwas anderes ist als
|
||||
// der Name im Panel — sonst stünde dieselbe Zeile zweimal da.
|
||||
if (r.spielname && r.spielname.trim() && r.spielname.trim() !== r.name?.trim()) {
|
||||
embed.setDescription(`*${r.spielname.trim().slice(0, 200)}*`);
|
||||
|
||||
// Kopf: Name im Spiel und der Zustand in einer Zeile. Als Beschreibung
|
||||
// statt als Feld — der Status ist die Überschrift und kein Datenpunkt,
|
||||
// und ein eigenes Feld dafür verbrauchte eine ganze Reihe.
|
||||
const kopf = [];
|
||||
if (r.spielname?.trim() && r.spielname.trim() !== r.name?.trim()) {
|
||||
// Kursiv statt Discords Kleintext-Auszeichnung: die rendert in Embeds
|
||||
// nicht verlässlich, und dann stünde das Steuerzeichen wörtlich da.
|
||||
kopf.push(`*${r.spielname.trim().slice(0, 200)}*`);
|
||||
}
|
||||
kopf.push([
|
||||
r.online ? '🟢 **Online**' : '🔴 **Offline**',
|
||||
zugang.grund ? `🔒 ${zugang.grund}` : null,
|
||||
r.online && r.ping != null ? `${r.ping} ms` : null,
|
||||
].filter(Boolean).join(' · '));
|
||||
embed.setDescription(kopf.join('\n'));
|
||||
|
||||
// Die Zahl, wegen der man hinschaut — über die volle Breite
|
||||
if (r.online && r.players != null && r.max) {
|
||||
embed.addFields({ name: 'Auslastung', value: spielerBalken(r.players, r.max) });
|
||||
}
|
||||
|
||||
if (r.online && r.players != null && r.max) {
|
||||
const pct = Math.min(1, r.players / r.max);
|
||||
const bar = '█'.repeat(Math.round(pct * 10)).padEnd(10, '░');
|
||||
embed.addFields({
|
||||
name: 'Spieler',
|
||||
value: `\`${bar}\` ${r.players}/${r.max} (${Math.round(pct * 100)}%)`,
|
||||
inline: true,
|
||||
});
|
||||
}
|
||||
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.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 l of serverLinks(r)) {
|
||||
if (!istWebLink(l.url)) {
|
||||
embed.addFields({ name: String(l.label).trim().slice(0, 256), value: `\`\`\`\n${l.url}\n\`\`\`` });
|
||||
}
|
||||
}
|
||||
if (r.address) embed.addFields({ name: 'Adresse', value: `\`\`\`\n${r.address}\n\`\`\`` });
|
||||
// Genau eine volle Reihe, notfalls aufgefüllt
|
||||
embed.addFields(...dreierReihe([
|
||||
r.map ? { name: 'Map', value: String(r.map).slice(0, 60), inline: true } : null,
|
||||
r.version ? { name: 'Version', value: String(r.version).slice(0, 60), inline: true } : null,
|
||||
r.mods != null ? { name: 'Mods', value: String(r.mods), inline: true } : null,
|
||||
]));
|
||||
|
||||
if (r.online && r.playerNames.length > 0) {
|
||||
embed.addFields({
|
||||
name: `Spieler-Liste (${r.playerNames.length})`,
|
||||
name: `Wer drauf ist (${r.playerNames.length})`,
|
||||
value: r.playerNames.map((n) => `\`${n}\``).join(' ').slice(0, 1000),
|
||||
});
|
||||
}
|
||||
|
||||
// Zum Kopieren: die Adresse und alles, was Discord nicht klickbar macht
|
||||
// (fivem://, steam://, ts3server://). Ein Codeblock je Eintrag wäre eine
|
||||
// Wand — deshalb alle zusammen in einen.
|
||||
const zumKopieren = [
|
||||
r.address ? `${r.address}` : null,
|
||||
...serverLinks(r).filter((l) => !istWebLink(l.url)).map((l) => `${String(l.label).trim()}: ${l.url}`),
|
||||
].filter(Boolean);
|
||||
if (zumKopieren.length > 0) {
|
||||
embed.addFields({ name: 'Zum Kopieren', value: `\`\`\`\n${zumKopieren.join('\n').slice(0, 1000)}\n\`\`\`` });
|
||||
}
|
||||
return embed;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user