diff --git a/.env.example b/.env.example index 0f938b1..f554fc7 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,73 @@ -# Identidade padrão do SHOGUN +# SHOGUN — configuração privada da instância +# +# Os instaladores copiam este arquivo para .env.local quando ele não existe. +# O núcleo do bot não exige chave de API para iniciar. Preencha somente as +# integrações que você realmente utilizar. Nunca versione .env.local. + +# Identidade visual padrão do produto. O dono principal é configurado por +# `npm run setup` e fica em dados/src/config.json, não neste arquivo. BOT_NAME=𝖘𝖍𝖔𝖌𝖚𝖓 DEFAULT_PERSONA=shogun # Recomendado para Termux e aparelhos com pouca memória. SHOGUN_LOW_MEMORY=false -# Controles seguros do módulo de downloads. +# --------------------------------------------------------------------------- +# BunnyFy — opcional +# --------------------------------------------------------------------------- +# false = o bot usa caminhos locais/legados quando disponíveis. +# true = habilita as capacidades BunnyFy configuradas abaixo. +BUNNYFY_ENABLED=false +BUNNYFY_BASE_URL= +BUNNYFY_API_TOKEN= +BUNNYFY_ALLOW_INSECURE_HTTP=false +BUNNYFY_ACCOUNT_URL= +BUNNYFY_CAPABILITY_TIMEOUT_MS=120000 + +# Modos: off | primary | exclusive +# off = não usa BunnyFy nessa capacidade +# primary = tenta BunnyFy e usa fallback quando o contrato permitir +# exclusive = usa BunnyFy sem fallback legado +BUNNYFY_AI_MODE=off +BUNNYFY_AI_TIMEOUT_MS=120000 BUNNYFY_YOUTUBE_MODE=off BUNNYFY_YOUTUBE_TIMEOUT_MS=190000 BUNNYFY_YOUTUBE_MAX_BYTES=52428800 BUNNYFY_YOUTUBE_MAX_CONCURRENCY=4 +BUNNYFY_TRANSCRIPTION_MODE=off +BUNNYFY_FACEBOOK_MODE=off +BUNNYFY_PINTEREST_MODE=off +BUNNYFY_TIKTOK_MODE=off +BUNNYFY_KWAI_MODE=off +BUNNYFY_IMAGES_MODE=off +BUNNYFY_STICKERS_MODE=off +BUNNYFY_CANVAS_MODE=off +BUNNYFY_LOGOS_MODE=off +BUNNYFY_GAMES_MODE=off +BUNNYFY_IMAGE_GEN_MODE=off +BUNNYFY_TAVERN_RENDER_MODE=off +BUNNYFY_NEXO_RENDER_MODE=off + +# --------------------------------------------------------------------------- +# IA direta NVIDIA — opcional +# --------------------------------------------------------------------------- +# Necessária somente quando a assistente usar NVIDIA diretamente: +# - BUNNYFY_AI_MODE=off; ou +# - BUNNYFY_AI_MODE=primary e você quiser fallback direto. +# Em BUNNYFY_AI_MODE=exclusive, a credencial do provider pertence à BunnyFy. +NVIDIA_API_KEY= + +# --------------------------------------------------------------------------- +# Transcrição VEX legada — opcional +# --------------------------------------------------------------------------- +# Preencha apenas se sua instalação usar o fallback legado de transcrição. +VEX_API_KEY= +VEX_SITE= + +# --------------------------------------------------------------------------- +# Upload GitHub legado — opcional +# --------------------------------------------------------------------------- +# Não é requisito geral do SHOGUN. Use apenas nos fluxos que ainda dependam +# desse mecanismo e crie um token dedicado com o menor escopo possível. +UPLOAD_GITHUB_TOKEN= +UPLOAD_GITHUB_REPO= diff --git a/.github/workflows/shogun-validate.yml b/.github/workflows/shogun-validate.yml index e2e5f1c..2202b90 100644 --- a/.github/workflows/shogun-validate.yml +++ b/.github/workflows/shogun-validate.yml @@ -33,6 +33,8 @@ jobs: run: npm ci --no-audit --no-fund - name: Inspecionar plataforma run: npm run preflight + - name: Validar contrato de configuração pública + run: node scripts/validate-instance-config-contract.mjs - name: Validar arquitetura modular run: | npm run typecheck:vnext @@ -59,7 +61,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] node: ['20.19.0', '22', '24'] steps: - name: Baixar o repositório @@ -75,12 +77,55 @@ jobs: shell: bash run: | node --check scripts/preflight-platform.mjs + node --check scripts/validate-instance-config-contract.mjs node --check dados/src/.scripts/config.js node --check dados/src/.scripts/start.js node --check dados/src/.scripts/start-v9-fixed.js node --check dados/src/connect.js node --check dados/src/index.js + bash -n scripts/install-linux.sh + bash -n scripts/install-macos.sh + bash -n scripts/install-termux.sh - name: Conferir configuração neutra run: node -e "const c=require('./dados/src/config.example.json'); if(c.nomebot!=='SHOGUN'||c.numerodono!=='55DDDNUMERO') process.exit(1)" - name: Construir arquitetura modular run: npm run build:vnext + + contrato-termux: + name: Contrato Termux/Android + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Baixar o repositório + uses: actions/checkout@v4 + - name: Preparar Node.js mínimo suportado + uses: actions/setup-node@v4 + with: + node-version: '20.19.0' + cache: npm + - name: Preparar FFmpeg + run: | + sudo apt-get update + sudo apt-get install -y ffmpeg + - name: Instalar dependências como clone limpo + run: npm ci --no-audit --no-fund + - name: Validar script Android + run: | + bash -n scripts/install-termux.sh + grep -q 'nodejs-lts' scripts/install-termux.sh + grep -q 'ffmpeg' scripts/install-termux.sh + grep -q 'termux-tools' scripts/install-termux.sh + grep -q '.env.local' scripts/install-termux.sh + - name: Exercitar ramificação Termux do preflight + env: + TERMUX_VERSION: ci + PREFIX: /tmp/shogun-termux-prefix + run: | + mkdir -p "$PREFIX/bin" + touch "$PREFIX/bin/termux-wake-lock" + node scripts/preflight-platform.mjs + - name: Validar entradas do runtime + run: | + node --check dados/src/.scripts/start-v9-fixed.js + node --check dados/src/connect.js + node --check dados/src/index.js diff --git a/README.md b/README.md index 8fa63ba..a2933d6 100644 --- a/README.md +++ b/README.md @@ -5,193 +5,310 @@

𝖘𝖍𝖔𝖌𝖚𝖓

- Um bot para os seus grupos de WhatsApp.
- Ele modera, baixa vídeo e música, faz figurinha, joga e tem um RPG inteiro.
- Roda no seu computador ou num celular Android parado na gaveta — e é de graça. + Seu grupo não precisa de mais um bot. Precisa de um sistema.
+ Moderação, mídia, jogos, economia e automações em uma única experiência para WhatsApp.

- Tela do bot conectando, com arte em pixel -    - Feed mostrando comandos chegando em tempo real + Node.js 20.19+ + WhatsApp via Baileys + Windows Linux macOS Termux + Licença ISC

- Assim que ele fica quando está ligado: a tela de conexão e o - acompanhamento ao vivo de tudo que chega. + Instalar +  •  + Recursos +  •  + Arquitetura +  •  + Configuração +  •  + Primeiros passos +  •  + Problemas

--- -## 👉 Nunca instalou nada assim? Comece por aqui - -Escolha onde o bot vai rodar. Cada guia começa do zero, mostra **o que aparece -na sua tela** a cada passo e o que fazer quando não aparece. - - - -
-📱 Android
-Celular reserva na tomada
-15 a 35 min
-baixar o Termux -
-🪟 Windows
-10 ou 11, no seu PC
-10 a 20 min +
+ +### Um bot que realmente vive no grupo + +O SHOGUN foi pensado para grupos que querem mais do que meia dúzia de respostas automáticas. Ele combina administração, mídia, interação entre membros, economia persistente, RPG e recursos opcionais de IA sem transformar a instalação numa peregrinação por quinze serviços obrigatórios. + +- **o núcleo inicia sem chave de API**; +- **funciona sem IA** e ganha recursos extras quando integrações são configuradas; +- **mantém sessão e dados localmente** no aparelho onde está rodando; +- **trata Termux como plataforma de verdade**, não como nota de rodapé; +- **separa o núcleo do bot de serviços externos**, evitando que uma API indisponível derrube tudo. + -🐧 Linux
-Desktop, mini PC ou servidor
-10 a 20 min +
+ Painel de conexão do SHOGUN
+ Painel de conexão e primeira execução.

- Painel de conexão do bot no terminal, com arte em pixel -    - Feed do terminal mostrando comandos e mensagens chegando -

- -

- À esquerda, a tela de conexão. À direita, o feed ao vivo — cada comando - e mensagem que chega aparece assim no seu terminal. + Feed do SHOGUN mostrando comandos em tempo real

+

Mensagens e comandos chegando em tempo real. Sem painel web obrigatório, sem teatro.

--- -## Vai rodar no Android? +## ✦ O que vem no SHOGUN -Antes de tudo, instale o **Termux** — é o aplicativo onde o bot roda. Toque e o -download começa: + + + + + + + + + + + +
+

🛡️ Moderação

+Boas-vindas, anti-link, anti-flood, advertências, controles administrativos e automações para manter o grupo utilizável quando os humanos resolvem testar os limites da civilização. +
+

🎞️ Mídia

+Figurinhas, conversões, áudio, imagem e fluxos de download. Capacidades avançadas podem usar a BunnyFy quando configuradas. +
+

🎮 Jogos

+Brincadeiras, comandos sociais e experiências interativas pensadas para acontecer dentro da conversa, sem mandar todo mundo para outro aplicativo. +
+

🪙 Economia & RPG

+Progressão, inventário, recursos persistentes e sistemas de grupo que continuam existindo depois que a mensagem some do histórico. +
+

🧠 IA opcional

+O núcleo inicia sem chave de IA. Quando provedores externos são configurados, o SHOGUN libera conversação e geração de mídia sem acoplar o boot a eles. +
+

💾 Estado local

+Sessão, configuração e bancos pertencem à instalação. Atualizar o código não deveria significar sacrificar o estado vivo do bot aos deuses do `rm -rf`. +
+ +--- -**[⬇️ Baixar Termux (F-Droid)](https://f-droid.org/repo/com.termux_1022.apk)** - · [alternativa no GitHub](https://github.com/termux/termux-app/releases/download/v0.118.3/termux-app_v0.118.3%2Bgithub-debug_universal.apk) +## ⬇ Instale onde quiser -> Não use a versão da Play Store: está parada há anos e não funciona para isso. + + + + + + + +
+

📱 Android

+Termux

+Guia completo →
+F-Droid ou GitHub Releases +
+

🪟 Windows

+Windows 10/11

+Guia completo →
+PowerShell +
+

🐧 Linux

+Desktop / servidor

+Guia completo →
+Bash +
+

🍎 macOS

+Intel / Apple Silicon

+Guia completo →
+Bash + Homebrew +
-Depois siga o [guia do Android](docs/instalacao/termux.md), que explica o resto -tela por tela. +> [!IMPORTANT] +> No Android, use preferencialmente a linha tradicional do **Termux pelo F-Droid ou GitHub Releases**. A edição do Google Play segue uma linha diferente e pode apresentar incompatibilidades que o guia do SHOGUN não assume. -## Já usa terminal? Comece em três minutos +### Terminal já não mete medo? ```bash git clone https://github.com/dgreych/shogun.git cd shogun -bash scripts/install-linux.sh # Windows: install-windows.ps1 · Android: install-termux.sh +bash scripts/install-linux.sh # Android: install-termux.sh | macOS: install-macos.sh npm start ``` -Leia o QR no WhatsApp, mande `!menu` no grupo e pronto. Se a palavra "terminal" -já assusta, comece pelo [guia da sua plataforma](#-nunca-instalou-nada-assim-comece-por-aqui) — ele explica cada -tela, sem pressupor nada. +No Windows: -## Como funciona +```powershell +powershell -ExecutionPolicy Bypass -File .\scripts\install-windows.ps1 +npm start +``` + +Os instaladores preservam um `.env.local` existente ou criam um automaticamente a partir de `.env.example`, com integrações externas desligadas por padrão. A primeira execução abre o fluxo de conexão e oferece **QR Code** ou **código de pareamento**. Depois disso, a sessão é reaproveitada nas próximas inicializações. + +--- + +## ◈ Como ele se organiza ```mermaid flowchart LR - A[Seu WhatsApp] <--> B[𝖘𝖍𝖔𝖌𝖚𝖓
no seu aparelho] - B --> C[Grupos
moderação e jogos] - B --> D[Downloads
YouTube, TikTok e mais] - B -.opcional.-> E[API de IA
imagem e transcrição] + W[WhatsApp] <--> S[𝖘𝖍𝖔𝖌𝖚𝖓] + S --> G[Grupos] + S --> M[Mídia] + S --> R[Jogos · RPG · Economia] + S --> L[(Sessão e dados locais)] + S -. quando configurado .-> E[Serviços externos] + E -. mídia avançada .-> B[BunnyFy] + E -. recursos opcionais .-> A[IA / APIs] ``` -A sessão e os dados dos grupos ficam **no seu aparelho**. Nada de servidor de -terceiro, salvo as APIs que você mesmo configurar. +O bot continua responsável pelo fluxo do WhatsApp e pelo estado do grupo. Integrações externas entram como capacidades adicionais, não como condição para o processo existir. -## O que ele faz +--- -**Cuida do grupo.** Boas-vindas, anti-link, anti-flood e advertências com -banimento automático na terceira. Moderadores com permissões próprias, separadas -das do administrador do WhatsApp. Silenciar quem está atrapalhando, abrir e -fechar o grupo por horário. +## ✓ Primeira inicialização -**Resolve mídia.** Baixa de YouTube, TikTok, Instagram, Twitter, Facebook, -Pinterest e Kwai. Vídeo vira áudio, áudio vira texto, imagem vira figurinha e -figurinha vira imagem. Faz figurinha animada de vídeo curto e mistura dois -emojis num só. + + + + + + + +
1. Instale
o instalador cria o ambiente local e instala dependências.
2. Configure
npm run setup
define o dono principal desta instância, nome e prefixo.
3. Verifique
npm run preflight
confere sistema, configuração e integrações.
4. Conecte
npm start
abre QR ou pareamento.
-**Gera imagem por IA.** Texto vira imagem, remove fundo, aumenta resolução. O -roteador escolhe o modelo pelo tipo de pedido: pedido rápido vai para o modelo -rápido, pedido caprichado vai para o modelo de qualidade. +

+ Preflight do SHOGUN +

-**Conversa.** A assistente responde quando mencionada. Cada grupo escolhe a -personalidade, e ela muda o tom das respostas e o visual dos menus junto. +### Quem é o dono principal? + +```text +SHOGUN (projeto e créditos) + | + +-- sua instalação clonada + | + +-- dono principal da instância <- seu numerodono + | + +-- sessão WhatsApp + | + +-- grupos + +-- admins do grupo são outra coisa +``` -**Tem um RPG inteiro.** Trabalho, mineração, pesca, caça, forja, plantio, -cozinha, propriedades que rendem por dia, mercado entre jogadores, habilidades -que evoluem e ranking. Cada grupo tem a própria economia. +O número informado no setup controla **esta cópia do bot** e os comandos reservados ao dono. Ele não muda a autoria do projeto e não significa “dono do grupo” no WhatsApp. A explicação completa, incluindo APIs e modos BunnyFy, está em **[Configuração da sua instância](docs/configuracao-da-instancia.md)**. -**E jogos.** Velha, forca, quiz, roleta, caça-palavras e uma taverna de duelos -por turnos. +A configuração local fica em `dados/src/config.json`, as opções privadas ficam em `.env.local` e a sessão do WhatsApp fica em `dados/database/qr-code/`. -## Configuração +> [!CAUTION] +> A pasta `dados/database/qr-code/`, `.env.local` e `dados/src/config.json` pertencem à sua instalação. **Não envie, compacte nem publique esses arquivos.** A sessão contém material de autenticação e os outros arquivos podem conter dados privados ou credenciais. -

- Saída do comando de verificação do ambiente -

+--- -

- npm run preflight confere tudo que o bot precisa antes de você começar. -

+## 🔑 APIs sem adivinhação + +Você **não precisa cadastrar uma pilha de chaves para chegar ao primeiro `!menu`**. -O instalador pergunta seu nome, seu número com país e DDD, o nome do bot e o -prefixo dos comandos. Nada disso sai do seu aparelho. +| Situação | O que configurar | +| --- | --- | +| só quero instalar e usar o núcleo | setup + WhatsApp; nenhuma chave de API | +| quero BunnyFy | URL + credencial de consumidor BunnyFy e os modos desejados | +| quero IA NVIDIA direta | `NVIDIA_API_KEY` | +| quero IA exclusivamente pela BunnyFy | BunnyFy ativa + `BUNNYFY_AI_MODE=exclusive`; sem chave NVIDIA no bot | +| quero fallback VEX legado | `VEX_API_KEY` + `VEX_SITE` | -## Manutenção +O arquivo **[docs/configuracao-da-instancia.md](docs/configuracao-da-instancia.md)** contém a matriz completa e exemplos seguros. O `npm run preflight` mostra o que está configurado sem revelar valores. + +--- + +## ⚙ Operação do dia a dia ```bash -npm run preflight # confere Node.js, npm, Git, FFmpeg e a plataforma -npm run setup # refaz a configuração inicial -npm start # inicia o bot +npm run preflight # diagnóstico do ambiente e da configuração +npm run setup # configuração do dono/nome/prefixo +npm start # iniciar ``` -Atualizar: +Para atualizar uma instalação existente: ```bash -git pull --ff-only && npm ci --no-audit --no-fund && npm start +git pull --ff-only +npm ci --no-audit --no-fund +npm run preflight +npm start ``` -## Dúvidas frequentes +No Termux, mantenha o processo acordado com: -**Preciso de um número separado?** Sim. Use um chip só do bot — ele conecta -como aparelho vinculado e responde por essa conta. +```bash +termux-wake-lock +``` -**Preciso deixar o computador ligado?** Sim, enquanto quiser o bot no ar. Por -isso muita gente usa um Android antigo na tomada. +O comando faz parte das ferramentas do Termux. **Não é necessário instalar o aplicativo Termux:API apenas para usar o wake lock.** + +--- -**Funciona sem chave de IA?** Funciona. Moderação, downloads, figurinhas, jogos -e RPG não dependem de IA. Só geração de imagem e transcrição precisam. +## ◉ Portabilidade verificada -**Vão banir meu número?** O bot usa a conexão oficial de aparelhos vinculados. -O que causa bloqueio é comportamento: disparo em massa e spam. Use com bom -senso. +A validação pública não se resume a “rodou na máquina de quem fez”. O workflow testa o projeto em combinações de Node.js sobre **Linux, Windows e macOS**, além de manter um contrato específico para os caminhos usados no **Termux**. -**Meus dados vão para algum servidor?** Não. Sessão, bancos e configuração -ficam no aparelho onde o bot roda. +| Superfície | Verificação | +| --- | --- | +| Linux | instalação, build e validações do projeto | +| Windows | lockfile, runtime e portabilidade | +| macOS | Intel/Apple Silicon no caminho suportado pelo Node | +| Termux | instalador, dependências e contrato Android | -## Segurança +Requisito mínimo: **Node.js 20.19+**, npm, Git e FFmpeg. -A pasta `dados/database/qr-code/` guarda a sessão do WhatsApp. **Quem tem essa -pasta entra na sua conta**: não compacte, não envie, não publique. Ela já está -protegida pelo `.gitignore`. +--- -## Requisitos +## ? Dúvidas rápidas + +
+Preciso de um número separado? +
+É fortemente recomendado. O SHOGUN funciona como aparelho vinculado à conta conectada. +
+ +
+O que significa “dono” no setup? +
+É o dono principal desta instalação: o número autorizado a usar comandos reservados ao dono. Não é o autor do projeto e não é automaticamente o administrador de todos os grupos. Veja Configuração da sua instância. +
+ +
+Preciso de chave de API para iniciar? +
+Não. O núcleo inicia sem chave de API. BunnyFy, NVIDIA, VEX e outras integrações são configuradas conforme os recursos que você quiser ativar. +
+ +
+Preciso deixar o aparelho ligado? +
+Sim. O processo precisa continuar rodando. No Android, use termux-wake-lock e retire o Termux da otimização agressiva de bateria. +
+ +
+Funciona sem IA? +
+Sim. IA e serviços externos são capacidades opcionais; o núcleo do bot não depende deles para iniciar. +
+ +
+Onde começo se nunca usei terminal? +
+Use o guia da sua plataforma. Ele acompanha os scripts existentes no repositório, em vez de mandar você executar uma coleção arqueológica de comandos copiados da internet. +
-

