gratian-manager
Version:
Sistema de gerenciamento e hospedagem de múltiplos bots Discord.
519 lines (421 loc) • 16.1 kB
JavaScript
// bot-manager.js
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const readline = require('readline');
function updateStatusFile(botName, data) {
const botStatusDir = path.join(process.cwd(), 'bots', botName);
const statusFilePath = path.join(botStatusDir, 'status.json');
// Garante que a pasta do bot existe
if (!fs.existsSync(botStatusDir)) {
fs.mkdirSync(botStatusDir, { recursive: true });
}
// Escreve o status
fs.writeFileSync(statusFilePath, JSON.stringify(data, null, 2));
}
const config = {
botsDirectory: './bots',
};
if (!fs.existsSync(config.botsDirectory)) {
fs.mkdirSync(config.botsDirectory, { recursive: true });
}
const bots = {};
const stoppingBots = new Set(); // Bots sendo parados manualmente
function createPackageJson(botDir, botName) {
const packagePath = path.join(botDir, 'package.json');
if (fs.existsSync(packagePath)) {
return true;
}
console.log(`Criando package.json para ${botName}...`);
const packageJson = {
name: botName,
version: '1.0.0',
description: `Bot ${botName}`,
main: 'index.js',
scripts: {
start: 'node index.js'
},
dependencies: {},
author: '',
license: 'ISC'
};
try {
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
console.log(`Package.json criado para ${botName}!`);
return true;
} catch (error) {
console.error(`Erro ao criar package.json para ${botName}:`, error.message);
return false;
}
}
function createIndexJs(botDir, botName) {
const indexPath = path.join(botDir, 'index.js');
if (fs.existsSync(indexPath)) {
return true;
}
console.log(`Criando index.js básico para ${botName}...`);
const basicIndexJs = `// Bot ${botName}
console.log('Bot ${botName} iniciado com sucesso!');
// Seu código do bot aqui
console.log('Configurando bot...');
console.log('Bot ${botName} em execução...');
// Exemplo de loop para manter o bot rodando
setInterval(() => {
}, 60000); // Log a cada minuto
`;
try {
fs.writeFileSync(indexPath, basicIndexJs);
console.log(`Index.js criado para ${botName}!`);
return true;
} catch (error) {
console.error(`Erro ao criar index.js para ${botName}:`, error.message);
return false;
}
}
async function installDependencies(botDir, botName) {
const hasPackageLock = fs.existsSync(path.join(botDir, 'package-lock.json'));
console.log(`Instalando dependências para ${botName}...`);
try {
if (hasPackageLock) {
execSync('npm ci --quiet', { cwd: botDir });
} else {
execSync('npm install --quiet', { cwd: botDir });
}
console.log(`Dependências instaladas para ${botName}!`);
return true;
} catch (error) {
console.error(`Erro ao instalar dependências para ${botName}:`, error.message);
return false;
}
}
async function startBot(botName) {
const botDir = path.join(config.botsDirectory, botName);
if (!fs.existsSync(botDir)) {
console.error(`Bot "${botName}" não encontrado.`);
return false;
}
if (bots[botName] && bots[botName].process) {
console.log(`Bot "${botName}" já está rodando.`);
return false;
}
try {
// Gera package.json se não existir
if (!fs.existsSync(path.join(botDir, 'package.json'))) {
const created = createPackageJson(botDir, botName);
if (!created) return false;
}
// Gera index.js se não existir
if (!fs.existsSync(path.join(botDir, 'index.js'))) {
const created = createIndexJs(botDir, botName);
if (!created) return false;
}
// Instala dependências
if (!fs.existsSync(path.join(botDir, 'node_modules'))) {
const installed = await installDependencies(botDir, botName);
if (!installed) return false;
}
console.log(`Iniciando bot "${botName}"...`);
const botProcess = spawn('node', ['.'], {
cwd: botDir,
stdio: 'inherit',
detached: false
});
const startedAt = new Date();
// Atualiza status.json
updateStatusFile(botName, {
status: 'running',
pid: botProcess.pid,
startedAt: startedAt.toISOString()
});
// Registra processo do bot
bots[botName] = {
process: botProcess,
status: 'running',
startTime: startedAt,
startedAt: startedAt,
directory: botDir
};
// Quando o processo encerrar
botProcess.on('close', (code) => {
console.log(`Bot "${botName}" encerrado com código ${code}.`);
delete bots[botName];
// Verifica se foi uma parada manual
if (stoppingBots.has(botName)) {
stoppingBots.delete(botName); // remove do set
updateStatusFile(botName, {
status: 'stopped',
pid: null,
startedAt: null,
uptime: 0
});
return; // ← Não reinicia!
}
// Se não foi manual, reinicia
console.log(`Reiniciando bot "${botName}" em 5 segundos...`);
updateStatusFile(botName, {
status: 'restarting',
pid: null,
startedAt: null,
uptime: 0
});
setTimeout(() => {
startBot(botName);
}, 5000);
});
console.log(`Bot "${botName}" iniciado com sucesso!`);
return true;
} catch (error) {
console.error(`Erro ao iniciar bot "${botName}":`, error.message);
return false;
}
}
function stopBot(botName) {
const bot = bots[botName];
if (!bot || !bot.process) {
console.error(`Bot "${botName}" não está rodando.`);
return false;
}
console.log(`Parando bot "${botName}"...`);
stoppingBots.add(botName); // ← marca como parada manual
bot.process.kill('SIGTERM');
// Calcular o uptime com base no startedAt, se existir
let uptime = 0;
if (bot.startedAt) {
const startTime = new Date(bot.startedAt).getTime();
uptime = Date.now() - startTime;
}
// Atualiza o arquivo de status imediatamente
updateStatusFile(botName, {
status: 'stopped',
pid: null,
startedAt: null,
uptime
});
// Após 3 segundos, força o encerramento se necessário
setTimeout(() => {
if (bot.process) {
console.log(`Forçando encerramento do bot "${botName}"...`);
bot.process.kill('SIGKILL');
}
}, 3000);
// Remove o bot da memória
delete bots[botName];
return true;
}
async function restartBot(botName) {
console.log(`Reiniciando bot "${botName}"...`);
// Atualiza o status para "restarting"
updateStatusFile(botName, {
status: 'restarting',
pid: null,
startedAt: null,
uptime: 0
});
const wasRunning = stopBot(botName);
// Aguarda 2s apenas se ele estava rodando
await new Promise(resolve => setTimeout(resolve, wasRunning ? 2000 : 0));
// Inicia novamente
return startBot(botName);
}
function listAvailableBots() {
try {
const files = fs.readdirSync(config.botsDirectory);
return files.filter(file => {
const botDir = path.join(config.botsDirectory, file);
return fs.statSync(botDir).isDirectory();
});
} catch (error) {
console.error('Erro ao listar bots:', error.message);
return [];
}
}
function showStatus() {
const availableBots = listAvailableBots();
console.log('\n===== STATUS DOS BOTS =====');
console.log(`Total de bots disponíveis: ${availableBots.length}`);
let running = 0;
let stopped = 0;
availableBots.forEach(botName => {
const status = bots[botName] ? 'RODANDO' : 'PARADO';
const statusEmoji = bots[botName] ? '🟢 ' : '🔴 ';
if (bots[botName]) {
running++;
const uptime = Math.floor((new Date() - bots[botName].startTime) / 1000);
console.log(`${statusEmoji} ${botName}: ${status} (uptime: ${formatUptime(uptime)})`);
} else {
stopped++;
console.log(`${statusEmoji} ${botName}: ${status}`);
}
});
console.log(`\nBots rodando: ${running}`);
console.log(`Bots parados: ${stopped}`);
console.log('===========================\n');
}
function formatUptime(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const secs = seconds % 60;
return `${hours}h ${minutes}m ${secs}s`;
}
async function startBotsSequentially(botNames) {
for (const botName of botNames) {
console.log(`Iniciando bot: ${botName}`);
await startBot(botName);
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'Gratian Manager> '
});
console.log('=== GRATIAN MANAGER ===');
console.log('Comandos disponíveis:');
console.log('.start [botName] - Inicia um bot específico ou todos');
console.log('.stop [botName] - Para um bot específico ou todos');
console.log('.restart [botName] - Reinicia um bot específico ou todos');
console.log('.status - Mostra o status de todos os bots');
console.log('.npm [botName] - Instala dependências para um bot específico ou todos');
console.log('.list - Lista todos os bots disponíveis');
console.log('.help - Mostra esta ajuda');
console.log('.exit - Sai do gerenciador');
console.log('\nIniciando automaticamente todos os bots disponíveis...');
const allBots = listAvailableBots();
if (allBots.length > 0) {
startBotsSequentially(allBots);
} else {
console.log('Nenhum bot encontrado para iniciar.');
}
rl.prompt();
rl.on('line', async (line) => {
const input = line.trim();
const [command, ...args] = input.split(' ');
const botName = args.join(' ');
switch (command.toLowerCase()) {
case '.start':
if (botName) {
await startBot(botName);
} else {
const botsList = listAvailableBots();
if (botsList.length === 0) {
console.log('Nenhum bot encontrado para iniciar.');
} else {
console.log(`Iniciando todos os ${botsList.length} bots...`);
await startBotsSequentially(botsList);
}
}
break;
case '.stop':
if (botName) {
stopBot(botName);
} else {
const runningBots = Object.keys(bots);
if (runningBots.length === 0) {
console.log('Nenhum bot está rodando.');
} else {
console.log(`Parando todos os ${runningBots.length} bots...`);
runningBots.forEach(bot => stopBot(bot));
}
}
break;
case '.restart':
if (botName) {
await restartBot(botName);
} else {
const allBotsList = listAvailableBots();
if (allBotsList.length === 0) {
console.log('Nenhum bot encontrado para reiniciar.');
} else {
console.log(`Reiniciando todos os ${allBotsList.length} bots...`);
for (const bot of allBotsList) {
if (bots[bot]) {
await restartBot(bot);
} else {
await startBot(bot);
}
// Pequeno intervalo entre reinicializações
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
}
break;
case '.status':
showStatus();
break;
case '.npm':
case '.npm-i':
case '.npm-install':
if (botName) {
const botDir = path.join(config.botsDirectory, botName);
if (fs.existsSync(botDir)) {
await installDependencies(botDir, botName);
} else {
console.error(`Bot "${botName}" não encontrado.`);
}
} else {
const allBotsList = listAvailableBots();
if (allBotsList.length === 0) {
console.log('Nenhum bot encontrado para instalar dependências.');
} else {
console.log(`Instalando dependências para todos os ${allBotsList.length} bots...`);
for (const bot of allBotsList) {
const botDir = path.join(config.botsDirectory, bot);
await installDependencies(botDir, bot);
}
}
}
break;
case '.list':
const availableBots = listAvailableBots();
console.log('\n=== BOTS DISPONÍVEIS ===');
if (availableBots.length === 0) {
console.log('Nenhum bot encontrado.');
} else {
availableBots.forEach(bot => {
const status = bots[bot] ? '🟢 RODANDO' : '🔴 PARADO';
console.log(`${bot}: ${status}`);
});
}
console.log('=======================\n');
break;
case '.help':
console.log('\n=== AJUDA ===');
console.log('.start [botName] - Inicia um bot específico ou todos');
console.log('.stop [botName] - Para um bot específico ou todos');
console.log('.restart [botName] - Reinicia um bot específico ou todos');
console.log('.status - Mostra o status de todos os bots');
console.log('.npm [botName] - Instala dependências para um bot específico ou todos');
console.log('.list - Lista todos os bots disponíveis');
console.log('.help - Mostra esta ajuda');
console.log('.exit - Sai do gerenciador');
console.log('============\n');
break;
case '.exit':
console.log('Encerrando todos os bots antes de sair...');
Object.keys(bots).forEach(bot => stopBot(bot));
setTimeout(() => {
console.log('Saindo do Gratian Manager. Até mais!');
rl.close();
process.exit(0);
}, 2000);
return;
default:
console.log(`Comando desconhecido: ${command}`);
console.log('Use .help para ver a lista de comandos disponíveis.');
}
rl.prompt();
}).on('close', () => {
console.log('Saindo do Gratian Manager. Até mais!');
process.exit(0);
});
process.on('SIGINT', () => {
console.log('\nEncerrando Gratian Manager...');
Object.keys(bots).forEach(bot => stopBot(bot));
setTimeout(() => {
process.exit(0);
}, 2000);
});
setTimeout(() => {
showStatus();
rl.prompt();
}, 1000);