Chat-Overlay: echte Twitch-Abzeichen statt farbiger Punkte
Der Hub loest die Abzeichen aus dem IRC-Tag ueber Helix (global und Kanal) zu Bildern auf und schickt sie mit jeder Chatzeile mit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
884e4517d8
commit
67ca472317
13 files changed
+171
-19
No files matched your search
@@ -30,6 +30,7 @@ import { createBot } from '#discord/bot.ts';
|
||||
import { helix } from '#twitch/helix.ts';
|
||||
import { readSecrets } from '#twitch/secrets.ts';
|
||||
import { createEventSub } from '#twitch/eventsub.ts';
|
||||
import { createBadges } from '#twitch/badges.ts';
|
||||
import { createIrc, type UserNotice } from '#twitch/irc.ts';
|
||||
import { streamWechsel } from '#twitch/stream-state.ts';
|
||||
import { piperFehlt, sprechen, stimmenPfad } from '#media/piper.ts';
|
||||
@@ -245,6 +246,21 @@ export async function runHub(): Promise<void> {
|
||||
const bot = createBot();
|
||||
if (bot) void bot.run(abort.signal);
|
||||
|
||||
// ---- Abzeichen-Bilder fuer den Chat. Twitch aendert sie selten, ein
|
||||
// neues Abo-Abzeichen des Kanals ist nach spaetestens sechs Stunden da.
|
||||
const badges = createBadges();
|
||||
void (async () => {
|
||||
while (!abort.signal.aborted) {
|
||||
if (helix.canReadStream()) {
|
||||
const id = state.broadcasterId || cfg.channel.userId ||
|
||||
(await helix.userByLogin(cfg.channel.login).catch(() => null))?.id || null;
|
||||
if (id) state.broadcasterId = id;
|
||||
await badges.laden(id);
|
||||
}
|
||||
try { await sleep(6 * 60 * 60_000, abort.signal); } catch { return; }
|
||||
}
|
||||
})();
|
||||
|
||||
// ---- Chat
|
||||
const irc = createIrc(cfg.channel.login, {
|
||||
onConnected: (anonymous) => {
|
||||
@@ -273,6 +289,7 @@ export async function runHub(): Promise<void> {
|
||||
mod: msg.isMod,
|
||||
sub: msg.isSub,
|
||||
broadcaster: msg.isBroadcaster,
|
||||
badges: badges.aufloesen(msg.badges),
|
||||
bits: msg.bits,
|
||||
});
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ function msg(text: string, userId: string, advanceMs = 0): ChatMessage {
|
||||
id: `m${clock}-${userId}`, ts: new Date(clock).toISOString(), userId,
|
||||
login: `u${userId}`, displayName: `U${userId}`, text, emotes: [], gifs: [],
|
||||
isFirst: false, isReturning: false, isCommand: text.startsWith('!'),
|
||||
isMod: false, isSub: false, isBroadcaster: false, bits: 0, color: null,
|
||||
isMod: false, isSub: false, isBroadcaster: false, badges: [], bits: 0, color: null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ function nachricht(over: Partial<ChatEvent> = {}): ChatEvent {
|
||||
return {
|
||||
kind: 'chat', id: 'm1', userId: 'u1', login: 'kevin', name: 'Kevin',
|
||||
text: 'moin', color: null, emotes: [], gifs: [], first: false, mod: false,
|
||||
sub: false, broadcaster: false, bits: 0,
|
||||
sub: false, broadcaster: false, badges: [], bits: 0,
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ export type ChatEvent = {
|
||||
mod: boolean;
|
||||
sub: boolean;
|
||||
broadcaster: boolean;
|
||||
/** Abzeichen als fertige Bilder, aufgeloest im Hub (#twitch/badges.ts). */
|
||||
badges: Array<{ url: string; title: string }>;
|
||||
bits: number;
|
||||
};
|
||||
|
||||
|
||||
@@ -96,6 +96,23 @@ export const TEST_KNOEPFE: TestKnopf[] = [
|
||||
{ art: 'live-aus', label: 'Stand zurücksetzen', gruppe: 'Stand' },
|
||||
];
|
||||
|
||||
// Twitchs globale Abzeichen. Die Adressen sind fest, so zeigt die Vorfuehrung
|
||||
// echte Bilder, ohne Helix fragen zu muessen.
|
||||
const ABZEICHEN = 'https://static-cdn.jtvnw.net/badges/v1/';
|
||||
const MOD = { url: `${ABZEICHEN}3267646d-33f0-4b17-b3df-f923a41db1d0/2`, title: 'Moderator' };
|
||||
const VIP = { url: `${ABZEICHEN}b817aba4-fad8-49e2-b88a-7cc744dfa6ec/2`, title: 'VIP' };
|
||||
const ABO = { url: `${ABZEICHEN}5d9f2208-5dd8-11e7-8513-2ff4adfae661/2`, title: 'Subscriber' };
|
||||
|
||||
function zufallsAbzeichen(): Pick<ChatEvent, 'mod' | 'sub' | 'badges'> {
|
||||
const mod = Math.random() < 0.15;
|
||||
const vip = !mod && Math.random() < 0.1;
|
||||
const sub = Math.random() < 0.3;
|
||||
return {
|
||||
mod, sub,
|
||||
badges: [...(mod ? [MOD] : []), ...(vip ? [VIP] : []), ...(sub ? [ABO] : [])],
|
||||
};
|
||||
}
|
||||
|
||||
function chat(over: Partial<ChatEvent> = {}): ChatEvent {
|
||||
const name = zufall(NAMEN);
|
||||
return {
|
||||
@@ -109,8 +126,7 @@ function chat(over: Partial<ChatEvent> = {}): ChatEvent {
|
||||
emotes: [],
|
||||
gifs: [],
|
||||
first: false,
|
||||
mod: Math.random() < 0.15,
|
||||
sub: Math.random() < 0.3,
|
||||
...zufallsAbzeichen(),
|
||||
broadcaster: false,
|
||||
bits: 0,
|
||||
...over,
|
||||
|
||||
@@ -53,18 +53,15 @@
|
||||
background: rgba(0, 0, 0, .35);
|
||||
}
|
||||
|
||||
/* Abzeichen statt Bilder: Twitchs Abzeichen-URLs haengen am Kanal und muessten
|
||||
* ueber Helix aufgeloest werden. Ein farbiger Punkt sagt dasselbe. */
|
||||
/* Twitchs eigene Abzeichen. Die Bilder loest der Hub ueber Helix auf und
|
||||
* schickt sie mit jeder Zeile mit (#twitch/badges.ts). */
|
||||
.abzeichen {
|
||||
display: inline-block;
|
||||
width: .5em; height: .5em;
|
||||
border-radius: 50%;
|
||||
margin-right: .4em;
|
||||
vertical-align: .08em;
|
||||
height: 1.05em;
|
||||
width: auto;
|
||||
margin-right: .3em;
|
||||
vertical-align: -.17em;
|
||||
}
|
||||
.abzeichen.mod { background: var(--gruen); }
|
||||
.abzeichen.abo { background: var(--lila); }
|
||||
.abzeichen.host { background: var(--rot); }
|
||||
.abzeichen:last-of-type { margin-right: .45em; }
|
||||
|
||||
/* Wer zum ersten Mal schreibt, wird hervorgehoben - das ist der Moment, in
|
||||
* dem eine Begruessung am meisten bringt. Ohne den Balken an der linken Kante
|
||||
@@ -152,9 +149,13 @@
|
||||
el.dataset.user = ev.userId;
|
||||
|
||||
var abzeichen = '';
|
||||
if (ev.broadcaster) abzeichen += '<span class="abzeichen host"></span>';
|
||||
else if (ev.mod) abzeichen += '<span class="abzeichen mod"></span>';
|
||||
if (ev.sub) abzeichen += '<span class="abzeichen abo"></span>';
|
||||
var bilder = ev.badges || [];
|
||||
for (var i = 0; i < bilder.length; i++) {
|
||||
// Nur Twitchs eigene Bildadressen: was hier steht, laedt OBS.
|
||||
if (!/^https:\/\/static-cdn\.jtvnw\.net\//.test(bilder[i].url)) continue;
|
||||
abzeichen += '<img class="abzeichen" src="' + Overlay.esc(bilder[i].url) +
|
||||
'" alt="' + Overlay.esc(bilder[i].title) + '">';
|
||||
}
|
||||
|
||||
el.innerHTML =
|
||||
abzeichen +
|
||||
|
||||
Reference in new issue
Block a user