Add read-only chatroom

This commit is contained in:
2026-02-01 18:26:52 -07:00
parent f3052c5ba5
commit 6fbc3da29b
2 changed files with 181 additions and 0 deletions

View File

@@ -0,0 +1,174 @@
'use client'
import { BackButton } from '@/app/components/BackButton'
import { DiscordButton } from '@/app/components/DiscordButton'
import { GetIconForUser } from '@/util/bd'
import axios from 'axios'
import { useEffect, useRef, useState } from 'react'
interface WSMessage {
success: boolean
message: string | null
for: string
data: any
}
type BirdColor = [number, number, number]
interface Message {
username: string
userId: number
content: string
id: number
icon: number
overlay: number
birdColor: BirdColor
overlayColor: BirdColor
customIcon: string | null
}
interface CustomIconEntry {
data: string
id: string
}
export default function BerryDashChatroom () {
const [connected, setConnected] = useState<boolean>(false)
const [messages, setMessages] = useState<Message[]>([])
const [customIconCache, setCustomIconCache] = useState<CustomIconEntry[]>([])
const [ws, setWs] = useState<WebSocket | null>(null)
const messagesDiv = useRef<HTMLDivElement>(null)
useEffect(() => {
document.title = 'Lncvrt Games - Chatroom'
const socket = new WebSocket('wss://games.lncvrt.xyz/api/ws')
socket.onopen = () => {
setConnected(true)
socket.send(
JSON.stringify({
type: 'info_request',
kind: 'chatroom_messages',
timestamp: Date.now()
})
)
}
socket.onmessage = event => {
const message = JSON.parse(event.data) as WSMessage
if (message.for == 'info_request:chatroom_messages') {
const messages = message.data as Message[]
setMessages(messages)
} else if (message.for == 'upload:chatroom_message') {
const msg = message.data as Message
setMessages(prev => [...prev.slice(1), msg])
}
}
socket.onclose = () => {
setConnected(false)
setMessages([])
}
setWs(socket)
return () => {
socket.close()
}
}, [])
useEffect(() => {
if (messagesDiv.current) {
messagesDiv.current.scrollTop = messagesDiv.current.scrollHeight
}
const all = customIconCache.map(icon => icon.id)
const allFromMessages = Array.from(
new Set(
messages
.filter(icon => icon.customIcon != null)
.map(icon => icon.customIcon as string)
)
)
const notInAllIds = allFromMessages
.filter(id => !all.includes(id))
.map(id => `"${id}"`)
.join(',')
;(async () => {
const result = await axios.get(
`https://games.lncvrt.xyz/api/berrydash/icon-marketplace/icon?ids=[${notInAllIds}]`
)
if (result.data.success) {
const add: CustomIconEntry[] = []
for (const item of result.data.data) {
add.push({
data: item.data,
id: item.id
})
}
setCustomIconCache(prev => [
...prev,
...result.data.data.map((item: CustomIconEntry) => ({
data: item.data,
id: item.id
}))
])
}
})()
}, [messages])
return (
<div className='box'>
<BackButton href='/game/berry-dash' />
<DiscordButton />
<p className='px-8 mb-4 -mt-2 text-center text-2xl'>
Berry Dash Chatroom
</p>
{connected && (
<>
<div
className='sub-box -m-4 mt-0 flex flex-col gap-2 max-h-[calc(100vh-204px)] overflow-y-auto'
ref={messagesDiv}
>
{messages.map(item => {
return (
<div key={item.id} className='sub-box2 flex flex-row gap-1'>
<img
src={
!item.customIcon
? `https://games-r2.lncvrt.xyz/game-assets/berrydash/icons/bird_${
item.icon == 1
? GetIconForUser(item.userId)
: item.icon
}.png`
: customIconCache.find(i => i.id === item.customIcon)
? 'data:image/png;base64,' +
customIconCache.find(i => i.id === item.customIcon)
?.data
: 'https://games-r2.lncvrt.xyz/game-assets/berrydash/other/loading.png'
}
className='pointer-events-none'
width={48}
height={48}
></img>
<div>
<p>{item.username}</p>
<p className='max-w-240 truncate select-text'>
{atob(item.content)}
</p>
</div>
</div>
)
})}
</div>
</>
)}
{!connected && (
<p className='text-center text-red-500'>
Not connected to the chatroom
</p>
)}
</div>
)
}