implement a basic alerts template

This commit is contained in:
2025-07-25 23:54:02 +01:00
parent a9c89cd616
commit 15c8abc2c3
15 changed files with 194 additions and 34 deletions

View File

@@ -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
});
};

37
src/web/alerts/types.ts Normal file
View File

@@ -0,0 +1,37 @@
import type { serverNotificationEvent } from "web/serverTypes";
type alertBase<name extends string> = {
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;

View File

@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>qwerinope's alert server</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>

View File

@@ -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();

View File

@@ -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
}

View File

@@ -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();
};

View File

@@ -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;
};
};

View File

@@ -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

View File

@@ -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));
};

View File

@@ -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 => {

View File

@@ -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;
};
},

13
src/web/serverTypes.ts Normal file
View File

@@ -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;
};

View File

@@ -19,5 +19,5 @@
"noPropertyAccessFromIndexSignature": false
},
"include": ["src/**/*"],
"exclude": ["src/web/chatWidget/www/**/*"]
"exclude": ["src/web/chatWidget/www/**/*", "src/web/alerts/www/**/*"]
}

View File

@@ -15,6 +15,6 @@
},
"references": [
{ "path": "./tsconfig.bot.json" },
{ "path": "./tsconfig.chatwidget.json" }
{ "path": "./tsconfig.web.json" }
]
}

View File

@@ -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/**/*"]
}