- Node.js 20.19+ - WhatsApp Baileys - Windows, Linux e Termux - Licença ISC -

+--- -Node.js 20.19 ou superior · FFmpeg · Git · um número de WhatsApp dedicado +## Documentação -Os instaladores cuidam disso para você. A lista está aqui para quem já tem o -ambiente montado e quer conferir. +**[Configuração da instância](docs/configuracao-da-instancia.md)** · **[Primeiros passos](docs/primeiros-passos.md)** · **[Segurança](docs/seguranca.md)** · **[Solução de problemas](docs/solucao-de-problemas.md)** · **[Termux](docs/instalacao/termux.md)** · **[Windows](docs/instalacao/windows.md)** · **[Linux](docs/instalacao/linux.md)** · **[macOS](docs/instalacao/macos.md)** -## Licença +## Licença e créditos -Publicado sob a licença ISC. Veja [LICENSE](LICENSE) e [NOTICE](NOTICE). +Publicado sob a licença ISC. Consulte [LICENSE](LICENSE) e [NOTICE](NOTICE). \ No newline at end of file diff --git a/dados/src/.scripts/validate-release-output.js b/dados/src/.scripts/validate-release-output.js index e5e897f..568116c 100644 --- a/dados/src/.scripts/validate-release-output.js +++ b/dados/src/.scripts/validate-release-output.js @@ -37,7 +37,7 @@ if (fs.existsSync(runtimeIndexPath)) { const runtimeIndex = fs.readFileSync(runtimeIndexPath, 'utf8'); assert(runtimeIndex.includes("case 'criador'"), 'comando criador presente'); assert(runtimeIndex.includes('github.com/dgreych/shogun'), 'repositório do produto presente'); - assert(runtimeIndex.includes('*Maurício Almeida*'), 'autoria do produto presente'); + assert(runtimeIndex.includes('*Alaska dev* (Maurício)'), 'autoria do produto presente no cartão real do criador'); assert(!runtimeIndex.includes('Hiudy'), 'crédito de terceiro não aparece em saída do bot'); assert(!/sentinela/i.test(runtimeIndex), 'sem rótulo genérico na identidade'); assert(runtimeIndex.includes('Comando não reconhecido'), 'cartão de comando inválido presente'); @@ -68,6 +68,7 @@ if (fs.existsSync(packagePath)) { assert(packageData.name === 'shogun-whatsapp', 'pacote identificado como SHOGUN'); assert(packageData.version === '2.0.0', 'versão principal definida como 2.0.0'); assert(String(packageData.description || '').startsWith('SHOGUN é seu sentinela'), 'descrição independente presente'); + assert(packageData.author === 'Maurício Almeida', 'autoria canônica declarada no pacote'); assert(packageData.engines?.node === '>=20.19.0', 'piso real do Node.js declarado'); assert(Boolean(packageData.scripts?.preflight), 'inspeção multiplataforma disponível'); } diff --git a/dados/src/funcs/downloads/youtube.js b/dados/src/funcs/downloads/youtube.js index 4615cf3..2eee42f 100644 --- a/dados/src/funcs/downloads/youtube.js +++ b/dados/src/funcs/downloads/youtube.js @@ -7,7 +7,7 @@ import axios from 'axios'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; -import { getConfig } from '../../utils/gyomeiStore.js'; +import { getConfig } from '../../utils/shogunStore.js'; import { downloadYoutubeAudioForPlay, downloadYoutubeVideoForPlay } from '../../services/bunnyfy/youtubeGateway.js'; const DOWNLOAD_TIMEOUT = 180000; diff --git a/dados/src/services/bunnyfy/runtimeConfig.js b/dados/src/services/bunnyfy/runtimeConfig.js index bb33597..f5ff607 100644 --- a/dados/src/services/bunnyfy/runtimeConfig.js +++ b/dados/src/services/bunnyfy/runtimeConfig.js @@ -1,4 +1,4 @@ -import { getConfig } from '../../utils/gyomeiStore.js'; +import { getConfig } from '../../utils/shogunStore.js'; const BUNNYFY_CONFIG_KEYS = Object.freeze([ 'BUNNYFY_ENABLED', @@ -17,7 +17,15 @@ const BUNNYFY_CONFIG_KEYS = Object.freeze([ 'BUNNYFY_STICKERS_MODE', 'BUNNYFY_CANVAS_MODE', 'BUNNYFY_LOGOS_MODE', - 'BUNNYFY_GAMES_MODE' + 'BUNNYFY_GAMES_MODE', + 'BUNNYFY_IMAGE_GEN_MODE', + 'BUNNYFY_TAVERN_RENDER_MODE', + 'BUNNYFY_NEXO_RENDER_MODE', + 'BUNNYFY_TRANSCRIPTION_MODE', + 'BUNNYFY_FACEBOOK_MODE', + 'BUNNYFY_PINTEREST_MODE', + 'BUNNYFY_TIKTOK_MODE', + 'BUNNYFY_KWAI_MODE' ]); const EXTRA_ALIASES = Object.freeze({ @@ -59,4 +67,4 @@ function resolveBunnyFyRuntimeEnv(env = process.env, config = getConfig()) { export { BUNNYFY_CONFIG_KEYS, resolveBunnyFyRuntimeEnv -}; +}; \ No newline at end of file diff --git a/dados/src/utils/shogunMedia.js b/dados/src/utils/shogunMedia.js new file mode 100644 index 0000000..61d42a1 --- /dev/null +++ b/dados/src/utils/shogunMedia.js @@ -0,0 +1,44 @@ +import { downloadContentFromMessage } from 'baileys'; +import { getQuotedMediaSource } from './shogunCore.js'; + +async function streamToBuffer(stream) { + const chunks = []; + for await (const chunk of stream) { + chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + } + return Buffer.concat(chunks); +} + +export async function downloadQuotedCommandMedia(message) { + const source = getQuotedMediaSource(message); + if (!source?.message) { + return { + ok: false, + msg: 'Responda a uma foto, GIF ou vídeo válido.' + }; + } + + try { + const stream = await downloadContentFromMessage(source.message, source.type); + const buffer = await streamToBuffer(stream); + + if (!Buffer.isBuffer(buffer) || buffer.length === 0) { + return { + ok: false, + msg: 'A mídia citada foi reconhecida, mas veio vazia.' + }; + } + + return { + ok: true, + buffer, + type: source.type, + gifPlayback: source.gifPlayback === true + }; + } catch (error) { + return { + ok: false, + msg: `Não foi possível baixar a mídia citada: ${error.message}` + }; + } +} diff --git a/dados/src/utils/shogunOperations.js b/dados/src/utils/shogunOperations.js new file mode 100644 index 0000000..0a2f14d --- /dev/null +++ b/dados/src/utils/shogunOperations.js @@ -0,0 +1,373 @@ +import axios from 'axios'; +import fs from 'fs'; +import { downloadContentFromMessage } from 'baileys'; +import { + DELETED_FILE, + MEDIA_DIR, + getAutomationData, + getConfig, + normalizeCommand, + readJson, + saveAutomationData, + unwrapMessageContent, + writeJson +} from './shogunStore.js'; +import { getActivePersona } from './shogunCore.js'; +import { transcriptionWithBunnyFy } from '../services/bunnyfy/capabilityGateway.js'; + +const MAX_RECENT_MESSAGES = 5000; +const commandContext = new Map(); +const deletedMessages = new Map(); +const recentMessages = new Map(); +const mediaInterceptorSockets = new WeakSet(); +const deletedTrackerSockets = new WeakSet(); + +export function isAutoTranscriptionEnabled(chatId) { + return getAutomationData().autoTranscriptionGroups?.[chatId] === true; +} + +export function toggleAutoTranscription(chatId) { + const data = getAutomationData(); + data.autoTranscriptionGroups[chatId] = !data.autoTranscriptionGroups[chatId]; + saveAutomationData(data); + return data.autoTranscriptionGroups[chatId]; +} + +export async function transcribeAudioUrl(audioUrl) { + const config = getConfig(); + const site = String(config.site_vex || '').replace(/\/$/, ''); + const apiKey = String(config.apikey_vex || '').trim(); + + if (!site || !apiKey || apiKey.startsWith('COLOQUE_')) { + return { ok: false, msg: 'Configure site_vex e apikey_vex em dados/src/config.json.' }; + } + if (!audioUrl) return { ok: false, msg: 'Não foi possível gerar o link temporário do áudio.' }; + + try { + const url = `${site}/api/ias/transcrever?apikey=${encodeURIComponent(apiKey)}&query=${encodeURIComponent(audioUrl)}`; + const response = await axios.get(url, { + headers: { Accept: '*/*', 'User-Agent': 'Node.js' }, + timeout: 120000 + }); + const data = response.data; + const text = data?.resultado?.data?.texto + || data?.resultado?.texto + || data?.data?.texto + || data?.data?.text + || data?.texto + || data?.text + || data?.resposta + || data?.result; + + if (!text) { + return { ok: false, msg: data?.message || data?.msg || 'A API não retornou uma transcrição.' }; + } + return { ok: true, texto: String(text).trim(), source: 'vex' }; + } catch (error) { + return { + ok: false, + msg: error?.response?.data?.message + || error?.response?.data?.msg + || error?.response?.data?.detail + || error.message + }; + } +} + +export async function transcribeAudio(buffer, { mime = 'audio/ogg', language, uploadForFallback } = {}) { + try { + return await transcriptionWithBunnyFy(buffer, { + mime, + language, + legacyFallback: async () => { + if (typeof uploadForFallback !== 'function') { + return { ok: false, msg: 'Não foi possível gerar o link temporário do áudio.' }; + } + const audioUrl = await uploadForFallback(); + return transcribeAudioUrl(audioUrl); + } + }); + } catch (error) { + return { ok: false, msg: error?.message || 'Não foi possível transcrever o áudio agora.' }; + } +} + +export async function saveCommandMedia(command, buffer, type, gifPlayback = false) { + const normalized = normalizeCommand(command); + if (!normalized) throw new Error('Informe o comando que receberá a mídia.'); + if (!Buffer.isBuffer(buffer) || buffer.length === 0) throw new Error('Mídia inválida.'); + + fs.mkdirSync(MEDIA_DIR, { recursive: true }); + const normalizedType = type === 'image' ? 'image' : type === 'gif' ? 'gif' : 'video'; + const extension = normalizedType === 'image' ? 'jpg' : normalizedType === 'gif' ? 'gif' : 'mp4'; + const safeName = normalized.replace(/[^a-z0-9_-]/gi, '_'); + const filePath = `${MEDIA_DIR}/${safeName}.${extension}`; + fs.writeFileSync(filePath, buffer); + + const data = getAutomationData(); + data.commandMedia[normalized] = { + type: normalizedType, + path: filePath, + gifPlayback: Boolean(gifPlayback), + updatedAt: new Date().toISOString() + }; + saveAutomationData(data); + return data.commandMedia[normalized]; +} + +export function listCommandMedia() { + return Object.entries(getAutomationData().commandMedia || {}) + .filter(([, item]) => item?.path) + .map(([command, item]) => ({ command, ...item })); +} + +export function getCommandMedia(command) { + const normalized = normalizeCommand(command); + const item = getAutomationData().commandMedia?.[normalized]; + return item?.path ? item : null; +} + +export function resolveCommandMedia(command) { + const persona = getActivePersona(); + return getCommandMedia(`${persona}_${command}`) || getCommandMedia(command); +} + +export function removeCommandMedia(command) { + const normalized = normalizeCommand(command); + const data = getAutomationData(); + const current = data.commandMedia?.[normalized]; + if (!current) return false; + + try { + if (current.path && fs.existsSync(current.path)) fs.unlinkSync(current.path); + } catch {} + + delete data.commandMedia[normalized]; + saveAutomationData(data); + return true; +} + +function installCommandMediaInterceptor(sock) { + if (!sock?.sendMessage || mediaInterceptorSockets.has(sock)) return; + mediaInterceptorSockets.add(sock); + + const originalSendMessage = sock.sendMessage.bind(sock); + sock.sendMessage = async (jid, content, options) => { + try { + const context = commandContext.get(jid); + const fresh = context && Date.now() - context.timestamp < 20000; + const replaceable = content && ( + content.image + || content.video + || typeof content.text === 'string' + || typeof content.caption === 'string' + ); + + if (fresh && replaceable) { + const custom = resolveCommandMedia(context.command); + if (custom?.path && fs.existsSync(custom.path)) { + const nextContent = { ...content }; + const caption = String(content.caption ?? content.text ?? ''); + delete nextContent.text; + delete nextContent.caption; + delete nextContent.image; + delete nextContent.video; + + const mediaBuffer = fs.readFileSync(custom.path); + if (custom.type === 'image') { + nextContent.image = mediaBuffer; + nextContent.caption = caption; + nextContent.mimetype = 'image/jpeg'; + } else { + nextContent.video = mediaBuffer; + nextContent.caption = caption; + nextContent.gifPlayback = Boolean(custom.gifPlayback); + nextContent.mimetype = custom.type === 'gif' ? 'image/gif' : 'video/mp4'; + } + + commandContext.delete(jid); + return originalSendMessage(jid, nextContent, options); + } + } + } catch (error) { + console.error('[GYOMEI/MÍDIA] Falha ao aplicar mídia personalizada:', error.message); + } + return originalSendMessage(jid, content, options); + }; +} + +export function prepareCommandMediaContext(sock, chatId, command) { + const normalized = normalizeCommand(command); + const ignored = new Set([ + 'setmidia', 'delmidia', 'remmidia', 'menumidia', 'listmidias', + 'setmidia-profilep', 'setperfilpersona', 'changeperso', 'mudarpersona', + 'setprompt', 'verprompt', 'resetprompt', 'prompts', 'menuprompt', + 'adddono', 'deldono', 'listdonos' + ]); + if (!chatId || !normalized || ignored.has(normalized)) return; + installCommandMediaInterceptor(sock); + commandContext.set(chatId, { command: normalized, timestamp: Date.now() }); +} + +function recentKey(remoteJid, id) { + return remoteJid && id ? `${remoteJid}_${id}` : ''; +} + +function extractDeleteKey(message) { + return unwrapMessageContent(message)?.protocolMessage?.key || null; +} + +function cacheIncomingMessage(message) { + const remoteJids = [message?.key?.remoteJid, message?.key?.remoteJidAlt].filter(Boolean); + const id = message?.key?.id; + if (!id || !remoteJids.length || extractDeleteKey(message)) return; + + for (const remoteJid of remoteJids) recentMessages.set(recentKey(remoteJid, id), message); + while (recentMessages.size > MAX_RECENT_MESSAGES) { + const oldest = recentMessages.keys().next().value; + if (!oldest) break; + recentMessages.delete(oldest); + } +} + +function rememberDeleted(chatId, message) { + if (!chatId || !message?.key?.id) return; + const list = deletedMessages.get(chatId) || []; + if (list.some(item => item?.key?.id === message.key.id)) return; + list.unshift(message); + deletedMessages.set(chatId, list.slice(0, 5)); + + const serializable = {}; + for (const [jid, items] of deletedMessages.entries()) serializable[jid] = items.slice(0, 5); + writeJson(DELETED_FILE, serializable); +} + +function findCachedMessage(cacheGetter, key) { + if (!key?.id) return null; + const possibleChats = [key.remoteJid, key.remoteJidAlt].filter(Boolean); + for (const chatId of possibleChats) { + const internal = recentMessages.get(recentKey(chatId, key.id)); + if (internal) return internal; + } + + const cache = typeof cacheGetter === 'function' ? cacheGetter() : cacheGetter; + if (!cache) return null; + for (const chatId of possibleChats) { + const external = cache.get?.(recentKey(chatId, key.id)); + if (external) return external; + } + return null; +} + +export function installDeletedMessageTracker(sock, cacheGetter) { + if (!sock?.ev || deletedTrackerSockets.has(sock)) return; + deletedTrackerSockets.add(sock); + + const persisted = readJson(DELETED_FILE, {}); + for (const [jid, items] of Object.entries(persisted)) { + if (Array.isArray(items)) deletedMessages.set(jid, items.slice(0, 5)); + } + + const captureKey = key => { + const original = findCachedMessage(cacheGetter, key); + if (original) { + const chatId = key.remoteJid || original.key?.remoteJid; + if (chatId) rememberDeleted(chatId, original); + } + }; + + sock.ev.on('messages.delete', event => { + const keys = Array.isArray(event?.keys) ? event.keys : Array.isArray(event) ? event : []; + keys.forEach(captureKey); + }); + + sock.ev.on('messages.update', updates => { + if (!Array.isArray(updates)) return; + for (const item of updates) { + const key = extractDeleteKey(item?.update?.message || item?.message); + if (key) captureKey(key); + } + }); + + sock.ev.on('messages.upsert', event => { + for (const message of event?.messages || []) { + const deleteKey = extractDeleteKey(message); + if (deleteKey) captureKey(deleteKey); + else cacheIncomingMessage(message); + } + }); +} + +async function streamToBuffer(stream) { + const chunks = []; + for await (const chunk of stream) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)); + return Buffer.concat(chunks); +} + +export async function returnDeletedMessage(sock, chatId, position, quotedMessage) { + const index = Number(position) - 1; + if (!Number.isInteger(index) || index < 0 || index > 4) { + return { ok: false, msg: 'Use return1 a return5 ou return 1 a return 5.' }; + } + + const original = (deletedMessages.get(chatId) || [])[index]; + if (!original?.message) { + return { ok: false, msg: `Não há mensagem apagada na posição ${position}.` }; + } + + const content = unwrapMessageContent(original.message); + const sender = original.key?.participant || original.key?.remoteJid; + const prefix = sender + ? `🗑️ *Mensagem apagada #${position}*\n👤 @${String(sender).split('@')[0]}\n\n` + : `🗑️ *Mensagem apagada #${position}*\n\n`; + const mentions = sender ? [sender] : []; + const options = quotedMessage ? { quoted: quotedMessage } : undefined; + + try { + const text = content?.conversation || content?.extendedTextMessage?.text; + if (text) { + await sock.sendMessage(chatId, { text: `${prefix}${text}`, mentions }, options); + return { ok: true }; + } + + const candidates = [ + ['imageMessage', 'image'], + ['videoMessage', 'video'], + ['audioMessage', 'audio'], + ['stickerMessage', 'sticker'], + ['documentMessage', 'document'] + ]; + + for (const [field, type] of candidates) { + const media = content?.[field]; + if (!media) continue; + const stream = await downloadContentFromMessage(media, type); + const buffer = await streamToBuffer(stream); + const caption = media.caption ? `${prefix}${media.caption}` : prefix.trim(); + const payload = { mentions }; + + if (type === 'image') Object.assign(payload, { image: buffer, caption }); + if (type === 'video') Object.assign(payload, { video: buffer, caption, gifPlayback: media.gifPlayback === true }); + if (type === 'audio') Object.assign(payload, { + audio: buffer, + mimetype: media.mimetype || 'audio/ogg; codecs=opus', + ptt: media.ptt === true + }); + if (type === 'sticker') Object.assign(payload, { sticker: buffer }); + if (type === 'document') Object.assign(payload, { + document: buffer, + fileName: media.fileName || 'arquivo', + mimetype: media.mimetype || 'application/octet-stream', + caption + }); + + await sock.sendMessage(chatId, payload, options); + return { ok: true }; + } + + await sock.sendMessage(chatId, { text: `${prefix}[Tipo de mensagem ainda não suportado]`, mentions }, options); + return { ok: true }; + } catch (error) { + return { ok: false, msg: `Não foi possível recuperar a mídia: ${error.message}` }; + } +} diff --git a/dados/src/utils/shogunRuntime.js b/dados/src/utils/shogunRuntime.js new file mode 100644 index 0000000..d79971c --- /dev/null +++ b/dados/src/utils/shogunRuntime.js @@ -0,0 +1,4 @@ +export { getConfig } from './shogunStore.js'; +export * from './shogunCore.js'; +export * from './shogunOperations.js'; +export * from './shogunMedia.js'; diff --git a/dados/src/utils/shogunStore.js b/dados/src/utils/shogunStore.js new file mode 100644 index 0000000..d8e7543 --- /dev/null +++ b/dados/src/utils/shogunStore.js @@ -0,0 +1,149 @@ +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +import { DEFAULT_NVIDIA_MODEL, isKnownNvidiaModel } from './nvidiaApi.js'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +export const SRC_DIR = path.resolve(__dirname, '..'); +export const DATABASE_DIR = path.resolve(SRC_DIR, '..', 'database'); +export const OWNER_DB_DIR = path.join(DATABASE_DIR, 'dono'); +export const AUTOMATIONS_FILE = path.join(OWNER_DB_DIR, 'automacoes-v9.json'); +export const MEDIA_DIR = path.join(OWNER_DB_DIR, 'command-media'); +export const DELETED_FILE = path.join(OWNER_DB_DIR, 'deleted-messages-v9.json'); +export const CONFIG_FILE = path.join(SRC_DIR, 'config.json'); + +export const DEFAULT_DATA = { + autoTranscriptionGroups: {}, + commandMedia: {}, + additionalOwners: [], + assistantPrompts: {}, + activePersona: 'gyomei' +}; + +export function ensureDirectories() { + fs.mkdirSync(OWNER_DB_DIR, { recursive: true }); + fs.mkdirSync(MEDIA_DIR, { recursive: true }); +} + +function reviveJsonValue(_key, value) { + if ( + value + && value.type === 'Buffer' + && Array.isArray(value.data) + && value.data.every(item => Number.isInteger(item) && item >= 0 && item <= 255) + ) { + return Buffer.from(value.data); + } + return value; +} + +export function readJson(file, fallback) { + try { + return JSON.parse(fs.readFileSync(file, 'utf8'), reviveJsonValue); + } catch { + return fallback; + } +} + +export function writeJson(file, value) { + ensureDirectories(); + const temporary = `${file}.tmp`; + fs.writeFileSync( + temporary, + JSON.stringify(value, (_, item) => typeof item === 'bigint' ? item.toString() : item, 2) + ); + fs.renameSync(temporary, file); +} + +export function getAutomationData() { + const stored = readJson(AUTOMATIONS_FILE, {}); + return { + ...DEFAULT_DATA, + ...stored, + autoTranscriptionGroups: stored.autoTranscriptionGroups || {}, + commandMedia: stored.commandMedia || {}, + additionalOwners: Array.isArray(stored.additionalOwners) ? stored.additionalOwners : [], + assistantPrompts: stored.assistantPrompts || {} + }; +} + +export function saveAutomationData(data) { + writeJson(AUTOMATIONS_FILE, data); +} + +export function getConfig() { + const stored = readJson(CONFIG_FILE, {}); + return { + ...stored, + nvidia_model: isKnownNvidiaModel(stored.nvidia_model) ? stored.nvidia_model : DEFAULT_NVIDIA_MODEL, + apikey_vex: process.env.VEX_API_KEY || stored.apikey_vex || '', + site_vex: process.env.VEX_SITE || stored.site_vex || '', + upload_github_token: process.env.UPLOAD_GITHUB_TOKEN || stored.upload_github_token || '', + upload_github_repo: process.env.UPLOAD_GITHUB_REPO || stored.upload_github_repo || 'uploadsnew/uploads' + }; +} + +export function normalizeCommand(command) { + return String(command || '') + .trim() + .replace(/^[!./#]+/, '') + .toLowerCase(); +} + +export function normalizeIdentity(value) { + const raw = String(value || '').trim().replace(/^@/, ''); + if (!raw) return ''; + if (raw.includes('@')) return raw.split(':')[0].toLowerCase(); + const digits = raw.replace(/\D/g, ''); + return digits || raw.toLowerCase(); +} + +export function identityAliases(value) { + const normalized = normalizeIdentity(value); + if (!normalized) return new Set(); + const aliases = new Set([normalized]); + const base = normalized.split('@')[0].split(':')[0].replace(/\D/g, ''); + if (base) aliases.add(base); + if (/^\d+$/.test(normalized)) { + aliases.add(`${normalized}@s.whatsapp.net`); + aliases.add(`${normalized}@lid`); + } + return aliases; +} + +export function identitiesMatch(left, right) { + const leftAliases = identityAliases(left); + const rightAliases = identityAliases(right); + return [...leftAliases].some(alias => rightAliases.has(alias)); +} + +export function unwrapMessageContent(message) { + let current = message?.message || message || null; + const wrappers = [ + 'ephemeralMessage', + 'viewOnceMessage', + 'viewOnceMessageV2', + 'viewOnceMessageV2Extension', + 'documentWithCaptionMessage', + 'editedMessage' + ]; + + for (let depth = 0; depth < 8 && current; depth++) { + const wrapper = wrappers.find(key => current?.[key]?.message); + if (!wrapper) break; + current = current[wrapper].message; + } + return current; +} + +export function contextInfoFromContent(content) { + return content?.extendedTextMessage?.contextInfo + || content?.imageMessage?.contextInfo + || content?.videoMessage?.contextInfo + || content?.audioMessage?.contextInfo + || content?.documentMessage?.contextInfo + || content?.stickerMessage?.contextInfo + || null; +} diff --git a/docs/configuracao-da-instancia.md b/docs/configuracao-da-instancia.md new file mode 100644 index 0000000..8a654a2 --- /dev/null +++ b/docs/configuracao-da-instancia.md @@ -0,0 +1,280 @@ +# Configuração da sua instância do SHOGUN + +Este guia responde três perguntas antes que você precise abrir código: + +1. **quem é o dono desta instalação?** +2. **o que é obrigatório para o bot iniciar?** +3. **quais chaves de API só são necessárias para recursos opcionais?** + +O objetivo é que uma instalação nova seja previsível. Nada de descobrir uma +credencial escondida depois de quinze minutos encarando um stack trace como se +ele fosse um oráculo. + +--- + +## 1. Quem é o “dono” do bot? + +Quando você clona o SHOGUN e executa `npm run setup`, o número informado em +`numerodono` define o **dono principal daquela instância**. + +```text +Projeto SHOGUN +│ +├── autoria e créditos do projeto +│ └── continuam os mesmos em qualquer clone +│ +└── sua instalação + │ + ├── dono principal da instância + │ └── seu número configurado em numerodono + │ + ├── sessão WhatsApp desta instalação + │ + └── grupos onde o bot participa + └── administradores do grupo ≠ dono principal da instância +``` + +### Em termos simples + +- **Dono principal da instância:** pessoa que controla esta cópia do bot e pode + usar comandos reservados ao dono. +- **Administrador de grupo:** cargo concedido pelo próprio WhatsApp dentro de um + grupo. Não transforma alguém em dono da instância. +- **Autor/criador do projeto:** crédito de quem criou ou desenvolveu o software. + Clonar o projeto não altera autoria. + +O número do dono deve incluir país + DDD + número e conter somente dígitos. +Exemplo de formato brasileiro: `55DDDNUMERO`. + +A configuração fica em `dados/src/config.json`. Esse arquivo é local e não deve +ser enviado ao GitHub. + +--- + +## 2. O que é obrigatório para o SHOGUN funcionar? + +### Núcleo do bot + +Para iniciar o núcleo e conectar ao WhatsApp, você precisa de: + +- Node.js compatível; +- npm; +- Git; +- FFmpeg; +- `dados/src/config.json`, criado pelo setup; +- uma sessão WhatsApp criada por QR Code ou código de pareamento. + +**Nenhuma chave de API é obrigatória apenas para o bot iniciar.** Recursos que +dependem de serviços externos ficam disponíveis conforme você os configura. + +Execute: + +```bash +npm run preflight +``` + +O diagnóstico informa o que está pronto sem imprimir seus segredos. + +--- + +## 3. `.env.local`: onde ficam as opções privadas + +O SHOGUN carrega configurações privadas de: + +```text +.env.local +``` + +O repositório fornece um modelo seguro: + +```text +.env.example +``` + +Os instaladores oficiais criam `.env.local` a partir do exemplo quando ele +não existe. Se estiver fazendo o processo manualmente: + +### Linux, macOS ou Termux + +```bash +cp -n .env.example .env.local +chmod 600 .env.local +``` + +### Windows PowerShell + +```powershell +if (-not (Test-Path .env.local)) { Copy-Item .env.example .env.local } +``` + +Nunca publique `.env.local`. + +--- + +## 4. Matriz de integrações e credenciais + +| Recurso | Precisa de chave? | Configuração | Quando usar | +| --- | --- | --- | --- | +| Núcleo + WhatsApp | **não** | setup + sessão | sempre | +| BunnyFy | sim, se ativada | `BUNNYFY_ENABLED`, `BUNNYFY_BASE_URL`, `BUNNYFY_API_TOKEN` | capacidades servidas pela BunnyFy | +| Assistente via BunnyFy | chave BunnyFy | `BUNNYFY_AI_MODE=exclusive` | IA totalmente pela BunnyFy | +| Assistente BunnyFy + fallback NVIDIA | BunnyFy + NVIDIA | `BUNNYFY_AI_MODE=primary`, `NVIDIA_API_KEY` | BunnyFy primeiro, NVIDIA direta se houver falha transitória | +| Assistente NVIDIA direta | NVIDIA | `BUNNYFY_AI_MODE=off`, `NVIDIA_API_KEY` | IA sem BunnyFy | +| Transcrição via BunnyFy | chave BunnyFy | BunnyFy ativa + modo correspondente | caminho recomendado quando disponível | +| Transcrição VEX legado | VEX | `VEX_API_KEY`, `VEX_SITE` | somente quando o fallback legado for usado | +| Upload GitHub legado | GitHub | `UPLOAD_GITHUB_TOKEN`, `UPLOAD_GITHUB_REPO` | somente nos fluxos legados que ainda dependam dele | + +### Regra importante sobre BunnyFy + +A BunnyFy é uma plataforma separada. Se uma instância do SHOGUN usa a BunnyFy, +o bot recebe **somente uma credencial de consumidor da BunnyFy**. + +Chaves internas de provedores usados pela BunnyFy, como NVIDIA ou outros +serviços da infraestrutura da API, pertencem ao operador da BunnyFy e **não +devem ser copiadas para o bot**. + +--- + +## 5. Modos BunnyFy + +A chave mestra é: + +```env +BUNNYFY_ENABLED=false +``` + +Com `false`, as capacidades BunnyFy ficam desligadas e os caminhos legados ou +locais continuam sendo usados quando existirem. + +Ao ativar: + +```env +BUNNYFY_ENABLED=true +BUNNYFY_BASE_URL=https://SUA-BUNNYFY.example +BUNNYFY_API_TOKEN=SUA_CREDENCIAL_DE_CONSUMIDOR +``` + +Cada família usa um modo: + +```text +off -> não usa BunnyFy nessa capacidade +primary -> tenta BunnyFy; em falha transitória usa fallback quando houver +exclusive -> usa BunnyFy; não cai para o provider legado +``` + +As famílias atuais incluem IA, YouTube, imagens, stickers, canvas, logos, +geração visual, Tavern e NEXO. O `.env.example` é a referência dos nomes +exatos disponíveis nesta versão. + +> Não ative `exclusive` em uma capacidade antes de confirmar que a BunnyFy +> configurada oferece aquele contrato. + +--- + +## 6. Configurações da IA + +### Opção A — IA totalmente pela BunnyFy + +```env +BUNNYFY_ENABLED=true +BUNNYFY_AI_MODE=exclusive +BUNNYFY_BASE_URL=https://SUA-BUNNYFY.example +BUNNYFY_API_TOKEN=SUA_CREDENCIAL +NVIDIA_API_KEY= +``` + +O bot não precisa conhecer a chave do provedor de IA da BunnyFy. + +### Opção B — NVIDIA direta + +```env +BUNNYFY_ENABLED=false +NVIDIA_API_KEY=SUA_CHAVE_NVIDIA +``` + +O modelo pode ser escolhido pelos mecanismos do próprio bot. + +### Opção C — BunnyFy com fallback NVIDIA + +```env +BUNNYFY_ENABLED=true +BUNNYFY_AI_MODE=primary +BUNNYFY_BASE_URL=https://SUA-BUNNYFY.example +BUNNYFY_API_TOKEN=SUA_CREDENCIAL +NVIDIA_API_KEY=SUA_CHAVE_NVIDIA +``` + +Aqui a chave NVIDIA é necessária para o fallback direto realmente funcionar. + +--- + +## 7. VEX e integrações legadas + +O SHOGUN ainda preserva alguns fallbacks para não remover recursos antes de uma +substituição comprovada. + +Para o fallback VEX de transcrição: + +```env +VEX_API_KEY= +VEX_SITE= +``` + +Deixe vazio se você não usa esse caminho. + +Da mesma forma, `UPLOAD_GITHUB_TOKEN` não é requisito geral do bot. Só configure +credenciais legadas quando você souber qual recurso as consome. + +--- + +## 8. Como verificar sem vazar segredo + +Use: + +```bash +npm run preflight +``` + +E, quando estiver configurando NVIDIA direta: + +```bash +npm run test:nvidia:live +``` + +O preflight deve mostrar **presença/ausência e modo**, nunca o valor de tokens. + +Nunca cole em issue, print, vídeo ou pedido de suporte: + +- `.env.local`; +- `dados/src/config.json`; +- `dados/database/qr-code/`; +- tokens e chaves; +- cookies; +- URLs assinadas. + +--- + +## 9. Sequência recomendada para uma instalação nova + +```text +1. instalar Git + Node + FFmpeg + ↓ +2. clonar o repositório + ↓ +3. executar o instalador da sua plataforma + ↓ +4. informar o dono principal da instância + ↓ +5. rodar npm run preflight + ↓ +6. iniciar com npm start + ↓ +7. conectar o WhatsApp + ↓ +8. testar !menu + ↓ +9. só então ativar APIs opcionais que você realmente quiser +``` + +Isso mantém o primeiro boot simples e deixa integrações externas como extensão, +não como pedágio para chegar ao menu. diff --git a/docs/instalacao/linux.md b/docs/instalacao/linux.md index 44557af..e991672 100644 --- a/docs/instalacao/linux.md +++ b/docs/instalacao/linux.md @@ -1,19 +1,31 @@ -# SHOGUN no Linux: do terminal ao primeiro menu +

