diff --git a/services/minecraft/package.json b/services/minecraft/package.json index 00c599f23..32a5fc2e7 100644 --- a/services/minecraft/package.json +++ b/services/minecraft/package.json @@ -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", diff --git a/services/minecraft/src/debug/mineflayer-viewer.ts b/services/minecraft/src/debug/mineflayer-viewer.ts new file mode 100644 index 000000000..06af9f16e --- /dev/null +++ b/services/minecraft/src/debug/mineflayer-viewer.ts @@ -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) + } + }) +} diff --git a/services/minecraft/src/debug/server.ts b/services/minecraft/src/debug/server.ts index d22b4742a..e51e28bfe 100644 --- a/services/minecraft/src/debug/server.ts +++ b/services/minecraft/src/debug/server.ts @@ -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 ` + + + + + Mineflayer Viewer + + + + + +` +} + 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' diff --git a/services/minecraft/src/main.ts b/services/minecraft/src/main.ts index 6deac933b..143a548d1 100644 --- a/services/minecraft/src/main.ts +++ b/services/minecraft/src/main.ts @@ -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,