150 lines
5.9 KiB
TypeScript
150 lines
5.9 KiB
TypeScript
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
import { useSnapshot } from 'valtio'
|
|
import { formatMessage, isStringAllowed } from '../chatUtils'
|
|
import { getBuiltinCommandsList, tryHandleBuiltinCommand } from '../builtinCommands'
|
|
import { gameAdditionalState, hideCurrentModal, miscUiState } from '../globalState'
|
|
import { options } from '../optionsStorage'
|
|
import { viewerVersionState } from '../viewerConnector'
|
|
import Chat, { Message } from './Chat'
|
|
import { useIsModalActive } from './utilsApp'
|
|
import { hideNotification, notificationProxy, showNotification } from './NotificationProvider'
|
|
import { getServerIndex, updateLoadedServerData } from './serversStorage'
|
|
import { lastConnectOptions } from './AppStatusProvider'
|
|
import { showOptionsModal } from './SelectOption'
|
|
|
|
export default () => {
|
|
const [messages, setMessages] = useState([] as Message[])
|
|
const isChatActive = useIsModalActive('chat')
|
|
const lastMessageId = useRef(0)
|
|
const lastPingTime = useRef(0)
|
|
const usingTouch = useSnapshot(miscUiState).currentTouch
|
|
const { chatSelect, messagesLimit, chatOpacity, chatOpacityOpened, chatVanillaRestrictions, debugChatScroll, chatPingExtension } = useSnapshot(options)
|
|
const isUsingMicrosoftAuth = useMemo(() => !!lastConnectOptions.value?.authenticatedAccount, [])
|
|
const { forwardChat } = useSnapshot(viewerVersionState)
|
|
const { viewerConnection } = useSnapshot(gameAdditionalState)
|
|
|
|
useEffect(() => {
|
|
bot.addListener('message', (jsonMsg, position) => {
|
|
if (position === 'game_info') return // ignore action bar messages, they are handled by the TitleProvider
|
|
if (jsonMsg['unsigned']) {
|
|
jsonMsg = jsonMsg['unsigned']
|
|
}
|
|
const parts = formatMessage(jsonMsg)
|
|
const messageText = parts.map(part => part.text).join('')
|
|
|
|
// Handle ping response
|
|
if (messageText === 'Pong!' && lastPingTime.current > 0) {
|
|
const latency = Date.now() - lastPingTime.current
|
|
parts.push({ text: ` Latency: ${latency}ms`, color: '#00ff00' })
|
|
lastPingTime.current = 0
|
|
}
|
|
|
|
setMessages(m => {
|
|
lastMessageId.current++
|
|
const newMessage: Message = {
|
|
parts,
|
|
id: lastMessageId.current,
|
|
timestamp: Date.now()
|
|
}
|
|
|
|
return [...m, newMessage].slice(-messagesLimit)
|
|
})
|
|
})
|
|
}, [])
|
|
|
|
return <Chat
|
|
chatVanillaRestrictions={chatVanillaRestrictions}
|
|
debugChatScroll={debugChatScroll}
|
|
allowSelection={chatSelect}
|
|
usingTouch={!!usingTouch}
|
|
opacity={(isChatActive ? chatOpacityOpened : chatOpacity) / 100}
|
|
messages={messages}
|
|
opened={isChatActive}
|
|
placeholder={forwardChat || !viewerConnection ? undefined : 'Chat forwarding is not enabled in the plugin settings'}
|
|
currentPlayerName={chatPingExtension ? bot.username : undefined}
|
|
getPingComplete={async (value) => {
|
|
const players = Object.keys(bot.players)
|
|
return players.filter(name => (!value || name.toLowerCase().includes(value.toLowerCase())) && name !== bot.username).map(name => `@${name}`)
|
|
}}
|
|
sendMessage={async (message) => {
|
|
// Record ping command time
|
|
if (message === '/ping') {
|
|
lastPingTime.current = Date.now()
|
|
}
|
|
|
|
const builtinHandled = tryHandleBuiltinCommand(message)
|
|
if (getServerIndex() !== undefined && (message.startsWith('/login') || message.startsWith('/register')) && options.saveLoginPassword !== 'never') {
|
|
const savePassword = () => {
|
|
let hadPassword = false
|
|
updateLoadedServerData((server) => {
|
|
server.autoLogin ??= {}
|
|
const password = message.split(' ')[1]
|
|
hadPassword = !!server.autoLogin[bot.username]
|
|
server.autoLogin[bot.username] = password
|
|
return { ...server }
|
|
})
|
|
if (options.saveLoginPassword === 'always') {
|
|
const message = hadPassword ? 'Password updated in browser for auto-login' : 'Password saved in browser for auto-login'
|
|
showNotification(message, undefined, false, undefined)
|
|
} else {
|
|
hideNotification()
|
|
}
|
|
}
|
|
if (options.saveLoginPassword === 'prompt') {
|
|
showNotification('Click here to save your password in browser for auto-login', undefined, false, undefined, savePassword)
|
|
} else {
|
|
savePassword()
|
|
}
|
|
notificationProxy.id = 'auto-login'
|
|
const listener = () => {
|
|
hideNotification()
|
|
}
|
|
bot.on('kicked', listener)
|
|
setTimeout(() => {
|
|
bot.removeListener('kicked', listener)
|
|
}, 2000)
|
|
}
|
|
if (!builtinHandled) {
|
|
if (chatVanillaRestrictions && !miscUiState.flyingSquid) {
|
|
const validation = isStringAllowed(message)
|
|
if (!validation.valid) {
|
|
const choice = await showOptionsModal(`Can't send invalid characters to vanilla server (${validation.invalid?.join(', ')}). You can use them only in command blocks.`, [
|
|
'Remove Them & Send'
|
|
])
|
|
if (!choice) return
|
|
message = validation.clean!
|
|
}
|
|
}
|
|
|
|
if (message) {
|
|
bot.chat(message)
|
|
}
|
|
}
|
|
}}
|
|
onClose={() => {
|
|
hideCurrentModal()
|
|
}}
|
|
fetchCompletionItems={async (triggerKind, completeValue) => {
|
|
if ((triggerKind === 'explicit' || options.autoRequestCompletions)) {
|
|
let items = [] as string[]
|
|
try {
|
|
items = await bot.tabComplete(completeValue, true, true)
|
|
} catch (err) { }
|
|
if (typeof items[0] === 'object') {
|
|
// @ts-expect-error
|
|
if (items[0].match) items = items.map(i => i.match)
|
|
}
|
|
if (completeValue === '/') {
|
|
if (!items[0]?.startsWith('/')) {
|
|
// normalize
|
|
items = items.map(item => `/${item}`)
|
|
}
|
|
if (items.length) {
|
|
items = [...items, ...getBuiltinCommandsList()]
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
}}
|
|
/>
|
|
}
|