🐧 SHOGUN no Linux

+

Instalação em desktop ou servidor, com cada etapa verificável.

-Este guia atende computadores e servidores Linux. Se é sua primeira vez, -Ubuntu ou Debian oferecem o caminho mais simples. Reserve cerca de 20 minutos. +

+ Linux + Node 20.19+ + Tempo +

-## Etapa 1 — abrir o Terminal +## Rota completa -No Ubuntu, pressione `Ctrl+Alt+T`. Em outras distribuições, procure o aplicativo -**Terminal** no menu. Copie uma caixa por vez, cole com `Ctrl+Shift+V` e -pressione Enter. +| Etapa | Ação | Sinal de sucesso | +| --- | --- | --- | +| **1** | instalar Git e FFmpeg | versões aparecem no terminal | +| **2** | instalar Node.js | `node --version` ≥ 20.19 | +| **3** | clonar o SHOGUN | pasta `~/shogun` criada | +| **4** | executar instalador | setup concluído | +| **5** | rodar preflight | requisitos obrigatórios aprovados | +| **6** | conectar WhatsApp | feed do bot ativo | +| **7** | testar | `!menu` responde | -## Etapa 2 — instalar Git e FFmpeg +--- -Use somente o bloco da sua distribuição. +## 1 · Prepare o sistema -### Ubuntu, Debian, Linux Mint e derivados +Use **somente** o bloco correspondente à sua distribuição. + +### Ubuntu · Debian · Linux Mint ```bash sudo apt update @@ -32,161 +44,230 @@ sudo dnf install -y git ffmpeg curl sudo pacman -Syu --needed git ffmpeg curl ``` -Quando `sudo` pedir a senha, digite mesmo que nenhum caractere apareça e -pressione Enter. Isso é uma proteção normal do Linux. +> [!NOTE] +> Quando `sudo` pede a senha, nenhum caractere aparece enquanto você digita. Isso é normal. O terminal não travou, ele só decidiu que feedback visual era luxo. -## Etapa 3 — instalar Node.js LTS +--- -O SHOGUN exige Node.js 20.19 ou mais recente e recomenda a linha LTS 22 ou 24. -Se sua distribuição já oferece uma dessas versões, instale `nodejs` e `npm` -pelo gerenciador de pacotes dela. Caso contrário, siga o método mostrado na -página oficial [Baixar Node.js](https://nodejs.org/en/download). +## 2 · Instale Node.js -Não use Node.js 18. Depois de instalar, feche e abra o Terminal. +O SHOGUN exige **Node.js 20.19.0 ou superior**. Para uma instalação nova, prefira uma linha LTS atual suportada pelo projeto. -## Etapa 4 — conferir o terreno +Se sua distribuição já oferece uma versão adequada, instale `nodejs` e `npm` pelo gerenciador de pacotes. Caso contrário, use o método indicado na página oficial do Node.js. -Rode um por vez: +Depois confira: ```bash node --version -``` - -```bash npm --version -``` - -```bash git --version -``` - -```bash ffmpeg -version ``` -Cada comando deve mostrar uma versão. Para Node.js, espere algo como `v22...` -ou `v24...`. +Se Node mostrar `v18`, ainda não terminou esta etapa. -## Etapa 5 — baixar o SHOGUN +--- -Volte à sua pasta pessoal: +## 3 · Baixe o projeto ```bash -cd +cd ~ +git clone https://github.com/dgreych/shogun.git +cd shogun ``` -Baixe o projeto: +Confira: ```bash -git clone https://github.com/dgreych/shogun.git +pwd ``` -Entre na pasta: +O caminho deve terminar em `/shogun`. -```bash -cd shogun -``` +--- -## Etapa 6 — preparar e configurar +## 4 · Execute o instalador ```bash bash scripts/install-linux.sh ``` -O instalador baixa os componentes e faz quatro perguntas: +O instalador cria `.env.local` a partir de `.env.example` quando necessário, +executa o preflight, instala as dependências travadas em `package-lock.json` e +abre a configuração inicial. Um `.env.local` que já exista é preservado. + + + + + + +
Seu nomeidentificação do dono principal desta instância
Númeropaís + DDD + número do dono principal, somente dígitos
Nome do botnome desta instalação
Prefixopor exemplo !
+ +### O dono desta instalação -1. como o SHOGUN deve chamar você; -2. seu número com país e DDD, somente dígitos — exemplo fictício - `5511999999999`; -3. nome do bot — pressione Enter para manter `SHOGUN`; -4. prefixo — pressione Enter para manter `!`. +```text +projeto SHOGUN + └── sua instalação Linux + ├── dono principal -> numerodono + ├── sessão WhatsApp + └── grupos -> administradores próprios +``` -Quando aparecer **SHOGUN pronto**, a configuração local está protegida e a -instalação terminou. Para refazer apenas as perguntas: +`numerodono` identifica quem controla **esta instância** e pode usar comandos de +dono. Não altera a autoria do projeto e não transforma esse número no +administrador de todos os grupos. + +Para refazer apenas o setup depois: ```bash npm run setup ``` -## Etapa 7 — inspeção e conexão +A matriz de BunnyFy, NVIDIA, VEX e demais opções está em +**[Configuração da sua instância](../configuracao-da-instancia.md)**. + +--- + +## 5 · Verifique antes de iniciar ```bash npm run preflight ```

