UNPKG

php-universal-mcp-server

Version:

Servidor MCP universal para desenvolvimento PHP e criação de sites em qualquer provedor de hospedagem

335 lines (283 loc) 9.46 kB
const BaseProvider = require('./BaseProvider'); const MockProvider = require('./MockProvider'); const { EventEmitter } = require('events'); /** * Provider para integração com servidores cPanel */ class CPanelProvider extends BaseProvider { constructor(config = {}) { super(config); // Configurações específicas para cPanel this.username = config.cpanelUsername || ''; this.password = config.cpanelPassword || ''; this.domain = config.cpanelDomain || ''; this.apiToken = config.cpanelApiToken || ''; this.port = config.cpanelPort || 2083; this.useSSL = config.cpanelUseSSL !== false; // Base URL da API cPanel this.apiBaseUrl = this.useSSL ? `https://${this.domain}:${this.port}/execute` : `http://${this.domain}:${this.port}/execute`; // Instancia um MockProvider para fallback this.mockProvider = new MockProvider(config); // Mantém registro de requisições/respostas para análise this.requestLog = []; // Streams ativos this.activeStreams = new Map(); } /** * Retorna o nome do provider */ getName() { return 'cpanel'; } /** * Executa um comando na API do cPanel */ execute(payload) { // Se não tiver credenciais, usa o mock provider if ((!this.username || !this.password) && !this.apiToken && this.config.fallbackEnabled) { return this.mockProvider.execute(payload); } try { // Loga a requisição this._logRequest('execute', payload); // Executa a requisição real para a API do cPanel const response = this._makeApiRequest(payload); // Loga a resposta this._logResponse('execute', response); return response; } catch (error) { // Se ocorrer um erro e fallback estiver habilitado, usa o mock if (this.config.fallbackEnabled) { console.warn(`Falling back to mock provider: ${error.message}`); return this.mockProvider.execute(payload); } // Caso contrário, propaga o erro throw error; } } /** * Cria um stream para operações assíncronas */ createStream(payload) { // Se não tiver credenciais, usa o mock provider if ((!this.username || !this.password) && !this.apiToken && this.config.fallbackEnabled) { return this.mockProvider.createStream(payload); } try { const streamId = crypto.randomUUID(); const emitter = new EventEmitter(); // Armazena o stream this.activeStreams.set(streamId, { id: streamId, emitter: emitter, payload: payload, startTime: new Date() }); // Loga a requisição this._logRequest('stream', payload); // Inicia a operação assíncrona this._startStreamOperation(streamId, payload); return { id: streamId, emitter: emitter }; } catch (error) { // Se ocorrer um erro e fallback estiver habilitado, usa o mock if (this.config.fallbackEnabled) { console.warn(`Falling back to mock provider for stream: ${error.message}`); return this.mockProvider.createStream(payload); } // Caso contrário, propaga o erro throw error; } } /** * Fecha um stream ativo */ closeStream(stream) { if (!stream || !stream.id) return; const streamId = stream.id; // Verifica se é um stream gerenciado por este provider if (this.activeStreams.has(streamId)) { const activeStream = this.activeStreams.get(streamId); // Emite evento de fechamento activeStream.emitter.emit('close', { id: streamId, reason: 'user_closed', timestamp: new Date().toISOString() }); // Remove o stream da lista de ativos this.activeStreams.delete(streamId); } else { // Tenta fechar no mock provider como fallback this.mockProvider.closeStream(stream); } } /** * Cancela a execução de um comando */ cancel(sessionId) { // Verifica se é um stream ativo if (this.activeStreams.has(sessionId)) { const stream = this.activeStreams.get(sessionId); // Emite evento de cancelamento stream.emitter.emit('cancel', { id: sessionId, reason: 'user_canceled', timestamp: new Date().toISOString() }); // Remove o stream da lista de ativos this.activeStreams.delete(sessionId); return { success: true, message: `Canceled session ${sessionId}`, timestamp: new Date().toISOString() }; } // Tenta cancelar no mock provider como fallback return this.mockProvider.cancel(sessionId); } /** * Verifica se o provider está disponível */ isAvailable() { // Verifica se tem credenciais e se a API está respondendo if ((!this.username || !this.password) && !this.apiToken) return false; try { // Tenta fazer uma chamada simples para verificar disponibilidade const testResponse = this._makeApiRequest({ action: 'ping' }); return testResponse.success === true; } catch (error) { return false; } } /** * Mapeia ações do MCP para módulos/funções do cPanel * @private */ _mapActionToCPanelModule(action) { const actionMap = { 'createSite': { module: 'SubDomain', function: 'addsubdomain' }, 'uploadFile': { module: 'Fileman', function: 'upload_files' }, 'getDomainInfo': { module: 'DomainInfo', function: 'domains_data' }, 'deployFiles': { module: 'Fileman', function: 'upload_files' } // Adicionar mais mapeamentos conforme necessário }; return actionMap[action] || { module: 'API', function: 'exec' }; } /** * Faz uma requisição para a API do cPanel * @private */ _makeApiRequest(payload) { // Implementação real da chamada de API do cPanel usando fetch/axios/etc // Esta é uma implementação simulada para exemplo // Mapeia a ação para módulo/função do cPanel const { module, function: func } = this._mapActionToCPanelModule(payload.action); // Simula uma chamada de API if (Math.random() > 0.9) { // Simula falha ocasional para testar fallback throw new Error('cPanel API request failed'); } // Simula resposta de API return { success: true, requestId: `cpanel_req_${Math.floor(Math.random() * 1000000)}`, timestamp: new Date().toISOString(), data: { // Dados específicos com base no payload module: module, function: func, message: 'cPanel API request successful', action: payload.action, result: {} } }; } /** * Inicia uma operação de stream com a API do cPanel * @private */ _startStreamOperation(streamId, payload) { const stream = this.activeStreams.get(streamId); if (!stream) return; // Emite evento de início stream.emitter.emit('start', { id: streamId, timestamp: new Date().toISOString() }); // Implementação real de polling para operação de longa duração // Esta é uma implementação simulada para exemplo // Simula eventos em intervalos const eventCount = Math.floor(Math.random() * 5) + 3; for (let i = 0; i < eventCount; i++) { setTimeout(() => { // Verifica se o stream ainda está ativo if (!this.activeStreams.has(streamId)) return; // Gera dados de evento const eventData = { id: `cpanel_event_${i}`, progress: Math.round((i + 1) / eventCount * 100), data: { action: payload.action, step: `Step ${i + 1} of ${eventCount}`, details: `Processing ${payload.action} on cPanel` }, timestamp: new Date().toISOString() }; // Emite o evento stream.emitter.emit('data', eventData); // Se for o último evento, emite 'end' if (i === eventCount - 1) { stream.emitter.emit('end', { id: streamId, completed: true, timestamp: new Date().toISOString(), result: { action: payload.action, status: 'completed', message: `Operation ${payload.action} completed successfully` } }); // Remove o stream da lista de ativos this.activeStreams.delete(streamId); } }, 1000 * (i + 1)); // Intervalos de 1 segundo } } /** * Loga uma requisição para análise * @private */ _logRequest(type, payload) { this.requestLog.push({ type: type, direction: 'request', payload: payload, timestamp: new Date().toISOString() }); // Limita o tamanho do log if (this.requestLog.length > 100) { this.requestLog.shift(); } } /** * Loga uma resposta para análise * @private */ _logResponse(type, response) { this.requestLog.push({ type: type, direction: 'response', response: response, timestamp: new Date().toISOString() }); // Limita o tamanho do log if (this.requestLog.length > 100) { this.requestLog.shift(); } } } module.exports = CPanelProvider;