feat(chat): add widgetbot for discord-based chat
authorxangelo <me@xangelo.ca>
Tue, 3 Dec 2024 15:28:50 +0000 (10:28 -0500)
committerxangelo <me@xangelo.ca>
Tue, 3 Dec 2024 15:28:50 +0000 (10:28 -0500)
Instead of using a custom chat system, we're now using Widgetbot so
that the chat integrates with the Discord server.

16 files changed:
public/assets/css/chat.css [deleted file]
public/index.html
src/server/api.ts
src/server/chat-commands.ts [deleted file]
src/server/chat-commands/base.ts [deleted file]
src/server/chat-commands/give-permission.ts [deleted file]
src/server/chat-commands/index.ts [deleted file]
src/server/chat-commands/refresh-cities.ts [deleted file]
src/server/chat-commands/refresh-droptables.ts [deleted file]
src/server/chat-commands/refresh-monsters.ts [deleted file]
src/server/chat-commands/refresh-shops.ts [deleted file]
src/server/chat-commands/say.ts [deleted file]
src/server/chat-commands/set-level.ts [deleted file]
src/server/routes/chat.ts [deleted file]
src/server/routes/index.ts
src/server/views/chat.ts [deleted file]

diff --git a/public/assets/css/chat.css b/public/assets/css/chat.css
deleted file mode 100644 (file)
index e581446..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-#chat {
-    border: solid 1px #6d251c;
-}
-
-#chat-messages {
-    max-height: 250px;
-    overflow: auto;
-}
-
-.chat-message {
-    line-height: 1.2rem;
-    padding: 0.2rem 0.3rem 0.3rem;
-}
-
-.chat-message:nth-child(even) {
-    background: linear-gradient(270deg, rgba(0, 0, 0, 0) 0, rgba(196, 177, 149, 0.8) 100%);
-}
-
-.chat-message .from {
-    font-weight: bold;
-}
-
-.chat-message .from::after {
-    content: ':';
-}
-
-#chat-form {
-    display: flex;
-}
-
-#chat-form input {
-    flex-grow: 8;
-    padding: 0.3rem;
-    outline: none;
-    border-width: 1px 0 0;
-    background: transparent;
-}
-
-#chat-form input:focus {
-    outline: none;
-}
-
-#chat-form button {
-    border-right-width: 0px;
-    border-bottom-width: 0px;
-    font-weight: bold;
-}
-
-#chat-form button:active {
-    top: 0;
-}
\ No newline at end of file
index 6db73b59ac24b13da9c4af7907583f55bf781b20..e835d5327ffb29ab54102121d41a6d9dc03bcff4 100644 (file)
     </div>
 
     <section id="chat">
-      <div id="chat-messages" hx-trigger="load delay:1s" hx-get="/chat/history" hx-swap="afterbegin"></div>
-      <form id="chat-form" hx-post="/chat">
-        <input type="text" id="message" name="message" autocomplete="off"><button type="submit">Send</button>
-      </form>
+      <widgetbot server="1116668527684894772" channel="1116668528402104362" width="100%" height="600"></widgetbot>
+      <script src="https://cdn.jsdelivr.net/npm/@widgetbot/html-embed"></script>
     </section>
 
     <section id="game-footer">
index aadaeec0dd2a29fa05db7ea972e4b98662dab836..3d22e9f848e69e8319c4941d0548c7ede84f0d29 100644 (file)
@@ -15,9 +15,7 @@ import { random, each } from 'lodash';
 import {broadcastMessage} from '@shared/message';
 import { Player } from '@shared/player';
 import {createFight, getMonsterList, getMonsterLocation, getRandomMonster, loadMonster, loadMonsterFromFight} from './monster';
-import { addInventoryItem } from './inventory';
 import {FightTrigger, Monster} from '@shared/monsters';
-import {getShopEquipment } from './shopEquipment';
 import { getAllPaths, getAllServices, getCityDetails, getService, getTravelPlan, getDungeon } from './map';
 import { signup, login, authEndpoint } from './auth';
 import {db} from './lib/db';
@@ -35,7 +33,6 @@ import { renderSkills } from './views/skills';
 import { renderMonsterSelector, renderOnlyMonsterSelector } from './views/monster-selector';
 import { renderFight, renderFightPreRound, renderRoundDetails } from './views/fight';
 import { renderTravel, travelButton } from './views/travel';