- Saída da verificação do ambiente + Preflight do SHOGUN no Linux

-Imagem gerada da execução real do comando. O aviso amarelo sobre -configuração é esperado antes da etapa seguinte. +O comando verifica Node.js, npm, Git, FFmpeg, configuração, dono principal, +dependências e o estado das integrações sem revelar tokens. + +> [!NOTE] +> O núcleo não precisa de chave de API para chegar ao primeiro `!menu`. Avisos +> sobre integrações opcionais só significam que aquele recurso específico ainda +> não foi configurado. -Com os itens obrigatórios aprovados, inicie: +--- + +## 6 · Inicie e conecte ```bash npm start ``` -Escolha `1` para QR Code. No telefone, abra WhatsApp → menu de três pontos → -**Aparelhos conectados/Dispositivos conectados → Conectar um aparelho** e leia -o QR do terminal. +Escolha **QR Code** ou **código de pareamento** no painel. + +

+ Painel de conexão do SHOGUN +

+ +No telefone, para QR, abra **WhatsApp → Aparelhos conectados → Conectar um aparelho**. Se QR não for conveniente, reinicie o fluxo e escolha pareamento por código. + +A sessão criada é reutilizada nas próximas inicializações enquanto continuar válida. -Se o WhatsApp estiver no mesmo equipamento ou o QR não couber, reinicie com -`Ctrl+C`, escolha `2` e use o código de pareamento. +--- -## Etapa 8 — confirmar a primeira missão +## 7 · Teste de aceite -Numa conversa de teste, envie: +Envie em um grupo de teste: ```text !menu ``` -Se o menu chegou, o posto está pronto. O Terminal precisa permanecer aberto -enquanto o SHOGUN estiver em serviço. +Se escolheu outro prefixo, substitua `!`. -## Sua rotina +**Recebeu resposta?** O núcleo instalou, conectou e está despachando comandos. -Para parar, pressione `Ctrl+C`. +--- -Para voltar outro dia: +## APIs opcionais -```bash -cd ~/shogun -``` +Depois do primeiro `!menu`, edite `.env.local` apenas se quiser capacidades +externas. Resumo: + +- núcleo + WhatsApp: sem chave de API; +- BunnyFy: `BUNNYFY_ENABLED=true`, URL e credencial de consumidor; +- NVIDIA direta: `NVIDIA_API_KEY` para IA direta ou fallback; +- VEX legado: `VEX_API_KEY` + `VEX_SITE` somente quando esse fallback for usado. + +Veja **[Configuração da sua instância](../configuracao-da-instancia.md)** antes +de ativar modos `primary` ou `exclusive`. + +--- + +## Uso diário + +### Parar + +`Ctrl+C` + +### Iniciar de novo ```bash +cd ~/shogun npm start ``` -Para atualizar, pare o processo e rode: +### Atualizar ```bash +cd ~/shogun git pull --ff-only -``` - -```bash npm ci --no-audit --no-fund -``` - -```bash +npm run preflight npm start ``` -## Servidor ligado continuamente +> [!TIP] +> Em servidor que ficará ligado continuamente, faça primeiro a instalação manual e confirme `!menu`. Só depois configure supervisão de processo. Isso separa problema de instalação de problema de serviço, uma pequena gentileza para o seu futuro eu. + +Veja **[Implantação em servidor](../../DEPLOY.md)** para uma instalação pública supervisionada. + +--- + +## Diagnóstico + +
+sudo: command not found +
+Sua distribuição usa outro método de administração ou você está num ambiente restrito. Instale Git, Node.js e FFmpeg pelo mecanismo adequado ao sistema antes de continuar. +
+ +
+Node ainda mostra v18 +
+Instale uma versão compatível e abra um novo terminal. Depois confirme com node --version e npm run preflight. +
+ +
+Permissão negada no instalador +
+Use bash scripts/install-linux.sh. Não é necessário marcar o arquivo executável para esse fluxo. +
+ +
+A pasta shogun já existe +
+Entre nela com cd ~/shogun; não clone outra cópia por cima. +
+ +--- + +## 🔐 Proteja o estado local + +A sessão e a configuração devem sobreviver às atualizações: + +```text +dados/database/ +dados/src/config.json +.env.local +``` -Primeiro conclua a instalação manual e confirme `!menu`. Depois consulte -[Implantação em servidor](../../DEPLOY.md) para manter o processo supervisionado. -Não publique a pasta de sessão nem o arquivo de configuração ao mover o bot. +> [!CAUTION] +> Nunca publique esses arquivos. A sessão contém material de autenticação do +> WhatsApp e os arquivos de configuração podem conter informações privadas ou +> credenciais. -## Socorro rápido +Para diagnóstico adicional, rode `npm run preflight` e consulte **[Solução de problemas](../solucao-de-problemas.md)**. -- **`sudo: command not found`:** sua distribuição usa outro método de - administração; consulte a documentação dela ou peça ao administrador para - instalar Git, Node.js e FFmpeg. -- **Node mostra `v18`:** atualize para Node.js 22 ou 24 e reabra o Terminal. -- **Permissão negada no instalador:** rode com `bash scripts/install-linux.sh`, - exatamente como na etapa 6. -- **A pasta `shogun` já existe:** use `cd ~/shogun`; não clone por cima. -- **Ainda não funcionou:** rode `npm run preflight` e consulte - [solução de problemas](../solucao-de-problemas.md). +

← Voltar à página principal

\ No newline at end of file diff --git a/docs/instalacao/macos.md b/docs/instalacao/macos.md new file mode 100644 index 0000000..3bbcf1a --- /dev/null +++ b/docs/instalacao/macos.md @@ -0,0 +1,198 @@ +

🍎 SHOGUN no macOS

+

Intel ou Apple Silicon, do Terminal ao WhatsApp conectado.

+ +

+ macOS + Node 20.19+ + Tempo +

