feat(minecraft): hook up prismarine-viewer

This commit is contained in:
Rin
2026-02-18 11:12:18 +08:00
committed by Neko Ayaka
parent b11bb1a7c8
commit a9dc7d52fe
4 changed files with 76 additions and 0 deletions
+1
View File
@@ -19,6 +19,7 @@
"es-toolkit": "^1.44.0",
"eventemitter3": "^5.0.4",
"minecraft-data": "^3.102.3",
"canvas": "^3.2.1",
"mineflayer": "^4.33.0",
"mineflayer-armor-manager": "^2.0.1",
"mineflayer-auto-eat": "^5.0.3",
@@ -0,0 +1,44 @@
import type { Bot } from 'mineflayer'
import { useLogger } from '../utils/logger'
interface MineflayerViewerOptions {
port: number
firstPerson?: boolean
}
export function setupMineflayerViewer(mineflayer: { bot: Bot }, options: MineflayerViewerOptions): void {
let isViewerStarted = false
mineflayer.bot.once('spawn', async () => {
if (isViewerStarted)
return
isViewerStarted = true
const logger = useLogger()
try {
const { mineflayer: mineflayerViewer } = await import('prismarine-viewer')
mineflayerViewer(mineflayer.bot, {
port: options.port,
firstPerson: options.firstPerson ?? true,
})
logger.log(`Mineflayer viewer running at http://localhost:${options.port}`)
}
catch (err) {
const e = err as NodeJS.ErrnoException
const message = typeof e.message === 'string' ? e.message : ''
const isCanvasMissing = e?.code === 'MODULE_NOT_FOUND' && message.includes('\'canvas\'')
const isCanvasBinaryMissing = e?.code === 'MODULE_NOT_FOUND' && message.includes('canvas.node')
if (isCanvasMissing || isCanvasBinaryMissing) {
logger.log('Mineflayer viewer disabled: node-canvas is not available')
return
}
logger.errorWithError('Failed to start mineflayer viewer', e as Error)
}
})
}
+28
View File
@@ -18,6 +18,24 @@ import { useLogger } from '../utils/logger'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
function createViewerHtml(targetUrl: string): string {
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mineflayer Viewer</title>
<style>
html, body { height: 100%; margin: 0; }
iframe { width: 100%; height: 100%; border: 0; }
</style>
</head>
<body>
<iframe src="${targetUrl}" allow="fullscreen" referrerpolicy="no-referrer"></iframe>
</body>
</html>`
}
interface ClientInfo {
ws: WebSocket
id: string
@@ -165,6 +183,16 @@ export class DebugServer {
return
}
if (req.method === 'GET' && (req.url === '/viewer' || req.url?.startsWith('/viewer?'))) {
const html = createViewerHtml('http://localhost:3007')
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8',
'Cache-Control': 'no-cache',
})
res.end(html)
return
}
// Serve static files from web directory
let filePath = req.url === '/' ? '/index.html' : req.url || '/index.html'
+3
View File
@@ -14,6 +14,7 @@ import { initBot } from './composables/bot'
import { config, initEnv } from './composables/config'
import { createNeuriAgent } from './composables/neuri'
import { DebugService } from './debug'
import { setupMineflayerViewer } from './debug/mineflayer-viewer'
import { wrapPlugin } from './libs/mineflayer'
import { initLogger, useLogger } from './utils/logger'
@@ -38,6 +39,8 @@ async function main() {
],
})
setupMineflayerViewer(bot, { port: 3007, firstPerson: true })
// Connect airi server
const airiClient = new Client({
name: config.airi.clientName,