-import { renderChatMessage } from './views/chat';
 
 // TEMP!
 import { completeDungeonFight, getActiveDungeon, getRoomVists, loadRoom } from './dungeon';
@@ -100,8 +97,6 @@ async function bootstrapSocket(socket: Socket, player: Player) {
   });
 
   socket.emit('authToken', player.id);
-
-  socket.emit('chat', renderChatMessage(broadcastMessage('server', `${player.username} just logged in`)));
 }
 
 function uniqueConnectedUsers(): Set<string> {
diff --git a/src/server/chat-commands.ts b/src/server/chat-commands.ts
deleted file mode 100644 (file)
index 6d428a0..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Server, Socket } from 'socket.io';
-import { Player } from '@shared/player';
-import { broadcastMessage } from '@shared/message';
-import { renderChatMessage } from './views/chat';
-import { Commands } from './chat-commands/';
-import { logger } from './lib/logger';
-
-
-export async function handleChatCommands(msg: string, player: Player, io: Server, sender: Socket): Promise<void> {
-  const rawCommand = msg.split('/server ')[1];
-
-  logger.debug(`${player.username} running command: [${rawCommand}]`);
-  let matched = false;
-  Commands.forEach(async command => {
-    if(command.regex.test(rawCommand)) {
-      matched = true;
-      console.log(`${player.username} running command: [${rawCommand}]`);
-      await command.handler(rawCommand, sender, player, io);
-    }
-  });
-
-  if(!matched) {
-    const message = broadcastMessage('server', `Invalid command: [${rawCommand}]`);
-    sender.emit('chat', renderChatMessage(message));
-  }
-}
diff --git a/src/server/chat-commands/base.ts b/src/server/chat-commands/base.ts
deleted file mode 100644 (file)
index 8241067..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-import type { Player } from '../../shared/player';
-import type { Server, Socket } from 'socket.io';
-
-type ChatCommandHandler = (command: string, sender: Socket, player: Player, io: Server) => Promise<void>;
-
-export class ChatCommand {
-  constructor(
-    public name: string,
-    public regex: RegExp,
-    public handler: ChatCommandHandler
-  ) {
-
-  }
-}
diff --git a/src/server/chat-commands/give-permission.ts b/src/server/chat-commands/give-permission.ts
deleted file mode 100644 (file)
index 2586f8c..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Socket } from 'socket.io';
-import { Permission, PermissionGuard, Player } from '../../shared/player';
-import { ChatCommand } from './base';
-import { givePlayerPermission } from '../admin';
-import { findPlayerByUsername } from '../player';
-import { broadcastMessage, Message } from '../../shared/message';
-import { renderChatMessage } from './../views/chat';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('admin')) {
-    const pieces = rawCommand.split(' ');
-    const username = pieces[1];
-    const permission = pieces[2];
-
-    if(PermissionGuard(permission)) {
-      const recipient = await findPlayerByUsername(username);
-      if(player) {
-        await givePlayerPermission(recipient.id, permission as Permission);
-
-        const message: Message = broadcastMessage('server', `Granted ${permission} to ${recipient.username}`);
-        sender.emit('chat', renderChatMessage(message));
-      }
-      else {
-        const message: Message = broadcastMessage('server', `Cant find user [${username}]`);
-        sender.emit('chat', renderChatMessage(message));
-      }
-    }
-  }
-}
-
-export const setPermission = new ChatCommand('give-permission', new RegExp(/^give-permission (.*)+/), handler);
diff --git a/src/server/chat-commands/index.ts b/src/server/chat-commands/index.ts
deleted file mode 100644 (file)
index d8f638b..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import { ChatCommand } from "./base";
-import { refreshDropTables } from './refresh-droptables';
-import { refreshMonsters } from "./refresh-monsters";
-import { refreshCities } from './refresh-cities';
-import { refreshShops } from './refresh-shops';
-import { setLevel } from './set-level';
-import { say } from './say';
-import { setPermission } from './give-permission';
-
-export const Commands = new Set<ChatCommand>();
-
-Commands.add(refreshMonsters);
-Commands.add(refreshCities);
-Commands.add(refreshShops);
-Commands.add(refreshDropTables);
-Commands.add(setLevel);
-Commands.add(say);
-Commands.add(setPermission);
diff --git a/src/server/chat-commands/refresh-cities.ts b/src/server/chat-commands/refresh-cities.ts
deleted file mode 100644 (file)
index 25c94cb..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ChatCommand } from './base';
-import { Socket } from 'socket.io';
-import { createAllCitiesAndLocations } from '../../../seeds/cities';
-import { broadcastMessage } from '../../shared/message';
-import { renderChatMessage } from './../views/chat';
-import { Player } from '../../shared/player';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('admin')) {
-    await createAllCitiesAndLocations();
-    const message = broadcastMessage('server', 'Cities, Locations, and Paths refreshed!');
-    sender.emit('chat', renderChatMessage(message));
-  }
-}
-
-export const refreshCities = new ChatCommand('refresh-cities', new RegExp(/^refresh-cities/), handler);
diff --git a/src/server/chat-commands/refresh-droptables.ts b/src/server/chat-commands/refresh-droptables.ts
deleted file mode 100644 (file)
index 9b10bc2..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ChatCommand } from './base';
-import { Socket } from 'socket.io';
-import { createDropTables } from '../../../seeds/drop-tables';
-import { broadcastMessage } from '../../shared/message';
-import { renderChatMessage } from '../views/chat';
-import { Player } from '../../shared/player';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('admin')) {
-    await createDropTables();
-    const message = broadcastMessage('server', 'Drop tables refreshed');
-    sender.emit('chat', renderChatMessage(message));
-  }
-}
-
-export const refreshDropTables = new ChatCommand('refresh-droptables', new RegExp(/^refresh-droptables/), handler);
diff --git a/src/server/chat-commands/refresh-monsters.ts b/src/server/chat-commands/refresh-monsters.ts
deleted file mode 100644 (file)
index e0c0624..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ChatCommand } from './base';
-import { Socket } from 'socket.io';
-import { createMonsters } from '../../../seeds/monsters';
-import { broadcastMessage } from '../../shared/message';
-import { renderChatMessage } from './../views/chat';
-import { Player } from '../../shared/player';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('admin')) {
-    await createMonsters();
-    const message = broadcastMessage('server', 'Monsters refreshed!');
-    sender.emit('chat', renderChatMessage(message));
-  }
-}
-
-export const refreshMonsters = new ChatCommand('refresh-monsters', new RegExp(/^refresh-monsters$/), handler);
diff --git a/src/server/chat-commands/refresh-shops.ts b/src/server/chat-commands/refresh-shops.ts
deleted file mode 100644 (file)
index 2f90c3b..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-import { ChatCommand } from './base';
-import { Socket } from 'socket.io';
-import { createShopItems, createShopEquipment } from '../../../seeds/shop_items';
-import { broadcastMessage } from '../../shared/message';
-import { renderChatMessage } from './../views/chat';
-import { Player } from '../../shared/player';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('admin')) {
-    await createShopItems();
-    await createShopEquipment();
-    const message = broadcastMessage('server', 'Shop items refreshed!');
-    sender.emit('chat', renderChatMessage(message));
-
-  }
-}
-
-export const refreshShops = new ChatCommand('refresh-shops', new RegExp(/^refresh-shops/), handler);
diff --git a/src/server/chat-commands/say.ts b/src/server/chat-commands/say.ts
deleted file mode 100644 (file)
index 1c1104e..0000000
+++ /dev/null
@@ -1,14 +0,0 @@
-import { ChatCommand } from './base';
-import { Socket } from 'socket.io';
-import { broadcastMessage, Message } from '../../shared/message';
-import { renderChatMessage } from './../views/chat';
-import { Player } from '../../shared/player';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('moderator') || player.permissions.includes('admin')) {
-    let message: Message = broadcastMessage('server', rawCommand.split('say ')[1].trim());
-    sender.emit('chat', renderChatMessage(message));
-  }
-}
-
-export const say = new ChatCommand('say', new RegExp(/^say (.*)+/), handler);
diff --git a/src/server/chat-commands/set-level.ts b/src/server/chat-commands/set-level.ts
deleted file mode 100644 (file)
index 640a194..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-import { ChatCommand } from './base';
-import { Socket } from 'socket.io';
-import { broadcastMessage, Message } from '../../shared/message';
-import { renderChatMessage } from './../views/chat';
-import { updatePlayer } from '../player';
-import { maxHp, maxVigor, Player } from '../../shared/player';
-
-async function handler(rawCommand: string, sender: Socket, player: Player) {
-  if(player.permissions.includes('admin') || player.permissions.includes('tester')) {
-    let message: Message;
-    // command in set-level username level
-    const pieces = rawCommand.split(' ');
-    if(pieces.length !== 2) {
-      message = broadcastMessage('server', 'format: /set-level level');
-    }
-    else {
-      const level = parseInt(pieces.pop() || '0');
-      if(level < 1) {
-        message = broadcastMessage('server', 'format: /set-level [level >= 1]');
-      }
-      else {
-        message = broadcastMessage('server', `Set to level ${level}. Please reload.`);
-
-        player.level = level;
-        player.strength = 4;
-        player.constitution = 4;
-        player.dexterity = 4;
-        player.intelligence = 4;
-        player.hp = maxHp(player.constitution, player.level);
-        player.vigor = maxVigor(player.constitution, player.level);
-        player.stat_points = level-1;
-
-        await updatePlayer(player);
-      }
-    }
-
-    sender.emit('chat', renderChatMessage(message));
-
-  }
-}
-
-export const setLevel = new ChatCommand('set-level', new RegExp(/^set-level \d+$/), handler);
diff --git a/src/server/routes/chat.ts b/src/server/routes/chat.ts
deleted file mode 100644 (file)
index ff11e89..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-import { Request, Response, Router } from 'express';
-import { broadcastMessage, Message } from '../../shared/message';
-import { authEndpoint } from '../auth';
-import { renderChatMessage } from '../views/chat';
-import { handleChatCommands } from '../chat-commands';
-import xss from 'xss';
-import { logger } from '../lib/logger';
-export const chatRouter = Router();
-
-const chatHistory: Message[] = [];
-
-chatRouter.get('/chat/history', authEndpoint, (req: Request, res: Response) => {
-  let html = chatHistory.map(renderChatMessage);
-
-  res.send(html.join("\n"));
-});
-
-chatRouter.post('/chat', authEndpoint, async (req: Request, res: Response) => {
-  const msg = req.body.message.trim();
-
-  if(!msg || !msg.length) {
-    res.sendStatus(204);
-    return;
-  }
-
-  logger.debug(`${req.player.username} sending message: [${msg}] (${req.player.permissions.join(', ')})`);
-
-  if(msg.startsWith('/server') && req.player.permissions.includes('admin')) {
-    const sender = req.rl.io.sockets.sockets.get(req.rl.cache.get(`socket:${req.player.id}`));
-    try {
-      await handleChatCommands(msg, req.player, req.rl.io, sender);
-    }
-    catch(e) {
-      sender.emit('chat', renderChatMessage(broadcastMessage('server', e.message)));
-    }
-  }
-  else if(msg === '/online') {
-    const uniqueConnectedUsers = new Set<string>();
-
-    req.rl.io.sockets.sockets.forEach((socket) => {
-      uniqueConnectedUsers.add(req.rl.cache.get(`socket-lookup:${socket.id}`).username);
-    });
-
-    const users = Array.from(uniqueConnectedUsers.values());
-    // send to specific user
-    const message = broadcastMessage('server', `Online Players: [${users.join(", ")}]`);
-    req.rl.io.to(req.rl.cache.get(`socket:${req.player.id}`)).emit('chat', renderChatMessage(message));
-    res.sendStatus(204);
-  }
-  else {
-    const message = broadcastMessage(req.player.username, xss(msg, {
-      whiteList: {}
-    }));
-    chatHistory.push(message);
-    chatHistory.slice(-10);
-    req.rl.io.emit('chat', renderChatMessage(message));
-  }
-
-  res.sendStatus(204);
-});
index 07fe33dc780be395c372a23cea75f567a9775392..4ee83446cdacd7e2f5b48940de87ff93e9fa84c5 100644 (file)
@@ -1,4 +1,3 @@
-export { chatRouter } from './chat';
 export { inventoryRouter } from './inventory';
 export { profileRouter } from './profile';
 export { travelRouter } from './travel';
diff --git a/src/server/views/chat.ts b/src/server/views/chat.ts
deleted file mode 100644 (file)
index 69cc27d..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-import {Message} from "@shared/message";
-
-export function renderChatMessage(msg: Message): string {
-    return `<div class="chat-message" title="${new Date(parseInt(msg.sentAt))}" id="${msg.id}">
-    <span class="from">${msg.from}</span>
-    <span class="message">${msg.msg}</span>
-    </div>`;
-}