+ +## Rota completa + +| Etapa | Ação | Sinal de sucesso | +| --- | --- | --- | +| **1** | instalar requisitos | versões aparecem no Terminal | +| **2** | clonar o SHOGUN | pasta `~/shogun` criada | +| **3** | executar instalador | setup concluído | +| **4** | rodar preflight | requisitos obrigatórios aprovados | +| **5** | conectar WhatsApp | feed ativo | +| **6** | testar | `!menu` responde | + +--- + +## 1 · Prepare o Mac + +Se usa Homebrew: + +```bash +brew update +brew install node git ffmpeg +``` + +Confirme: + +```bash +node --version +npm --version +git --version +ffmpeg -version +``` + +> [!IMPORTANT] +> O SHOGUN exige **Node.js 20.19.0 ou superior**. + +Se `brew` não existir, instale os requisitos pelos distribuidores oficiais antes de continuar. + +--- + +## 2 · Baixe o projeto + +```bash +cd ~ +git clone https://github.com/dgreych/shogun.git +cd shogun +``` + +Se a pasta já existir, use `cd ~/shogun` em vez de clonar novamente. + +--- + +## 3 · Execute o instalador + +```bash +bash scripts/install-macos.sh +``` + +O script cria `.env.local` a partir de `.env.example` se necessário, executa o +preflight, instala as dependências travadas pelo `package-lock.json`, abre o +setup e verifica as entradas principais do runtime. Um `.env.local` existente +é preservado. + + + + + + +
Seu nomeidentificação do dono principal desta instância
Númeropaís + DDD + número do dono principal, somente dígitos
Nome do botnome desta instalação
Prefixopor exemplo !
+ +### Quem é o dono principal? + +```text +projeto SHOGUN + └── sua instalação no macOS + ├── dono principal -> numerodono + ├── sessão WhatsApp + └── grupos -> administradores próprios +``` + +O número salvo pelo setup controla **esta instância** e seus comandos de dono. +Ele não altera a autoria do projeto e não é a mesma coisa que ser administrador +de um grupo. + +Veja **[Configuração da sua instância](../configuracao-da-instancia.md)** para a +matriz de integrações e credenciais opcionais. + +--- + +## 4 · Faça o preflight + +```bash +npm run preflight +``` + +Esse comando confere os requisitos, a configuração do dono e o estado das +integrações sem mostrar tokens. Avisos sobre APIs opcionais não impedem o núcleo +quando esses recursos não estão sendo usados. + +> [!NOTE] +> Nenhuma chave de API é necessária apenas para chegar ao primeiro `!menu`. + +--- + +## 5 · Inicie e conecte + +```bash +npm start +``` + +O painel oferece **QR Code** ou **código de pareamento**. + +

+ Painel de conexão do SHOGUN +

+ +Para QR Code, no telefone abra **WhatsApp → Aparelhos conectados → Conectar um aparelho**. Depois que a sessão for criada, os próximos boots reutilizam a sessão enquanto ela permanecer válida. + +--- + +## 6 · Teste + +Em um grupo de teste: + +```text +!menu +``` + +Se escolheu outro prefixo, use-o no lugar de `!`. + +--- + +## APIs opcionais + +Depois de confirmar o núcleo, edite `.env.local` apenas para os recursos que +quiser ativar: + +- BunnyFy: URL + credencial de consumidor e modos desejados; +- NVIDIA direta: `NVIDIA_API_KEY` para IA direta/fallback; +- VEX legado: `VEX_API_KEY` + `VEX_SITE` somente quando usado. + +Detalhes: **[Configuração da sua instância](../configuracao-da-instancia.md)**. + +--- + +## Uso diário + +### Iniciar novamente + +```bash +cd ~/shogun +npm start +``` + +### Atualizar + +```bash +cd ~/shogun +git pull --ff-only +npm ci --no-audit --no-fund +npm run preflight +npm start +``` + +### Diagnosticar + +```bash +cd ~/shogun +npm run preflight +``` + +--- + +## 🔐 Proteja a instalação + +Arquivos privados: + +```text +.env.local +dados/src/config.json +dados/database/qr-code/ +``` + +> [!CAUTION] +> Não publique, envie ou compartilhe esses arquivos. A sessão contém material +> de autenticação da conta conectada e as configurações podem conter dados +> privados ou credenciais. + +Para problemas adicionais, consulte **[Solução de problemas](../solucao-de-problemas.md)**. + +

← Voltar à página principal

\ No newline at end of file diff --git a/docs/instalacao/termux.md b/docs/instalacao/termux.md index de5f90d..1c49575 100644 --- a/docs/instalacao/termux.md +++ b/docs/instalacao/termux.md @@ -1,422 +1,321 @@ -# Instalar o 𝖘𝖍𝖔𝖌𝖚𝖓 no Android +

📱 SHOGUN no Android

+

Instalação pelo Termux, do zero ao primeiro !menu.

-Este guia começa do zero. Você não precisa conhecer programação nem ter usado -um terminal antes. Faça uma etapa por vez e só passe para a próxima quando vir -o resultado indicado. - -> **Tempo da primeira instalação:** normalmente 15 a 35 minutos. Use Wi-Fi, -> deixe o aparelho carregando e reserve pelo menos 2 GB livres. - -### Como ler este guia - -Cada etapa tem um bloco assim, mostrando o que deve aparecer na sua tela: +

+ Android + Node 20.19+ + Tempo +

-> ✅ **Deu certo se você vir:** uma descrição do que a tela mostra quando o -> passo funcionou. -> -> ⚠️ **Se aparecer outra coisa:** o erro mais comum daquele passo e o que fazer. +> [!TIP] +> Faça a primeira instalação no Wi‑Fi, com o aparelho carregando e pelo menos **2 GB livres**. O bot não exige Termux:API para manter o wake lock. -**Copie e cole um comando por vez.** Espere ele terminar antes do próximo — a -linha só volta a aceitar digitação quando o anterior acabou. Se a tela ficar -parada com texto correndo, está trabalhando: aguarde. +

+ Preflight do SHOGUN no Termux +

-## O que você precisa antes de começar +## Rota completa -Você vai precisar de: +| Etapa | O que acontece | Você sabe que deu certo quando… | +| --- | --- | --- | +| **1** | instalar o Termux | aparece o terminal com `$` | +| **2** | atualizar pacotes | o prompt volta sem erro | +| **3** | clonar o SHOGUN | `pwd` termina em `/shogun` | +| **4** | rodar o instalador | aparece a configuração do bot | +| **5** | manter o processo acordado | `termux-wake-lock` não retorna erro | +| **6** | conectar o WhatsApp | o feed do SHOGUN começa a rodar | +| **7** | testar | `!menu` recebe resposta | -- um aparelho com Android 7 ou mais recente; -- WhatsApp funcionando no número que será conectado; -- internet estável durante a instalação; -- o navegador do celular; -- de preferência, um aparelho reserva para deixar o bot ligado direto. +--- -O 𝖘𝖍𝖔𝖌𝖚𝖓 funciona como um aparelho conectado à sua conta. Quando possível, -comece com um número e um grupo de testes antes de colocá-lo numa comunidade. +## 1 · Instale o Termux -## Etapa 1 — baixar o Termux verdadeiro +Use preferencialmente uma das fontes oficiais da linha tradicional: -O Termux é o aplicativo que abre a linha de comando onde o bot vai rodar. -Não use cópias encontradas em sites de APK e não use a edição antiga da Play -Store. +- **[F-Droid](https://f-droid.org/packages/com.termux/)** +- **[GitHub Releases](https://github.com/termux/termux-app/releases)** -### Baixe o APK +> [!IMPORTANT] +> A edição do Google Play segue uma linha diferente e ainda pode variar em compatibilidade. Também não misture o aplicativo principal e plugins baixados de fontes diferentes, porque as assinaturas não combinam. -Toque no link e o download começa. Os dois servem; o do F-Droid costuma estar -numa versão mais nova. +Abra o Termux. Se você vê uma linha terminando em `$`, está no lugar certo. É só um terminal. Ele parece hostil porque terminais foram desenhados antes de alguém descobrir bordas arredondadas. -| Fonte | Link direto | Observação | -| --- | --- | --- | -| **F-Droid** (recomendado) | [Baixar Termux](https://f-droid.org/repo/com.termux_1022.apk) | Versão mais recente. Não precisa instalar a loja F-Droid | -| **GitHub oficial** | [Baixar Termux](https://github.com/termux/termux-app/releases/download/v0.118.3/termux-app_v0.118.3%2Bgithub-debug_universal.apk) | Arquivo `universal`, funciona em qualquer aparelho | +--- -Se algum link estiver fora do ar ou você quiser conferir se saiu versão nova, -as páginas oficiais são -[F-Droid](https://f-droid.org/packages/com.termux/) e -[GitHub Releases](https://github.com/termux/termux-app/releases). +## 2 · Atualize o ambiente -> ⚠️ **Não baixe o Termux da Play Store.** A versão de lá está congelada há -> anos e não funciona para isso. Também não use sites de APK genéricos. +```bash +pkg update -y && pkg upgrade -y +``` -### Instalar o arquivo baixado +**O que isso faz:** atualiza a lista de pacotes e instala as versões mais recentes disponíveis para o seu Termux. -1. Abra o arquivo baixado. O Android costuma mostrar a notificação de download - concluído — toque nela. -2. Vai aparecer um aviso de que o navegador não tem permissão para instalar - aplicativos. Toque em **Configurações**, ligue **Permitir desta fonte** e - volte com o botão de voltar. -3. Toque em **Instalar** e aguarde. -4. Se quiser deixar o aparelho mais fechado, volte às configurações e desligue - essa permissão depois da instalação. +Se surgir uma pergunta sobre arquivo de configuração que você nunca alterou, a opção padrão costuma ser suficiente: pressione **Enter**. -> Escolha uma fonte e permaneça nela. Termux, Termux:API e Termux:Boot precisam -> vir todos do F-Droid ou todos do GitHub. Misturar fontes causa erro de -> assinatura e o Android recusa a instalação dos complementos. +--- -> ✅ **Deu certo se você vir:** o Termux abrindo numa tela preta com um cursor -> piscando, e não uma tela de erro do Android. -> -> ⚠️ **Se o app não instalar:** quase sempre é a permissão de "instalar apps -> desconhecidos" desligada para o navegador. Volte ao passo da permissão. +## 3 · Baixe o SHOGUN -## Etapa 2 — conhecer a tela preta +```bash +cd ~ +git clone https://github.com/dgreych/shogun.git +cd shogun +``` -1. Abra o **Termux** pelo ícone recém-instalado. -2. Na primeira abertura, aguarde a linha de texto com um sinal `$` aparecer. - Esse sinal quer dizer: “pronto para receber um comando”. -3. Para colar um comando, mantenha o dedo pressionado na tela e toque em - **Paste/Colar**. Depois toque na tecla **Enter** do teclado. +Confira onde está: -Você sempre vai copiar **somente o conteúdo dentro da caixa**, uma caixa por -vez. Não copie o sinal `$`, números de etapa ou explicações. +```bash +pwd +``` -> ✅ **Deu certo se você vir:** uma linha terminada em `$` esperando você -> digitar. Esse `$` é o convite: quando ele aparece, o Termux está livre. -> -> ⚠️ **Se a tela ficar em branco:** toque nela uma vez. O teclado sobe e o -> cursor volta. +> [!NOTE] +> Se aparecer `destination path 'shogun' already exists`, não clone de novo. Use `cd ~/shogun`. -## Etapa 3 — atualizar o Termux +--- -Cole este primeiro comando e pressione Enter: +## 4 · Deixe o projeto preparar o aparelho ```bash -pkg update -y +bash scripts/install-termux.sh ``` -Várias linhas vão passar pela tela. Isso é normal. Aguarde até o `$` aparecer -novamente. Se o Termux perguntar qual configuração manter, aceite a opção -padrão pressionando Enter. +O instalador do repositório faz a parte tediosa em ordem previsível: -Agora instale a ferramenta que vai buscar o 𝖘𝖍𝖔𝖌𝖚𝖓: +1. atualiza o índice do Termux; +2. instala **Git, Node.js LTS, FFmpeg e termux-tools**; +3. cria `.env.local` a partir de `.env.example` se ainda não existir; +4. executa o preflight da plataforma; +5. instala as dependências travadas em `package-lock.json` com `npm ci`; +6. abre a configuração inicial. -```bash -pkg install -y git -``` +Um `.env.local` existente é preservado. APIs opcionais começam desligadas. -Espere o `$` voltar. +### O setup pergunta -> ✅ **Deu certo se você vir:** várias linhas correndo e, no fim, o `$` de -> volta sem nenhuma linha começando com `E:` ou `Error`. -> -> ⚠️ **Se pedir confirmação:** digite `y` e toque em Enter. É normal. -> -> ⚠️ **Se travar em "Waiting for headers":** sua rede está instável. Aguarde -> ou troque de Wi-Fi e rode o comando de novo — repetir não estraga nada. + + + + + +
Seu nomecomo o bot identifica o dono principal desta instância
Seu númeropaís + DDD + número do dono principal, somente dígitos
Nome do boto nome exibido pela instalação
Prefixopor exemplo !
-## Etapa 4 — baixar o 𝖘𝖍𝖔𝖌𝖚𝖓 +A configuração é gravada localmente em `dados/src/config.json`. -Cole: +### Quem é o dono principal? -```bash -git clone https://github.com/dgreych/shogun.git +```text +projeto SHOGUN + └── sua instalação no Android + ├── dono principal -> numerodono + ├── sessão WhatsApp + └── grupos -> administradores próprios ``` -Quando aparecer `done` e o `$` voltar, entre na pasta que acabou de chegar: +O número do setup controla **esta cópia do bot** e seus comandos de dono. Isso +não muda os créditos/autoria do projeto e não é a mesma coisa que ser +administrador de um grupo. -```bash -cd shogun -``` +Veja **[Configuração da sua instância](../configuracao-da-instancia.md)** para +entender APIs e credenciais antes de preencher qualquer chave. + +--- -O terminal não mostra uma animação ao entrar. Você pode confirmar o lugar com: +## 5 · Impeça o Android de dormir em cima do bot ```bash -pwd +termux-wake-lock ``` -O fim da linha deve ser `/shogun`. - -> ✅ **Deu certo se você vir:** uma pasta nova chamada `shogun`. Confirme -> digitando `ls` e Enter: o nome precisa aparecer na lista. -> -> ⚠️ **Se disser "already exists":** a pasta já foi baixada antes. Entre nela -> com `cd shogun` e siga. - -## Etapa 5 — preparar o bot +O wake lock vem de `termux-tools`. **Não é necessário instalar o aplicativo Termux:API só para isso.** -Cole: +Se o comando estiver ausente: ```bash -bash scripts/install-termux.sh +pkg install -y termux-tools +termux-wake-lock ``` -O instalador prepara Node.js, FFmpeg e os componentes do 𝖘𝖍𝖔𝖌𝖚𝖓. Pode parecer -parado durante alguns minutos; não feche o Termux. A instalação chegou ao ponto -certo quando aparecer o título **Quartel de configuração do 𝖘𝖍𝖔𝖌𝖚𝖓**. - -### Responder à configuração +Também retire o Termux da otimização agressiva de bateria do fabricante. Android adora matar exatamente o processo que você queria manter vivo e chamar isso de otimização. -O assistente faz quatro perguntas. Digite a resposta e pressione Enter em cada -uma: +--- -1. **Como o 𝖘𝖍𝖔𝖌𝖚𝖓 deve chamar você?** — por exemplo, `Mauricio`. -2. **Seu número com país e DDD** — somente dígitos. Exemplo fictício: - `5511999999999` (`55` do Brasil, DDD e número; sem `+`, espaço ou traço). -3. **Nome do bot** — pressione Enter para manter `𝖘𝖍𝖔𝖌𝖚𝖓`. -4. **Prefixo de comando** — pressione Enter para manter `!`. +## 6 · Verifique e conecte -Ao final, você deve ver **Configuração local salva** e **𝖘𝖍𝖔𝖌𝖚𝖓 pronto**. O -arquivo com esses dados fica apenas no aparelho e não entra no repositório. - -Se digitou algo errado, não reinstale tudo. Rode: +Antes do boot: ```bash -npm run setup +npm run preflight ``` -> ✅ **Deu certo se você vir:** as perguntas de configuração aparecendo uma a -> uma, e no fim uma mensagem de conclusão. -> -> ⚠️ **Se parar com erro de permissão:** feche o Termux por completo, abra de -> novo e repita a partir do `cd shogun`. +O diagnóstico verifica sistema, configuração, dono principal e integrações sem +imprimir tokens. Avisos sobre APIs opcionais não impedem o núcleo quando você +não usa aqueles recursos. -## Etapa 6 — conferir se está tudo certo - -Cole: +Depois: ```bash -npm run preflight +npm start ``` -Node.js, npm, Git e FFmpeg devem aparecer aprovados. Um aviso sobre -`termux-wake-lock` não impede o primeiro teste; cuidaremos disso depois. +O painel real da aplicação oferece: -> ✅ **Deu certo se você vir:** uma lista de verificações com marcas de -> aprovado, sem nenhuma linha vermelha. +- **QR Code**; +- **código de pareamento**; +- sair.

- Saída do comando de verificação, com Node.js, npm, Git e FFmpeg aprovados + Painel de conexão do SHOGUN

