From 15c8abc2c3753bd5af654ed8690a59c327317e23 Mon Sep 17 00:00:00 2001 From: qwerinope Date: Fri, 25 Jul 2025 23:54:02 +0100 Subject: [PATCH] implement a basic alerts template --- src/web/alerts/serverFunctions.ts | 13 +++++++ src/web/alerts/types.ts | 37 +++++++++++++++++++ src/web/alerts/www/index.html | 12 ++++++ src/web/alerts/www/src/alerts/alertManager.ts | 30 +++++++++++++++ src/web/alerts/www/src/alerts/index.ts | 14 +++++++ src/web/alerts/www/src/alerts/userBlast.ts | 18 +++++++++ src/web/alerts/www/src/main.ts | 25 +++++++++++++ src/web/chatWidget/websockettypes.ts | 23 ++++++------ src/web/chatWidget/widgetServerFunctions.ts | 2 +- src/web/chatWidget/www/src/main.ts | 8 ++-- src/web/index.ts | 24 ++++++------ src/web/serverTypes.ts | 13 +++++++ tsconfig.bot.json | 2 +- tsconfig.json | 2 +- tsconfig.chatwidget.json => tsconfig.web.json | 5 +-- 15 files changed, 194 insertions(+), 34 deletions(-) create mode 100644 src/web/alerts/serverFunctions.ts create mode 100644 src/web/alerts/types.ts create mode 100644 src/web/alerts/www/index.html create mode 100644 src/web/alerts/www/src/alerts/alertManager.ts create mode 100644 src/web/alerts/www/src/alerts/index.ts create mode 100644 src/web/alerts/www/src/alerts/userBlast.ts create mode 100644 src/web/alerts/www/src/main.ts create mode 100644 src/web/serverTypes.ts rename tsconfig.chatwidget.json => tsconfig.web.json (52%) diff --git a/src/web/alerts/serverFunctions.ts b/src/web/alerts/serverFunctions.ts new file mode 100644 index 0000000..8e73d43 --- /dev/null +++ b/src/web/alerts/serverFunctions.ts @@ -0,0 +1,13 @@ +import server from "web"; +import type { alert, alertEventData } from "./types"; + +export async function sendAlertEvent(event: alertEventData) { + server.publish('alerts', JSON.stringify(event)); +}; + +export async function playAlert(alert: alert) { + await sendAlertEvent({ + function: 'playAlert', + alert + }); +}; diff --git a/src/web/alerts/types.ts b/src/web/alerts/types.ts new file mode 100644 index 0000000..de03dba --- /dev/null +++ b/src/web/alerts/types.ts @@ -0,0 +1,37 @@ +import type { serverNotificationEvent } from "web/serverTypes"; + +type alertBase = { + name: name; + user: string; +}; + +export type userBlastAlert = alertBase<'userBlast'> & { + target: string; +}; + +export type userExecutionAlert = alertBase<'userExecution'> & { + target: string; +}; + +export type grenadeExplosionAlert = alertBase<'grenadeExplosion'> & { + target: string; +}; + +export type tntExplosionAlert = alertBase<'tntExplosion'> & { + targets: string[]; +}; + +export type alert = + | userBlastAlert + | userExecutionAlert + | grenadeExplosionAlert + | tntExplosionAlert; + +type playAlertEvent = { + function: 'playAlert'; + alert: alert; +}; + +export type alertEventData = + | playAlertEvent + | serverNotificationEvent; diff --git a/src/web/alerts/www/index.html b/src/web/alerts/www/index.html new file mode 100644 index 0000000..b4f07ed --- /dev/null +++ b/src/web/alerts/www/index.html @@ -0,0 +1,12 @@ + + + + + + qwerinope's alert server + + +
+ + + diff --git a/src/web/alerts/www/src/alerts/alertManager.ts b/src/web/alerts/www/src/alerts/alertManager.ts new file mode 100644 index 0000000..128df49 --- /dev/null +++ b/src/web/alerts/www/src/alerts/alertManager.ts @@ -0,0 +1,30 @@ +import { alert } from "web/alerts/types"; +import alerts from "./index"; + +class AlertManager { + #busy: boolean; + #alertQueue: alert[]; + + constructor() { + this.#busy = false; + this.#alertQueue = []; + }; + + private async playAlert(alert: alert) { + this.#busy = true; + + await alerts[alert.name](alert); + + const nextAlert = this.#alertQueue.shift(); + if (!nextAlert) { this.#busy = false; return; }; // if .shift has no results, we done and return + + this.playAlert(nextAlert); + }; + + async queueAlert(alert: alert) { + if (this.#busy) { this.#alertQueue.push(alert); return; }; + await this.playAlert(alert); + }; +}; + +export default new AlertManager(); diff --git a/src/web/alerts/www/src/alerts/index.ts b/src/web/alerts/www/src/alerts/index.ts new file mode 100644 index 0000000..ae9a028 --- /dev/null +++ b/src/web/alerts/www/src/alerts/index.ts @@ -0,0 +1,14 @@ +export function delay(time: number) { + return new Promise(function(resolve) { + setTimeout(resolve, time) + }); +}; + +import userBlast from "./userBlast"; + +export default { + 'userBlast': userBlast, + 'userExecute': userBlast, + 'grenadeExplosion': userBlast, + 'tntExplosion': userBlast +} diff --git a/src/web/alerts/www/src/alerts/userBlast.ts b/src/web/alerts/www/src/alerts/userBlast.ts new file mode 100644 index 0000000..18f159e --- /dev/null +++ b/src/web/alerts/www/src/alerts/userBlast.ts @@ -0,0 +1,18 @@ +import { userBlastAlert } from "web/alerts/types"; +import { delay } from "./index"; + +export default async function execute(alert: userBlastAlert) { + const parentDiv = document.createElement('div'); + const textElement = document.createElement('span'); + textElement.textContent = `${alert.user} just blasted ${alert.target} for 60 seconds! Rip bozo!`; + parentDiv.appendChild(textElement); + Object.assign(textElement.style, { + position: 'fixed', + top: '20px', + left: '20px', + zIndex: 1000 + }); + document.querySelector("#app").appendChild(parentDiv); + await delay(10000); + parentDiv.remove(); +}; diff --git a/src/web/alerts/www/src/main.ts b/src/web/alerts/www/src/main.ts new file mode 100644 index 0000000..0162f17 --- /dev/null +++ b/src/web/alerts/www/src/main.ts @@ -0,0 +1,25 @@ +import { serverInstruction } from "web/serverTypes"; +import { alertEventData } from "web/alerts/types"; +import alertManager from "./alerts/alertManager"; + +const socket = new WebSocket(`ws://${location.host}`); + +socket.onopen = () => { + const instruction: serverInstruction = { + type: 'subscribe', + target: 'alerts' + }; + socket.send(JSON.stringify(instruction)); +}; + +socket.onmessage = event => { + const data: alertEventData = JSON.parse(event.data); + switch (data.function) { + case "playAlert": + alertManager.queueAlert(data.alert); + break; + case 'serverNotification': + console.log(data.message); + break; + }; +}; diff --git a/src/web/chatWidget/websockettypes.ts b/src/web/chatWidget/websockettypes.ts index fd4aee7..a6eee0d 100644 --- a/src/web/chatWidget/websockettypes.ts +++ b/src/web/chatWidget/websockettypes.ts @@ -1,3 +1,5 @@ +import type { serverNotificationEvent } from "web/serverTypes"; + export type createMessageEvent = { function: 'createMessage'; messageParts: EventSubChatMessagePart[]; @@ -12,17 +14,14 @@ export type deleteMessageEvent = { function: 'deleteMessage'; messageId: string; }; + export type userBanEvent = { function: 'userBan'; chatterId: string; }; -export type serverNotificationEvent = { - function: 'serverNotification'; - message: string; -}; export type twitchEventData = - createMessageEvent + | createMessageEvent | deleteMessageEvent | userBanEvent | serverNotificationEvent; @@ -32,44 +31,44 @@ export type twitchEventData = export interface EventSubChatMessageTextPart { type: 'text'; text: string; -} +}; export interface EventSubChatMessageCheermote { prefix: string; bits: number; tier: number; -} +}; export interface EventSubChatMessageCheermotePart { type: 'cheermote'; text: string; cheermote: EventSubChatMessageCheermote; -} +}; export interface EventSubChatMessageEmote { id: string; emote_set_id: string; owner_id: string; format: string[]; -} +}; export interface EventSubChatMessageEmotePart { type: 'emote'; text: string; emote: EventSubChatMessageEmote; -} +}; export interface EventSubChatMessageMention { user_id: string; user_name: string; user_login: string; -} +}; export interface EventSubChatMessageMentionPart { type: 'mention'; text: string; mention: EventSubChatMessageMention; -} +}; export type EventSubChatMessagePart = | EventSubChatMessageTextPart diff --git a/src/web/chatWidget/widgetServerFunctions.ts b/src/web/chatWidget/widgetServerFunctions.ts index 8990286..088674f 100644 --- a/src/web/chatWidget/widgetServerFunctions.ts +++ b/src/web/chatWidget/widgetServerFunctions.ts @@ -84,5 +84,5 @@ import server from "web"; import type { twitchEventData } from "web/chatWidget/websockettypes"; export async function sendTwitchChatEvent(event: twitchEventData) { - server.publish('twitchchat', JSON.stringify(event)); + server.publish('twitch.chat', JSON.stringify(event)); }; diff --git a/src/web/chatWidget/www/src/main.ts b/src/web/chatWidget/www/src/main.ts index 1a44fc7..123cf1a 100644 --- a/src/web/chatWidget/www/src/main.ts +++ b/src/web/chatWidget/www/src/main.ts @@ -3,14 +3,16 @@ import '@fontsource/jersey-15'; import { type twitchEventData } from "web/chatWidget/websockettypes"; import { parseMessage } from './createMessage'; +import { serverInstruction } from 'web/serverTypes'; const socket = new WebSocket(`ws://${location.host}`); socket.onopen = () => { - socket.send(JSON.stringify({ + const instruction: serverInstruction = { type: 'subscribe', - target: 'twitchchat' - })); + target: 'twitch.chat' + }; + socket.send(JSON.stringify(instruction)); }; socket.onmessage = event => { diff --git a/src/web/index.ts b/src/web/index.ts index 296e33f..4909659 100644 --- a/src/web/index.ts +++ b/src/web/index.ts @@ -1,7 +1,8 @@ import logger from "lib/logger"; -import { getBadges, getExternalEmotes } from "web/chatWidget/widgetServerFunctions"; import chatWidget from "web/chatWidget/www/index.html"; -import { sendTwitchChatEvent } from "web/chatWidget/widgetServerFunctions"; +import { getBadges, getExternalEmotes } from "web/chatWidget/widgetServerFunctions"; +import alerts from "web/alerts/www/index.html"; +import type { serverInstruction, serverNotificationEvent } from "web/serverTypes"; const port = Number(process.env.WEB_PORT); if (isNaN(port)) { logger.enverr("WEB_PORT"); process.exit(1); }; @@ -15,26 +16,23 @@ export default Bun.serve({ routes: { "/chat": chatWidget, "/chat/getBadges": getBadges, - "/chat/getEmotes": getExternalEmotes + "/chat/getEmotes": getExternalEmotes, + + "/alerts": alerts }, websocket: { - open(_ws) { - sendTwitchChatEvent({ - function: 'serverNotification', - message: 'Sucessfully opened websocket connection' - }); - }, message(ws, omessage) { - const message = JSON.parse(omessage.toString()); + const message = JSON.parse(omessage.toString()) as serverInstruction; if (!message.type) return; switch (message.type) { case 'subscribe': if (!message.target) return; + const target = message.target.toLowerCase(); ws.subscribe(message.target); - sendTwitchChatEvent({ + ws.send(JSON.stringify({ function: 'serverNotification', - message: `Successfully subscribed to all ${message.target} events` - }); + message: `Successfully subscribed to ${target} events` + } as serverNotificationEvent)); // Both alerts and chatwidget eventsub subscriptions have the notification field break; }; }, diff --git a/src/web/serverTypes.ts b/src/web/serverTypes.ts new file mode 100644 index 0000000..1623ab9 --- /dev/null +++ b/src/web/serverTypes.ts @@ -0,0 +1,13 @@ +type subscribe = { + type: 'subscribe', + target: string; +}; + +export type serverInstruction = + | subscribe; + +// This event is found on all listeners, so it should be placed here +export type serverNotificationEvent = { + function: 'serverNotification'; + message: string; +}; diff --git a/tsconfig.bot.json b/tsconfig.bot.json index e5ecfc5..8bcc265 100644 --- a/tsconfig.bot.json +++ b/tsconfig.bot.json @@ -19,5 +19,5 @@ "noPropertyAccessFromIndexSignature": false }, "include": ["src/**/*"], - "exclude": ["src/web/chatWidget/www/**/*"] + "exclude": ["src/web/chatWidget/www/**/*", "src/web/alerts/www/**/*"] } diff --git a/tsconfig.json b/tsconfig.json index 9dc875d..a43e69e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ }, "references": [ { "path": "./tsconfig.bot.json" }, - { "path": "./tsconfig.chatwidget.json" } + { "path": "./tsconfig.web.json" } ] } diff --git a/tsconfig.chatwidget.json b/tsconfig.web.json similarity index 52% rename from tsconfig.chatwidget.json rename to tsconfig.web.json index 8af98de..5dd8ba9 100644 --- a/tsconfig.chatwidget.json +++ b/tsconfig.web.json @@ -3,8 +3,7 @@ "compilerOptions": { "target": "ESNext", "module": "ESNext", - "lib": ["DOM", "ES2020"], - "types": [] + "lib": ["DOM", "ES2020"] }, - "include": ["src/web/chatWidget/www/**/*"] + "include": ["src/web/chatWidget/www/**/*", "src/web/alerts/www/**/*"] }