UNPKG

yunzai-micro-plugin

Version:

Yunzai开发管理面板

143 lines (140 loc) 5.27 kB
import iconvLite from 'iconv-lite'; import os from 'os'; import { randomUUID } from 'crypto'; import { spawn } from 'child_process'; import { resolve } from 'path'; import { Logger } from '../../../../adapter/index.js'; import { TermCfg } from './config.js'; const logger = await Logger(); class TerminalWs { clients; execPath; stream; constructor(ws) { this.clients = new Map(); this.init(ws); this.execPath = process.cwd(); } init(ws) { const ClientId = randomUUID(); if (TermCfg.ssh.isOpen) { ws.send(JSON.stringify({ params: ClientId, action: 'meta', type: 'ssh' })); this.ssh(ws, TermCfg.ssh); } else { ws.send(JSON.stringify({ params: ClientId, action: 'meta', type: 'exec' })); } this.clients.set(ClientId, ws); ws.on('message', async (message) => { logger.info(logger.blue(`[micro-terminal]收到消息:${message}`)); const data = JSON.parse(message); if (TermCfg.ssh.isOpen || data.params.type == 'ssh') { this.stream?.write(data.params.cmd + '\n'); if (data.params.cmd == 'logout') { TermCfg.ssh.isOpen = false; } } else { await this.exec(data.params.cmd, ws, data.params.path ? data.params.path : null); } }); ws.on('close', () => { logger.info(logger.blue(`[micro-terminal]已断开连接`)); this.clients.delete(ClientId); }); ws.on('error', (err) => { logger.error('[micro-terminal]连接错误:', err); this.clients.delete(ClientId); }); } async exec(cmd, ws, path) { const sendStdout = (data) => { ws.send(JSON.stringify({ type: 'exec', action: 'stdout', params: data })); }; if (cmd.trim() !== 'exit') { try { const command = cmd.trim(); const child = spawn(command, command.split(" ").slice(1), { cwd: this.execPath, shell: os.platform() === 'win32' ? 'powershell.exe' : true }); child.stdout.on('data', (data) => { sendStdout(iconvLite.decode(data, 'gbk')); }); child.stderr.on('data', (data) => { sendStdout(iconvLite.decode(data, 'gbk')); }); child.on('error', (err) => { sendStdout(err.message); }); child.on('close', (code) => { sendStdout(`Command finished with code ${code}`); }); if (cmd.startsWith('cd')) { if (path) { this.execPath = path; sendStdout('UpdateCwd:' + this.execPath); } else { this.execPath = resolve(this.execPath, (cmd.replace(/cd/, '')).trim()); sendStdout('UpdateCwd:' + this.execPath); } } } catch (err) { if (err instanceof Error) { sendStdout(`Error executing command: ${err.message}\nStderr: ${err.stderr || ''}`); } else { sendStdout(`Unexpected error: ${err.toString()}`); } } } else { ws.close(); } } async ssh(ws, account) { let Client; try { Client = (await import('ssh2')).Client; } catch (err) { logger.error('[Micro]未成功安装ssh2,无法使用ssh连接功能:' + err.message); return; } const sendStdout = (data) => { ws.send(JSON.stringify({ type: 'ssh', action: 'stdout', params: data })); }; const conn = new Client(); conn.on('ready', () => { console.log('Client :: ready'); conn.shell((err, stream) => { this.stream = stream; if (err) logger.error(err); stream.on('close', function () { console.log('Stream :: close'); conn.end(); }).on('data', function (data) { console.log('STDOUT: ' + data); sendStdout(data.toString()); }).stderr.on('data', function (data) { console.error('STDERR: ' + data); sendStdout(data.toString()); }); }); }).connect({ host: account.host, port: account.port || 22, username: account.username, password: account.password }); conn.on('error', (err) => { TermCfg.ssh.isOpen = false; console.error('SSH Connection :: error:', err.message); ws.send(JSON.stringify({ type: 'exec', action: 'stdout', params: '连接失败,请重连或检查主机密码是否正确:' + err.message })); }); } } export { TerminalWs as default };