-Imagem gerada da execução real do comando. O aviso amarelo sobre -configuração é esperado antes da etapa seguinte. -> -> ⚠️ **Se o FFmpeg aparecer como ausente:** rode `pkg install ffmpeg -y` e -> repita a verificação. +### QR Code -## Etapa 7 — conectar o WhatsApp no mesmo celular - -Inicie o 𝖘𝖍𝖔𝖌𝖚𝖓: - -```bash -npm start -``` +No WhatsApp do número que será usado pelo bot, abra **Aparelhos conectados → Conectar um aparelho** e leia o QR mostrado no Termux. -Na primeira vez, o terminal oferece três opções. Como WhatsApp e Termux estão -no mesmo celular, digite `2` para **código de pareamento** e pressione Enter. -Quando ele pedir o telefone, informe novamente país + DDD + número, somente -dígitos. +### Código de pareamento -Um código curto aparecerá no terminal. Anote-o ou copie-o antes de sair da tela. -Agora: +Escolha a opção correspondente. O SHOGUN usa o número salvo no setup e pede ao WhatsApp um código para ser informado no fluxo de aparelhos conectados. -1. abra o WhatsApp sem encerrar o Termux; -2. toque no menu de três pontos; -3. abra **Aparelhos conectados** ou **Dispositivos conectados**; -4. toque em **Conectar um aparelho**; -5. escolha **Conectar com número de telefone**; -6. digite o código mostrado pelo 𝖘𝖍𝖔𝖌𝖚𝖓. +> [!TIP] +> Depois que a sessão é criada, inicializações futuras reutilizam essa sessão enquanto ela permanecer válida. Novo QR em todo boot não é comportamento normal. -Volte ao Termux. Aguarde a confirmação de conexão. Na próxima abertura, essa -sessão será reconhecida automaticamente e não será necessário parear de novo. +--- -### Se o WhatsApp estiver em outro aparelho +## 7 · Faça o teste que importa -Digite `1` para usar QR Code. No aparelho que tem o WhatsApp, abra **Aparelhos -conectados → Conectar um aparelho** e leia o QR mostrado no Termux. - -> ✅ **Deu certo se você vir:** a palavra **CONECTADO** na tela e, logo -> depois, uma mensagem chegando no WhatsApp do dono. - -

- Terminal mostrando o bot iniciando e detectando a sessão -

+Quando o feed aparecer:

- Terminal mostrando a conexão estabelecida + SHOGUN conectado no Termux

-> -> ⚠️ **Se o QR sumir antes de você ler:** ele expira em segundos e é gerado de -> novo sozinho. Deixe a câmera pronta antes de olhar a tela. -> -> ⚠️ **Se pedir o QR toda vez que reinicia:** a sessão não está sendo salva. -> Confirme que você não apagou a pasta `dados/database`. - -## Etapa 8 — o primeiro comando -Com o 𝖘𝖍𝖔𝖌𝖚𝖓 conectado, abra uma conversa de teste no WhatsApp e envie: +Envie em um grupo de teste: ```text !menu ``` -Se o menu chegou, a instalação está concluída. Antes de dar cargo de -administrador, teste comandos simples e leia o guia de -[primeiros passos](../primeiros-passos.md). +Se escolheu outro prefixo, troque `!` por ele. -> ✅ **Deu certo se você vir:** o bot respondendo o `!menu` no grupo, com a -> lista de comandos. -> -> ⚠️ **Se ele não responder:** confirme que está no grupo, que o prefixo é o -> mesmo que você configurou, e que a tela do Termux ainda mostra o bot ligado. +**Recebeu resposta?** Instalação, conexão e roteamento básico estão funcionando. -## Etapa 9 — impedir que o Android desligue o bot +--- -O Android economiza bateria fechando aplicativos em segundo plano. Para o -𝖘𝖍𝖔𝖌𝖚𝖓 permanecer conectado, faça as duas proteções abaixo. +## APIs opcionais -### Retirar a otimização de bateria +Você não precisa de uma chave de API para chegar ao primeiro `!menu`. -Nas configurações do Android, procure **Aplicativos → Termux → Bateria** e -escolha algo como **Sem restrições**, **Não otimizar** ou **Permitir atividade -em segundo plano**. O nome muda conforme a marca do celular. +Depois que o núcleo estiver funcionando, edite `.env.local` somente para os +recursos que quiser ativar: -### Ativar o bloqueio de suspensão +- BunnyFy: URL + credencial de consumidor; +- NVIDIA direta: `NVIDIA_API_KEY`; +- VEX legado: `VEX_API_KEY` + `VEX_SITE`. -Instale o aplicativo **Termux:API** pela mesma fonte usada para o Termux. Depois -abra o Termux e rode: +A matriz completa e os modos `off`, `primary` e `exclusive` estão em +**[Configuração da sua instância](../configuracao-da-instancia.md)**. -```bash -pkg install -y termux-api -``` +--- -Ative a proteção: +## Da próxima vez ```bash +cd ~/shogun termux-wake-lock +npm start ``` -Se o Android pedir confirmação, permita. Para liberar a proteção quando o bot -estiver parado, use: +## Atualizar sem destruir a sessão + +Pare o processo com `Ctrl + C` e execute: ```bash -termux-wake-unlock +cd ~/shogun +git pull --ff-only +npm ci --no-audit --no-fund +npm run preflight +npm start ``` -## Sua rotina depois da instalação - -### Abrir o 𝖘𝖍𝖔𝖌𝖚𝖓 outro dia +`npm ci` respeita o lockfile do projeto. O diretório de sessão não deve ser apagado durante uma atualização normal. -Abra o Termux e use, um por vez: +--- -```bash -cd shogun -``` +## Diagnóstico rápido ```bash -termux-wake-lock -``` - -```bash -npm start +cd ~/shogun +npm run preflight ``` -### Parar com segurança +O preflight verifica **Node.js, npm, Git, FFmpeg, Termux, configuração, dono da +instância, dependências locais e estado das integrações opcionais**. -Volte ao Termux e pressione `Ctrl+C`. Na fileira extra do Termux, toque em -`CTRL` e depois na letra `C`. Quando o `$` reaparecer, o processo parou. - -### Atualizar o 𝖘𝖍𝖔𝖌𝖚𝖓 - -Pare o bot com `Ctrl+C`, confirme que está na pasta `shogun` e rode: +
+Node.js está abaixo de 20.19 +
```bash -git pull --ff-only +pkg update -y +pkg upgrade -y +pkg install -y nodejs-lts +node --version ``` +
-```bash -npm ci --no-audit --no-fund -``` +
+FFmpeg não existe +
```bash -npm start +pkg install -y ffmpeg +ffmpeg -version ``` +
-Não apague `dados/database/qr-code/`: essa pasta contém a sessão conectada. - -## Opcional — iniciar após reiniciar o celular - -Só faça isto depois de ter iniciado manualmente, conectado o WhatsApp e -confirmado `!menu`. - -1. Instale **Termux:Boot** pela mesma fonte usada para o Termux. -2. Toque uma vez no ícone **Termux:Boot**. Essa primeira abertura autoriza o - complemento a agir no próximo reinício. -3. Volte ao Termux e instale o editor usado nesta etapa: +
+npm ci falhou depois de uma instalação antiga +
```bash -pkg install -y nano +cd ~/shogun +rm -rf node_modules +npm ci --no-audit --no-fund ``` +
-4. Crie a pasta: +
+Quero refazer a configuração +
```bash -mkdir -p ~/.termux/boot +cd ~/shogun +npm run setup ``` +
-5. Abra o arquivo: +--- -```bash -nano ~/.termux/boot/start-shogun -``` +## 🔐 A parte que você não deve mandar para ninguém -6. Cole exatamente: +Arquivos privados desta instalação: -```bash -#!/data/data/com.termux/files/usr/bin/bash -termux-wake-lock -cd "$HOME/shogun" -npm start >> "$HOME/shogun/termux-boot.log" 2>&1 +```text +.env.local +dados/src/config.json +dados/database/qr-code/ ``` -7. Salve tocando em `CTRL`, depois `O`, Enter, `CTRL` e `X`. -8. Torne o arquivo executável: +> [!CAUTION] +> Esses arquivos podem representar acesso à sessão vinculada ou conter +> configuração privada. **Não envie, não coloque em Drive, não publique em +> GitHub e não cole seu conteúdo em suporte.** -```bash -chmod 700 ~/.termux/boot/start-shogun -``` +## O mínimo obrigatório -O 𝖘𝖍𝖔𝖌𝖚𝖓 não altera `.bashrc` e não ativa isso sozinho. Para conferir depois -de reiniciar, abra o Termux e veja: +**Necessário para o núcleo:** Android + Termux, Node.js 20.19+, npm, Git, FFmpeg, configuração do dono e conexão com WhatsApp. -```bash -tail -n 40 ~/shogun/termux-boot.log -``` +**Opcional:** integrações externas, IA e capacidades que dependam de APIs específicas. A ausência delas não impede o núcleo do SHOGUN de iniciar e conectar. + +--- -## Socorro rápido - -- **`pkg` ou downloads falham:** troque de Wi-Fi/dados móveis, reabra o Termux - e repita apenas o comando que falhou. -- **`cd: shogun: No such file or directory`:** o download não terminou ou você - está em outro lugar. Rode `cd`, depois repita a etapa 4. -- **O instalador parece parado:** mantenha o aplicativo aberto; a primeira - instalação pode levar vários minutos. -- **O código expirou:** pare com `Ctrl+C`, rode `npm start` e gere outro. -- **O bot cai com a tela apagada:** revise a bateria, rode - `termux-wake-lock` e mantenha a notificação do Termux ativa. -- **Termux:API ou Termux:Boot não instala:** provavelmente as fontes foram - misturadas. Todos os aplicativos Termux precisam vir da mesma fonte. -- **Apareceu um erro que você não entende:** rode `npm run preflight` e procure - a seção correspondente em [solução de problemas](../solucao-de-problemas.md). - -Nunca envie a pasta de sessão, seu código de pareamento ou uma captura contendo -credenciais. Para pedir ajuda, copie apenas a mensagem de erro. +

← Voltar à página principal

\ No newline at end of file diff --git a/docs/instalacao/windows.md b/docs/instalacao/windows.md index a6e92ba..ed598fc 100644 --- a/docs/instalacao/windows.md +++ b/docs/instalacao/windows.md @@ -1,198 +1,274 @@ -# SHOGUN no Windows: instalação para iniciantes +

🪟 SHOGUN no Windows

+

Do PowerShell ao primeiro !menu, sem pular as partes que normalmente viram problema depois.

-Este caminho serve para Windows 10 e 11. Reserve cerca de 20 minutos, use uma -conta que possa instalar programas e mantenha o computador conectado à internet. +

+ Windows + Node 20.19+ + Tempo +

+ +## Rota completa + +| Etapa | Ação | Sinal de sucesso | +| --- | --- | --- | +| **1** | abrir PowerShell | prompt disponível | +| **2** | instalar Git, Node e FFmpeg | comandos mostram versões | +| **3** | clonar o projeto | pasta `shogun` criada | +| **4** | rodar o instalador | setup concluído | +| **5** | executar preflight | requisitos obrigatórios aprovados | +| **6** | conectar WhatsApp | feed do bot ativo | +| **7** | testar | `!menu` responde | + +--- -## Etapa 1 — abrir o PowerShell +## 1 · Abra o PowerShell -1. Abra o menu **Iniciar**. -2. Digite `PowerShell`. -3. Abra **Windows PowerShell**. Não é necessário abrir como administrador para - rodar o SHOGUN. +No menu **Iniciar**, procure por **PowerShell**. Para executar o SHOGUN normalmente não é necessário abrir como administrador. -Você vai copiar uma caixa por vez, colar com o botão direito ou `Ctrl+V` e -pressionar Enter. Espere o cursor voltar antes de seguir. +Você pode colar os comandos com `Ctrl+V`. Execute um bloco de cada vez e espere o prompt voltar. -## Etapa 2 — instalar as ferramentas +--- -O Windows 10/11 atualizado inclui o `winget`, instalador oficial do sistema. -Confira: +## 2 · Instale as ferramentas + +Primeiro confira se o `winget` está disponível: ```powershell winget --version ``` -Se o comando não existir, instale ou atualize **Instalador de Aplicativo** pela -Microsoft Store, feche o PowerShell e abra-o de novo. +Se não estiver, atualize o **Instalador de Aplicativo** pela Microsoft Store e abra uma nova janela do PowerShell. -Instale o Git: +### Git ```powershell winget install --id Git.Git -e --source winget ``` -Instale o Node.js LTS: +### Node.js LTS ```powershell winget install --id OpenJS.NodeJS.LTS -e --source winget ``` -Instale o FFmpeg: +### FFmpeg ```powershell winget install --id Gyan.FFmpeg -e --source winget ``` -Aceite os termos quando o Windows pedir. Feche completamente o PowerShell e -abra uma janela nova para que os novos comandos sejam reconhecidos. - -Se preferir instalar clicando, use somente as páginas oficiais de -[Node.js](https://nodejs.org/en/download), -[Git](https://git-scm.com/install/windows) e -[FFmpeg](https://ffmpeg.org/download.html). Escolha Node.js 22 ou 24 LTS e -lembre-se de adicionar FFmpeg ao `PATH`. +Feche o PowerShell e abra outro depois das instalações. O Windows precisa reconstruir o `PATH`, porque descobrir imediatamente que um programa acabou de ser instalado seria aparentemente pedir demais. -## Etapa 3 — conferir o terreno - -Na janela nova, rode um por vez: +### Confira ```powershell node --version -``` - -```powershell npm --version -``` - -```powershell git --version -``` - -```powershell ffmpeg -version ``` -Cada comando deve mostrar uma versão. Se algum disser “não é reconhecido”, -reinicie o computador uma vez antes de reinstalar. +> [!IMPORTANT] +> O SHOGUN exige **Node.js 20.19.0 ou superior**. Node 22/24 LTS é uma boa escolha para instalação nova. -## Etapa 4 — baixar o SHOGUN +--- -Escolha uma pasta simples. Este comando vai para sua pasta de usuário: +## 3 · Baixe o projeto ```powershell Set-Location $HOME -``` - -Baixe o projeto: - -```powershell git clone https://github.com/dgreych/shogun.git +Set-Location shogun ``` -Entre no quartel: +Se a pasta já existir, não clone por cima: ```powershell -Set-Location shogun +Set-Location "$HOME\shogun" ``` -## Etapa 5 — preparar e configurar +--- + +## 4 · Execute o instalador do SHOGUN ```powershell powershell -ExecutionPolicy Bypass -File .\scripts\install-windows.ps1 ``` -O preparo baixa os componentes do bot e depois faz quatro perguntas: +O instalador verifica o ambiente, cria `.env.local` a partir de `.env.example` +se ele ainda não existir, instala as dependências travadas pelo projeto e abre +o setup. Integrações externas ficam desligadas por padrão. -1. como o SHOGUN deve chamar você; -2. seu número com país e DDD, somente dígitos — exemplo fictício - `5511999999999`; -3. nome do bot — pressione Enter para manter `SHOGUN`; -4. prefixo — pressione Enter para manter `!`. + + + + + +
Seu nomeidentificação do dono principal desta instância
Númeropaís + DDD + número do dono principal, somente dígitos
Nome do botnome desta instalação
Prefixopor exemplo !
-Espere aparecer **SHOGUN pronto**. Para corrigir uma resposta depois, use: +### O que “dono” significa aqui + +```text +projeto SHOGUN + └── sua instalação no Windows + ├── dono principal -> número informado no setup + ├── sessão WhatsApp + └── grupos -> administradores próprios +``` + +O número configurado é o **dono principal desta cópia do bot** e recebe os +privilégios reservados ao dono. Isso não muda a autoria do projeto e não é a +mesma coisa que ser administrador de um grupo do WhatsApp. + +Para refazer apenas essa configuração depois: ```powershell npm run setup ``` -## Etapa 6 — inspeção e conexão +Para entender BunnyFy, NVIDIA, VEX e quais chaves são opcionais, consulte +**[Configuração da sua instância](../configuracao-da-instancia.md)**. + +--- + +## 5 · Faça a inspeção antes do boot ```powershell npm run preflight ```

- Saída da verificação do ambiente + Preflight do SHOGUN no Windows

-Imagem gerada da execução real do comando. O aviso amarelo sobre -configuração é esperado antes da etapa seguinte. +O preflight verifica Node.js, npm, Git, FFmpeg, arquivos locais, dono principal +e o estado das integrações sem imprimir tokens. Avisos sobre APIs opcionais não +impedem o núcleo de iniciar quando esses recursos não estão sendo usados. + +> [!NOTE] +> Você não precisa de uma chave de API para chegar ao primeiro `!menu`. Configure +> integrações externas depois que o núcleo estiver funcionando. -Se todos os itens obrigatórios estiverem aprovados, inicie: +--- + +## 6 · Inicie e conecte o WhatsApp ```powershell npm start ``` -Escolha `1` para QR Code. No telefone, abra WhatsApp → menu de três pontos → -**Aparelhos conectados/Dispositivos conectados → Conectar um aparelho** e leia -o QR da tela do computador. +O painel oferece **QR Code** e **código de pareamento**. -Se a câmera não puder ler a tela, pare com `Ctrl+C`, inicie novamente, escolha -`2` e siga o código de pareamento. +

+ Painel de conexão do SHOGUN +

+ +Para QR Code, no telefone abra **WhatsApp → Aparelhos conectados → Conectar um aparelho**. + +Se preferir não usar a câmera, reinicie o fluxo e escolha o código de pareamento. Depois que a sessão for criada, ela é reaproveitada nos próximos boots enquanto continuar válida. -## Etapa 7 — confirmar a primeira missão +--- -Numa conversa de teste, envie: +## 7 · Prove que terminou + +Envie em um grupo de teste: ```text !menu ``` -Recebeu o menu? O posto está pronto. O PowerShell precisa permanecer aberto -enquanto o SHOGUN estiver em serviço. +Se escolheu outro prefixo, use-o no lugar de `!`. -## Sua rotina +> [!TIP] +> O PowerShell precisa permanecer aberto enquanto o processo estiver rodando. Fechar a janela encerra essa execução do bot. -Para parar com segurança, clique no PowerShell e pressione `Ctrl+C`. +--- -Para voltar outro dia: +## APIs opcionais -```powershell -Set-Location "$HOME\shogun" -``` +Depois que `!menu` estiver funcionando, abra `.env.local` somente se quiser +ativar recursos externos. A matriz atual está em +**[Configuração da sua instância](../configuracao-da-instancia.md)**. + +Resumo: + +- núcleo + WhatsApp: sem chave de API; +- BunnyFy: URL + credencial de consumidor quando ativada; +- NVIDIA direta: `NVIDIA_API_KEY` apenas para IA direta/fallback; +- VEX: `VEX_API_KEY` + `VEX_SITE` apenas no fallback legado correspondente. + +Nunca coloque chaves internas de providers da BunnyFy no bot. + +--- + +## Uso diário + +### Parar + +Pressione `Ctrl+C`. + +### Iniciar novamente ```powershell +Set-Location "$HOME\shogun" npm start ``` -Para atualizar, pare o bot e rode: +### Atualizar ```powershell +Set-Location "$HOME\shogun" git pull --ff-only -``` - -```powershell npm ci --no-audit --no-fund +npm run preflight +npm start ``` -```powershell -npm start +--- + +## Diagnóstico + +
+PowerShell diz que execução de scripts foi desabilitada +
+Use o comando do instalador exatamente com -ExecutionPolicy Bypass. Ele aplica a exceção ao processo usado para essa instalação. +
+ +
+ffmpeg, node ou git não é reconhecido +
+Feche todas as janelas do PowerShell, abra outra e teste novamente. Se persistir, reinicie o Windows uma vez e rode npm run preflight antes de reinstalar qualquer coisa. +
+ +
+O QR ficou pequeno ou ilegível +
+Maximize a janela ou use o código de pareamento. +
+ +
+A pasta shogun já existe +
+Entre nela com Set-Location "$HOME\shogun". Não clone outra cópia por cima. +
+ +--- + +## 🔐 Proteja a instalação + +Arquivos privados importantes: + +```text +.env.local +dados/src/config.json +dados/database/qr-code/ ``` -Não copie apenas `node_modules` para outro PC. Em uma máquina nova, clone o -projeto e execute o instalador novamente. Preserve com cuidado a pasta local de -sessão; ela vale como uma chave do WhatsApp. +> [!CAUTION] +> Não envie esses arquivos para suporte, GitHub, Drive ou outras pessoas. A +> sessão contém material de autenticação e os demais podem conter configuração +> privada ou credenciais. -## Socorro rápido +Para diagnóstico adicional, execute `npm run preflight` e consulte **[Solução de problemas](../solucao-de-problemas.md)**. -- **Execução de scripts foi desabilitada:** use exatamente o comando com - `-ExecutionPolicy Bypass` mostrado na etapa 5; ele vale apenas para esse - instalador. -- **`ffmpeg` não é reconhecido:** feche todas as janelas do PowerShell e abra - outra. Se persistir, reinstale o FFmpeg e reinicie o Windows. -- **O QR ficou pequeno:** maximize a janela ou use código de pareamento. -- **A pasta `shogun` já existe:** entre nela com `Set-Location shogun`; não - clone por cima. -- **Ainda não funcionou:** rode `npm run preflight` e consulte - [solução de problemas](../solucao-de-problemas.md). +

← Voltar à página principal

\ No newline at end of file diff --git a/docs/portabilidade.md b/docs/portabilidade.md new file mode 100644 index 0000000..5e62be5 --- /dev/null +++ b/docs/portabilidade.md @@ -0,0 +1,25 @@ +# Portabilidade do SHOGUN + +O repositório público é tratado como uma distribuição instalável, não como uma fotografia ornamental do código. + +| Plataforma | Entrada recomendada | Validação automatizada | +| --- | --- | --- | +| Android / Termux | `bash scripts/install-termux.sh` | contrato Termux + Node 20.19 | +| Windows 10/11 | `scripts/install-windows.ps1` | Windows + Node 20.19/22/24 | +| Linux | `bash scripts/install-linux.sh` | Ubuntu + Node 20.19/22/24 | +| macOS | `bash scripts/install-macos.sh` | macOS + Node 20.19/22/24 | + +## O que “suportado” significa + +A matriz verifica instalação de dependências, entradas principais do runtime, build modular e requisitos de plataforma. O job de qualidade executa também os testes de domínio, roteamento, estado, compatibilidade, regressões e validação de release. + +No Android, o GitHub Actions não finge ser um aparelho físico. O contrato automatizado valida o caminho específico do Termux, enquanto o guia documenta as etapas que dependem do dispositivo e do pareamento real com o WhatsApp. + +## Guias + +- [Termux](instalacao/termux.md) +- [Windows](instalacao/windows.md) +- [Linux](instalacao/linux.md) +- [macOS](instalacao/macos.md) + +Para a primeira conexão e os cuidados com a sessão, veja também [Primeiros passos](primeiros-passos.md) e [Segurança](seguranca.md). diff --git a/docs/primeiros-passos.md b/docs/primeiros-passos.md index 98d29b4..71ad6c9 100644 --- a/docs/primeiros-passos.md +++ b/docs/primeiros-passos.md @@ -1,5 +1,25 @@ # Primeiros passos com o SHOGUN +## Antes de tudo: esta instalação é sua instância + +Ao executar o setup, o número salvo em `numerodono` vira o **dono principal +desta instância** do SHOGUN. É esse número que recebe privilégios reservados ao +dono do bot. + +Isso não altera a autoria do projeto e também não é a mesma coisa que ser +administrador de um grupo do WhatsApp. + +```text +projeto SHOGUN + └── sua instalação + ├── dono principal -> numerodono + ├── sessão WhatsApp + └── grupos -> administradores próprios +``` + +Leia **[Configuração da sua instância](configuracao-da-instancia.md)** para a +matriz completa de APIs e credenciais opcionais. + ## A inspeção Dentro da pasta do projeto: @@ -8,7 +28,12 @@ Dentro da pasta do projeto: npm run preflight ``` -Todos os itens obrigatórios devem aparecer com `✅`. +Todos os itens obrigatórios devem aparecer com `✅`. Avisos sobre NVIDIA, VEX, +BunnyFy ou outras integrações opcionais não impedem o núcleo de iniciar quando +esses recursos não estão sendo usados. + +O preflight também verifica `.env.local` e a configuração da instância sem +imprimir tokens ou chaves. ## A configuração @@ -16,7 +41,12 @@ Todos os itens obrigatórios devem aparecer com `✅`. npm run setup ``` -Informe seu nome, número com país e DDD, nome do bot e prefixo. Para manter um valor existente, apenas pressione Enter. +Informe seu nome, número com país e DDD, nome do bot e prefixo. Para manter um +valor existente, apenas pressione Enter. + +O setup grava `dados/src/config.json`. Os instaladores oficiais também criam +`.env.local` a partir de `.env.example` quando necessário, mantendo APIs +opcionais desligadas por padrão. ## A conexão @@ -24,7 +54,8 @@ Informe seu nome, número com país e DDD, nome do bot e prefixo. Para manter um npm start ``` -Escolha QR ou código de pareamento. A sessão fica local e será reutilizada no próximo início. +Escolha QR ou código de pareamento. A sessão fica local e será reutilizada no +próximo início. ## A primeira missão @@ -34,11 +65,24 @@ Envie: !menu ``` -Depois teste uma ferramenta simples, uma figurinha e um comando de grupo sem efeito destrutivo. Só dê cargo de administrador quando entender quais rotinas serão usadas. +Depois teste uma ferramenta simples, uma figurinha e um comando de grupo sem +efeito destrutivo. Só dê cargo de administrador quando entender quais rotinas +serão usadas. + +## APIs vêm depois do primeiro boot + +O núcleo do SHOGUN funciona **sem chave de API obrigatória**. Você não precisa +configurar BunnyFy, NVIDIA, VEX ou upload externo para provar que a instalação +básica está correta. Primeiro confirme `!menu`; depois ative somente as +integrações que realmente quiser usar. + +Consulte **[Configuração da sua instância](configuracao-da-instancia.md)** para +saber exatamente qual variável pertence a cada capacidade. ## A rotina - `Ctrl+C` encerra com segurança; - `npm start` retoma a patrulha; -- `npm run setup` altera a configuração; +- `npm run setup` altera dono, nome e prefixo; +- `npm run preflight` diagnostica sistema, configuração e integrações; - `git pull --ff-only` e `npm ci` atualizam o código e as dependências. diff --git a/scripts/install-linux.sh b/scripts/install-linux.sh index 5b385ab..6e5eb51 100755 --- a/scripts/install-linux.sh +++ b/scripts/install-linux.sh @@ -10,6 +10,15 @@ for command in git node npm ffmpeg; do fi done +if [ ! -f .env.local ]; then + cp .env.example .env.local + chmod 600 .env.local + echo "🔐 .env.local criado a partir de .env.example. APIs opcionais continuam desativadas." +else + chmod 600 .env.local 2>/dev/null || true + echo "🔐 .env.local existente preservado." +fi + node scripts/preflight-platform.mjs GIT_CONFIG_COUNT=1 \ GIT_CONFIG_KEY_0=url.https://github.com/.insteadOf \ @@ -17,4 +26,4 @@ GIT_CONFIG_VALUE_0=ssh://git@github.com/ \ npm ci --no-audit --no-fund npm run setup -echo "SHOGUN pronto. Inicie com: npm start" +echo "SHOGUN pronto. Rode 'npm run preflight' e depois 'npm start'." diff --git a/scripts/install-macos.sh b/scripts/install-macos.sh new file mode 100644 index 0000000..08e9dbf --- /dev/null +++ b/scripts/install-macos.sh @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")/.." + +missing=0 +for cmd in git node npm ffmpeg; do + if ! command -v "$cmd" >/dev/null 2>&1; then + echo "Falta o comando: $cmd" + missing=1 + fi +done + +if [ "$missing" -ne 0 ]; then + echo "Instale os requisitos e rode novamente. Com Homebrew: brew install node git ffmpeg" + exit 1 +fi + +if [ ! -f .env.local ]; then + cp .env.example .env.local + chmod 600 .env.local + echo "🔐 .env.local criado a partir de .env.example. APIs opcionais continuam desativadas." +else + chmod 600 .env.local 2>/dev/null || true + echo "🔐 .env.local existente preservado." +fi + +node scripts/preflight-platform.mjs + +GIT_CONFIG_COUNT=1 \ +GIT_CONFIG_KEY_0=url.https://github.com/.insteadOf \ +GIT_CONFIG_VALUE_0=ssh://git@github.com/ \ +npm ci --no-audit --no-fund + +npm run setup + +node --check dados/src/.scripts/start-v9-fixed.js +node --check dados/src/connect.js + +echo "SHOGUN pronto. Rode 'npm run preflight' e depois 'npm start'." diff --git a/scripts/install-termux.sh b/scripts/install-termux.sh index 28fbcb4..805bb2e 100755 --- a/scripts/install-termux.sh +++ b/scripts/install-termux.sh @@ -4,7 +4,16 @@ set -euo pipefail cd "$(dirname "$0")/.." pkg update -y -pkg install -y git nodejs-lts ffmpeg +pkg install -y git nodejs-lts ffmpeg termux-tools + +if [ ! -f .env.local ]; then + cp .env.example .env.local + chmod 600 .env.local + echo "🔐 .env.local criado a partir de .env.example. APIs opcionais continuam desativadas." +else + chmod 600 .env.local 2>/dev/null || true + echo "🔐 .env.local existente preservado." +fi node scripts/preflight-platform.mjs @@ -15,4 +24,7 @@ npm ci --no-audit --no-fund npm run setup -echo "SHOGUN pronto. Use termux-wake-lock e depois npm start." +node --check dados/src/.scripts/start-v9-fixed.js +node --check dados/src/connect.js + +echo "SHOGUN pronto. Execute termux-wake-lock, rode 'npm run preflight' e depois 'npm start'." diff --git a/scripts/install-windows.ps1 b/scripts/install-windows.ps1 index 9c213b7..4b9a4c1 100644 --- a/scripts/install-windows.ps1 +++ b/scripts/install-windows.ps1 @@ -7,6 +7,14 @@ foreach ($command in @("git", "node", "npm", "ffmpeg")) { } } +if (-not (Test-Path ".env.local")) { + Copy-Item ".env.example" ".env.local" + Write-Host "🔐 .env.local criado a partir de .env.example. APIs opcionais continuam desativadas." -ForegroundColor Cyan +} +else { + Write-Host "🔐 .env.local existente preservado." -ForegroundColor Cyan +} + node scripts/preflight-platform.mjs $previousGitConfigCount = $env:GIT_CONFIG_COUNT @@ -25,4 +33,4 @@ finally { $env:GIT_CONFIG_VALUE_0 = $previousGitConfigValue } -Write-Host "SHOGUN pronto. Inicie com: npm start" -ForegroundColor Green +Write-Host "SHOGUN pronto. Rode 'npm run preflight' e depois 'npm start'." -ForegroundColor Green diff --git a/scripts/preflight-platform.mjs b/scripts/preflight-platform.mjs index b405dfe..1303592 100755 --- a/scripts/preflight-platform.mjs +++ b/scripts/preflight-platform.mjs @@ -4,6 +4,8 @@ import fs from 'fs'; import path from 'path'; import { spawnSync } from 'child_process'; +import { loadLocalEnv } from '../dados/src/.scripts/envLoader.js'; + const ROOT = path.resolve(import.meta.dirname, '..'); const MIN_NODE = [20, 19, 0]; const failures = []; @@ -32,11 +34,126 @@ function probe(label, command, args, required = true) { return false; } +function isTrue(value) { + return ['true', '1', 'yes', 'on'].includes(String(value || '').trim().toLowerCase()); +} + +function isPresent(value) { + return Boolean(String(value || '').trim()); +} + +function maskedState(value) { + return isPresent(value) ? 'configurado' : 'não configurado'; +} + +function readLocalConfig(configPath) { + if (!fs.existsSync(configPath)) return null; + try { + return JSON.parse(fs.readFileSync(configPath, 'utf8')); + } catch (error) { + failures.push(`dados/src/config.json não pôde ser lido: ${error.message}`); + return null; + } +} + +function inspectInstanceConfig(config) { + if (!config) return; + + const ownerName = String(config.nomedono || '').trim(); + const ownerNumber = String(config.numerodono || '').replace(/\D/g, ''); + const botName = String(config.nomebot || '').trim(); + const prefix = String(config.prefixo || '').trim(); + + if (!ownerName) failures.push('O nome do dono principal da instância está vazio; execute npm run setup.'); + if (!/^\d{10,15}$/.test(ownerNumber)) { + failures.push('O número do dono principal da instância deve ter 10 a 15 dígitos; execute npm run setup.'); + } + if (!botName) failures.push('O nome local do bot está vazio; execute npm run setup.'); + if (!prefix) failures.push('O prefixo de comandos está vazio; execute npm run setup.'); + + if (ownerName && /^\d{10,15}$/.test(ownerNumber) && botName && prefix) { + console.log('✅ Dono principal da instância — configurado'); + console.log('✅ Nome do bot e prefixo — configurados'); + } +} + +function inspectOptionalIntegrations() { + console.log('\n🔌 Integrações opcionais\n'); + + const modeKeys = [ + 'BUNNYFY_AI_MODE', + 'BUNNYFY_YOUTUBE_MODE', + 'BUNNYFY_TRANSCRIPTION_MODE', + 'BUNNYFY_FACEBOOK_MODE', + 'BUNNYFY_PINTEREST_MODE', + 'BUNNYFY_TIKTOK_MODE', + 'BUNNYFY_KWAI_MODE', + 'BUNNYFY_IMAGES_MODE', + 'BUNNYFY_STICKERS_MODE', + 'BUNNYFY_CANVAS_MODE', + 'BUNNYFY_LOGOS_MODE', + 'BUNNYFY_GAMES_MODE', + 'BUNNYFY_IMAGE_GEN_MODE', + 'BUNNYFY_TAVERN_RENDER_MODE', + 'BUNNYFY_NEXO_RENDER_MODE' + ]; + const allowedModes = new Set(['off', 'primary', 'exclusive']); + const modes = modeKeys.map(key => [key, String(process.env[key] || 'off').trim().toLowerCase()]); + + for (const [key, value] of modes) { + if (!allowedModes.has(value)) failures.push(`${key} possui modo inválido: use off, primary ou exclusive.`); + } + + const bunnyFyEnabled = isTrue(process.env.BUNNYFY_ENABLED); + const activeModes = modes.filter(([, value]) => value !== 'off'); + const bunnyFyBaseUrl = String(process.env.BUNNYFY_BASE_URL || '').trim(); + const bunnyFyToken = String(process.env.BUNNYFY_API_TOKEN || '').trim(); + + if (!bunnyFyEnabled) { + console.log('ℹ️ BunnyFy — desativada; núcleo e fallbacks disponíveis continuam independentes'); + } else if (!activeModes.length) { + warnings.push('BUNNYFY_ENABLED está ativo, mas nenhuma capacidade BunnyFy saiu de off.'); + } else { + if (!bunnyFyBaseUrl) failures.push('BunnyFy está ativa em alguma capacidade, mas BUNNYFY_BASE_URL está vazio.'); + if (!bunnyFyToken) failures.push('BunnyFy está ativa em alguma capacidade, mas BUNNYFY_API_TOKEN está vazio.'); + if (bunnyFyBaseUrl && bunnyFyToken) { + console.log(`✅ BunnyFy — configurada para ${activeModes.length} capacidade(s), sem exibir credencial`); + } + } + + const aiMode = bunnyFyEnabled + ? String(process.env.BUNNYFY_AI_MODE || 'off').trim().toLowerCase() + : 'off'; + const nvidiaConfigured = isPresent(process.env.NVIDIA_API_KEY); + + if (aiMode === 'exclusive') { + console.log('ℹ️ IA — exclusiva pela BunnyFy; chave NVIDIA direta não é necessária no bot'); + } else if (nvidiaConfigured) { + console.log(`✅ NVIDIA direta — ${maskedState(process.env.NVIDIA_API_KEY)}`); + } else if (aiMode === 'primary') { + warnings.push('IA está em primary, mas NVIDIA_API_KEY não foi configurada; o fallback direto ficará indisponível.'); + } else { + console.log('ℹ️ NVIDIA direta — não configurada; recurso opcional'); + } + + const vexKey = isPresent(process.env.VEX_API_KEY); + const vexSite = isPresent(process.env.VEX_SITE); + if (vexKey && vexSite) console.log('✅ VEX legado — configurado'); + else if (vexKey || vexSite) warnings.push('VEX está parcialmente configurada; preencha VEX_API_KEY e VEX_SITE ou deixe ambos vazios.'); + else console.log('ℹ️ VEX legado — não configurado'); + + const uploadToken = isPresent(process.env.UPLOAD_GITHUB_TOKEN); + const uploadRepo = isPresent(process.env.UPLOAD_GITHUB_REPO); + if (uploadToken && uploadRepo) console.log('✅ Upload GitHub legado — configurado'); + else if (uploadToken || uploadRepo) warnings.push('Upload GitHub legado está parcialmente configurado; token e repositório precisam existir juntos.'); + else console.log('ℹ️ Upload GitHub legado — não configurado'); +} + console.log('\n⛩️ Verificação do ambiente\n'); const nodeVersion = versionTuple(process.versions.node); if (atLeast(nodeVersion, MIN_NODE)) console.log(`✅ Node.js — v${process.versions.node}`); -else failures.push(`Node.js ${MIN_NODE.join('.')} ou superior é necessário; atual: ${process.versions.node}`); +else failures.push(`Node.js ${MIN_NODE.join('.')} ou superior é necessário; atual: v${process.versions.node}`); probe('npm', process.platform === 'win32' ? 'npm.cmd' : 'npm', ['--version']); probe('Git', 'git', ['--version']); @@ -46,23 +163,46 @@ const isTermux = Boolean(process.env.TERMUX_VERSION) || fs.existsSync('/data/dat const platformName = isTermux ? 'Termux/Android' : `${process.platform}/${process.arch}`; console.log(`✅ Plataforma detectada — ${platformName}`); -if (isTermux && !probe('termux-wake-lock', 'termux-wake-lock', [], false)) { - warnings.push('Use pkg install termux-api e o aplicativo Termux:API para manter o aparelho acordado.'); +if (isTermux) { + const prefix = process.env.PREFIX || '/data/data/com.termux/files/usr'; + const wakeLockPath = path.join(prefix, 'bin', 'termux-wake-lock'); + if (fs.existsSync(wakeLockPath)) console.log('✅ termux-wake-lock — disponível'); + else warnings.push('termux-wake-lock não foi encontrado. Atualize termux-tools com: pkg install termux-tools. Não é necessário instalar Termux:API para esse comando.'); +} + +const envPath = path.join(ROOT, '.env.local'); +if (fs.existsSync(envPath)) { + try { + loadLocalEnv(); + console.log('✅ .env.local — carregado sem exibir valores'); + } catch (error) { + failures.push(`.env.local não pôde ser carregado: ${error.message}`); + } +} else { + warnings.push('`.env.local` ainda não existe. Copie `.env.example` para `.env.local` ou execute o instalador oficial da plataforma.'); } const configPath = path.join(ROOT, 'dados', 'src', 'config.json'); const modulesPath = path.join(ROOT, 'node_modules'); -if (fs.existsSync(configPath)) console.log('✅ Configuração local encontrada'); -else warnings.push('Configuração local ainda não criada; execute npm run setup.'); +const localConfig = readLocalConfig(configPath); +if (localConfig) { + console.log('✅ Configuração local encontrada'); + inspectInstanceConfig(localConfig); +} else if (!fs.existsSync(configPath)) { + warnings.push('Configuração local ainda não criada; execute npm run setup.'); +} + if (fs.existsSync(modulesPath)) console.log('✅ Dependências locais encontradas'); else warnings.push('Dependências ainda não instaladas; execute npm ci.'); +inspectOptionalIntegrations(); + for (const warning of warnings) console.log(`⚠️ ${warning}`); for (const failure of failures) console.log(`❌ ${failure}`); if (failures.length) { - console.log('\n🚫 O ambiente ainda não está pronto. Corrija os itens acima e repita npm run preflight.\n'); + console.log('\n🚫 O ambiente ainda não está pronto. Corrija os itens obrigatórios acima e repita npm run preflight.\n'); process.exit(1); } -console.log('\n🛡️ Ambiente pronto. Siga para a configuração.\n'); +console.log('\n🛡️ Ambiente base pronto. Integrações marcadas como opcionais podem ser configuradas depois.\n'); diff --git a/scripts/validate-instance-config-contract.mjs b/scripts/validate-instance-config-contract.mjs new file mode 100644 index 0000000..461b0da --- /dev/null +++ b/scripts/validate-instance-config-contract.mjs @@ -0,0 +1,239 @@ +#!/usr/bin/env node + +import fs from 'node:fs'; +import path from 'node:path'; + +const ROOT = path.resolve(import.meta.dirname, '..'); +const failures = []; +const notes = []; + +// Somente código de runtime. Scripts de CI/manutenção podem usar variáveis +// operacionais que não pertencem ao contrato de configuração do usuário. +const RUNTIME_ROOTS = [ + path.join(ROOT, 'dados', 'src'), + path.join(ROOT, 'src') +]; + +const SKIP_DIRS = new Set([ + '.git', + 'node_modules', + 'dist', + 'dist-vnext', + 'coverage', + 'dados/database' +]); + +const USER_CONFIG_KEY = /^(?:BUNNYFY_|NVIDIA_|VEX_|UPLOAD_GITHUB_|SHOGUN_|BOT_NAME$|DEFAULT_PERSONA$)/; +const SECRET_LIKE = /(?:KEY|TOKEN|SECRET|PASSWORD|COOKIE)/; +const ALLOWED_MODES = new Set(['off', 'primary', 'exclusive']); + +function read(file) { + return fs.readFileSync(path.join(ROOT, file), 'utf8'); +} + +function walk(dir, output = []) { + if (!fs.existsSync(dir)) return output; + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + const relative = path.relative(ROOT, full).replaceAll(path.sep, '/'); + if (entry.isDirectory()) { + if (SKIP_DIRS.has(entry.name) || SKIP_DIRS.has(relative)) continue; + walk(full, output); + continue; + } + if (/\.(?:[cm]?js|ts)$/.test(entry.name)) output.push(full); + } + return output; +} + +function parseEnvExample() { + const env = new Map(); + for (const rawLine of read('.env.example').split(/\r?\n/)) { + const line = rawLine.trim(); + if (!line || line.startsWith('#')) continue; + const match = line.match(/^([A-Z][A-Z0-9_]*)=(.*)$/); + if (!match) { + failures.push(`Linha inválida em .env.example: ${rawLine}`); + continue; + } + if (env.has(match[1])) failures.push(`Chave duplicada em .env.example: ${match[1]}`); + env.set(match[1], match[2]); + } + return env; +} + +function collectRuntimeKeys() { + const keys = new Map(); + const patterns = [ + /process\.env\.([A-Z][A-Z0-9_]*)/g, + /process\.env\[['"]([A-Z][A-Z0-9_]*)['"]\]/g, + /\benv\.([A-Z][A-Z0-9_]*)/g, + // Modos são frequentemente passados dinamicamente como string para + // resolveCapabilityMode(name, env), portanto não aparecem como env.KEY. + // Restringir a *_MODE evita confundir códigos de erro BUNNYFY_*. + /['"](BUNNYFY_[A-Z0-9_]+_MODE)['"]/g + ]; + const files = RUNTIME_ROOTS.flatMap(sourceRoot => walk(sourceRoot, [])); + + for (const file of files) { + const relative = path.relative(ROOT, file).replaceAll(path.sep, '/'); + const content = fs.readFileSync(file, 'utf8'); + for (const pattern of patterns) { + pattern.lastIndex = 0; + let match; + while ((match = pattern.exec(content)) !== null) { + const key = match[1]; + if (!USER_CONFIG_KEY.test(key)) continue; + if (!keys.has(key)) keys.set(key, new Set()); + keys.get(key).add(relative); + } + } + } + return keys; +} + +function requireEnvKey(env, key) { + if (!env.has(key)) failures.push(`.env.example não documenta ${key}.`); +} + +function expectValue(env, key, expected) { + requireEnvKey(env, key); + if (env.has(key) && env.get(key) !== expected) { + failures.push(`${key} deve iniciar como ${JSON.stringify(expected)} no exemplo público.`); + } +} + +function validateSecretsAreBlank(env) { + for (const [key, value] of env.entries()) { + if (!SECRET_LIKE.test(key)) continue; + if (value.trim()) failures.push(`${key} parece segredo e deve ficar vazio em .env.example.`); + } +} + +function validateModes(env) { + for (const [key, value] of env.entries()) { + if (!key.startsWith('BUNNYFY_') || !key.endsWith('_MODE')) continue; + if (!ALLOWED_MODES.has(value)) { + failures.push(`${key} deve usar um modo seguro conhecido no exemplo: off, primary ou exclusive.`); + } + if (value !== 'off') failures.push(`${key} deve começar em off na distribuição pública.`); + } +} + +function validateInstaller(file) { + const content = read(file); + if (!content.includes('.env.example') || !content.includes('.env.local')) { + failures.push(`${file} não garante/explica o bootstrap de .env.local.`); + } + if (!content.includes('npm run setup')) failures.push(`${file} não executa npm run setup.`); +} + +function validateGuide(file) { + const content = read(file); + if (!content.includes('configuracao-da-instancia.md')) { + failures.push(`${file} não aponta para o guia canônico de configuração.`); + } + if (!/dono principal/i.test(content)) failures.push(`${file} não explica o dono principal da instância.`); + if (!/nenhuma chave|sem chave de API|não precisa de (?:uma )?chave/i.test(content)) { + failures.push(`${file} não deixa claro que o núcleo inicia sem chave de API.`); + } +} + +const env = parseEnvExample(); +const runtimeKeys = collectRuntimeKeys(); + +for (const [key, files] of runtimeKeys.entries()) { + if (!env.has(key)) { + failures.push(`Configuração usada pelo runtime não documentada: ${key} (${[...files].slice(0, 4).join(', ')}).`); + } +} + +for (const key of [ + 'BOT_NAME', + 'DEFAULT_PERSONA', + 'SHOGUN_LOW_MEMORY', + 'BUNNYFY_ENABLED', + 'BUNNYFY_BASE_URL', + 'BUNNYFY_API_TOKEN', + 'BUNNYFY_AI_MODE', + 'BUNNYFY_YOUTUBE_MODE', + 'BUNNYFY_TRANSCRIPTION_MODE', + 'BUNNYFY_FACEBOOK_MODE', + 'BUNNYFY_PINTEREST_MODE', + 'BUNNYFY_TIKTOK_MODE', + 'BUNNYFY_KWAI_MODE', + 'BUNNYFY_IMAGES_MODE', + 'BUNNYFY_STICKERS_MODE', + 'BUNNYFY_CANVAS_MODE', + 'BUNNYFY_LOGOS_MODE', + 'BUNNYFY_GAMES_MODE', + 'BUNNYFY_IMAGE_GEN_MODE', + 'BUNNYFY_TAVERN_RENDER_MODE', + 'BUNNYFY_NEXO_RENDER_MODE', + 'NVIDIA_API_KEY', + 'VEX_API_KEY', + 'VEX_SITE', + 'UPLOAD_GITHUB_TOKEN', + 'UPLOAD_GITHUB_REPO' +]) requireEnvKey(env, key); + +expectValue(env, 'BUNNYFY_ENABLED', 'false'); +validateSecretsAreBlank(env); +validateModes(env); + +for (const file of [ + 'scripts/install-linux.sh', + 'scripts/install-macos.sh', + 'scripts/install-termux.sh', + 'scripts/install-windows.ps1' +]) validateInstaller(file); + +for (const file of [ + 'README.md', + 'docs/primeiros-passos.md', + 'docs/instalacao/linux.md', + 'docs/instalacao/macos.md', + 'docs/instalacao/termux.md', + 'docs/instalacao/windows.md' +]) validateGuide(file); + +const configExample = JSON.parse(read('dados/src/config.example.json')); +for (const key of ['nomedono', 'numerodono', 'nomebot', 'prefixo']) { + if (!Object.hasOwn(configExample, key)) failures.push(`config.example.json não contém ${key}.`); +} +if (configExample.numerodono !== '55DDDNUMERO') { + failures.push('config.example.json deve manter número neutro, nunca um número real de dono.'); +} + +const preflight = read('scripts/preflight-platform.mjs'); +for (const key of [ + 'BUNNYFY_ENABLED', + 'BUNNYFY_BASE_URL', + 'BUNNYFY_API_TOKEN', + 'BUNNYFY_TRANSCRIPTION_MODE', + 'BUNNYFY_FACEBOOK_MODE', + 'BUNNYFY_PINTEREST_MODE', + 'BUNNYFY_TIKTOK_MODE', + 'BUNNYFY_KWAI_MODE', + 'NVIDIA_API_KEY', + 'VEX_API_KEY', + 'VEX_SITE' +]) { + if (!preflight.includes(key)) failures.push(`preflight não audita ${key}.`); +} +if (!preflight.includes('Dono principal da instância')) { + failures.push('preflight não valida a identidade do dono principal da instância.'); +} + +notes.push(`${runtimeKeys.size} variáveis/modos de configuração da instância encontrados no runtime.`); +notes.push(`${env.size} entradas documentadas em .env.example.`); + +for (const note of notes) console.log(`ℹ️ ${note}`); +for (const failure of failures) console.error(`❌ ${failure}`); + +if (failures.length) { + console.error(`\nContrato de configuração reprovado: ${failures.length} problema(s).\n`); + process.exit(1); +} + +console.log('\n✅ Contrato de configuração da instância aprovado.\n');