Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fca2bb65f | ||
|
|
e15ef5d007 | ||
| 885a043e2b | |||
|
|
dc9128d712 | ||
|
|
89b477888a | ||
|
|
c12374f86c | ||
|
|
f683496318 | ||
|
|
18821dd951 | ||
|
|
3c767bb14b | ||
|
|
d0c3f29ca6 | ||
|
|
8ef2d9d07d | ||
|
|
dc8fd06c2f | ||
|
|
b725c8bca3 | ||
|
|
54ed4cf307 | ||
|
|
b50099feb6 | ||
|
|
2e9378500c | ||
|
|
4ee78e69e1 | ||
|
|
c7bde09ad4 | ||
|
|
05f521d78b | ||
|
|
95767ee0d5 | ||
|
|
45323b2d3d | ||
|
|
58f5e13eb3 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,6 +1,8 @@
|
|||||||
env
|
env
|
||||||
|
src/plugins/
|
||||||
.wwebjs_auth
|
.wwebjs_auth
|
||||||
.wwebjs_cache
|
.wwebjs_cache
|
||||||
|
.claude
|
||||||
downloads
|
downloads
|
||||||
src/node_modules/
|
src/node_modules/
|
||||||
node_modules/
|
node_modules/
|
||||||
@@ -8,3 +10,8 @@ cookies.txt
|
|||||||
bin/
|
bin/
|
||||||
mychats.txt
|
mychats.txt
|
||||||
manybot.conf
|
manybot.conf
|
||||||
|
update.log
|
||||||
|
logs/audio-error.log
|
||||||
|
logs/video-error.log
|
||||||
|
registry.json
|
||||||
|
|
||||||
|
|||||||
36
Dockerfile
Normal file
36
Dockerfile
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
FROM node:20-slim
|
||||||
|
|
||||||
|
# Install dependencies for Puppeteer/Chrome
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
chromium \
|
||||||
|
chromium-sandbox \
|
||||||
|
fonts-ipafont-gothic \
|
||||||
|
fonts-wqy-zenhei \
|
||||||
|
fonts-thai-tlwg \
|
||||||
|
fonts-kacst \
|
||||||
|
fonts-freefont-ttf \
|
||||||
|
libxss1 \
|
||||||
|
--no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set environment variables for Puppeteer
|
||||||
|
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
|
||||||
|
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
|
||||||
|
|
||||||
|
# Set terminal to UTF-8 to suport QR Code characters
|
||||||
|
ENV LANG=C.UTF-8
|
||||||
|
ENV LC_ALL=C.UTF-8
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files and install dependencies
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --omit=dev
|
||||||
|
|
||||||
|
# Copy application files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Create necessary directories
|
||||||
|
RUN mkdir -p session logs
|
||||||
|
|
||||||
|
CMD ["node", "src/main.js"]
|
||||||
372
README.md
372
README.md
@@ -1,308 +1,194 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
ManyBot é um bot para WhatsApp que roda 100% localmente, sem depender da API oficial do WhatsApp. Ele utiliza a biblioteca `whatsapp-web.js`, que automatiza o WhatsApp Web sem depender de gráficos (headless).
|
<p>
|
||||||
|
<strong>Bot para WhatsApp 100% local, sem API oficial</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
Algumas funcionalidades desse bot incluem:
|
<p>
|
||||||
- Suporte a múltiplos chats em uma única sessão
|
<a href="#-recursos">Recursos</a> .
|
||||||
- Sistema de plugins — adicione, remova ou crie funcionalidades sem mexer no núcleo do bot
|
<a href="#-instalação-rápida">Instalação</a> .
|
||||||
|
<a href="#-uso">Uso</a> .
|
||||||
|
<a href="#-plugins">Plugins</a> .
|
||||||
|
<a href="#-documentação">Documentação</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
# Exemplos
|
<p>
|
||||||
|
🇧🇷 Português · <a href="README_EN.md">🇺🇸 English</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
<center>
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
|
||||||
|
<img src="https://img.shields.io/badge/npm-9+-CB3837?logo=npm&logoColor=white" alt="npm 9+">
|
||||||
|
<img src="https://img.shields.io/badge/License-GPL--v3-blue.svg" alt="License: GPL v3">
|
||||||
|
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows-lightgrey" alt="Platform">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/whatsapp--web.js-%2325D366?logo=whatsapp&logoColor=white" alt="whatsapp-web.js">
|
||||||
|
<img src="https://img.shields.io/badge/headless-Automated-green" alt="Headless">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
> **Versão Oficial Online**
|
||||||
|
> Quer usar o ManyBot sem instalar? Adicione o bot oficial:
|
||||||
|
>
|
||||||
|
> **+55 (16) 99459-1903**
|
||||||
|
>
|
||||||
|
> Online 24h (quando possível) - Disponibilidade não garantida
|
||||||
|
>
|
||||||
|
> Ao adicionar, você concorda com os [Termos de Uso](TERMOS_pt-br.md)
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
</center>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Requisitos
|
## Recursos
|
||||||
- Node.js
|
|
||||||
- NPM
|
|
||||||
- Sistema Linux ou Windows
|
|
||||||
|
|
||||||
obs: Sistemas Android e iOS ainda não são 100% compatíveis. O suporte para Termux está em fases de testes e sem garantia de funcionamento correto.
|
- **100% Local** - Sem depender da API oficial do WhatsApp
|
||||||
|
- **Multi-chat** - Suporte a múltiplos chats em uma única sessão
|
||||||
|
- **Sistema de Plugins** - Adicione, remova ou crie funcionalidades sem mexer no núcleo
|
||||||
|
- **Headless** - Funciona em segundo plano sem interface gráfica
|
||||||
|
- **Fácil Configuração** - Arquivo de config simples e intuitivo
|
||||||
|
|
||||||
# Instalação (Linux)
|
---
|
||||||
|
|
||||||
|
## Instalação Rápida
|
||||||
|
|
||||||
|
### Opção 1: Usar o Bot Oficial (Sem instalar)
|
||||||
|
|
||||||
|
Adicione o número **+55 (16) 99459-1903** aos seus contatos e envie `!many` para ver os comandos disponíveis.
|
||||||
|
|
||||||
|
**Status:** 🟢 Online (24h quando possível, mas sem garantia)
|
||||||
|
|
||||||
|
> ⚠️ **Importante:** Ao usar o bot oficial, você concorda com os [Termos de Uso](TERMOS_pt-br.md). Leia antes de adicionar!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Opção 2: Instalar sua Própria Versão
|
||||||
|
|
||||||
1. Clone o repositório e entre:
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://github.com/synt-xerror/manybot
|
# 1. Clone o repositório
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
cd manybot
|
cd manybot
|
||||||
```
|
|
||||||
|
|
||||||
2. Crie e abra o arquivo de configuração (use o editor de sua preferência):
|
# 2. Crie o arquivo de configuração
|
||||||
```bash
|
cp manybot.conf.example manybot.conf
|
||||||
touch manybot.conf
|
|
||||||
|
# 3. Configure conforme sua necessidade (veja a documentação)
|
||||||
nano manybot.conf
|
nano manybot.conf
|
||||||
```
|
|
||||||
|
|
||||||
3. Nele você pode configurar algumas coisas do ManyBot. Esse é o arquivo base para que possa modificar:
|
# 4. Execute o script de instalação
|
||||||
```bash
|
|
||||||
# Comentários com '#'
|
|
||||||
|
|
||||||
CLIENT_ID=bot_permanente
|
|
||||||
CMD_PREFIX=!
|
|
||||||
CHATS=[
|
|
||||||
123456789@c.us,
|
|
||||||
123456789@g.us
|
|
||||||
]
|
|
||||||
PLUGINS=[
|
|
||||||
video,
|
|
||||||
audio,
|
|
||||||
figurinha,
|
|
||||||
adivinhacao
|
|
||||||
]
|
|
||||||
```
|
|
||||||
- **CLIENT_ID:** ID do cliente, serve para identificar sua sessão.
|
|
||||||
- Valor padrão: `bot_permanente`
|
|
||||||
- **CMD_PREFIX:** Prefixo do comando, o caractere que você usa para executar um comando (!many, !figurinha).
|
|
||||||
- Valor padrão: `!`
|
|
||||||
- **CHATS:** ID dos chats no qual você quer que o bot assista. Use o utilitário: `src/utils/get_id.js` para descobrir os IDs. Deixe vazio caso queira que funcione com qualquer chat.
|
|
||||||
- Valor padrão: (nenhum)
|
|
||||||
- **PLUGINS:** Lista de plugins ativos. Cada nome corresponde a uma pasta dentro de `src/plugins/`. Remova ou comente uma linha para desativar o plugin sem apagá-lo.
|
|
||||||
- Valor padrão: (nenhum)
|
|
||||||
|
|
||||||
obs: o utilitário `src/utils/get_id.js` usa um CLIENT_ID separado para que não entre em conflito com a sessão principal do ManyBot. Você terá que escanear o QR Code novamente para executá-lo.
|
|
||||||
|
|
||||||
4. Execute o script de instalação:
|
|
||||||
```bash
|
|
||||||
bash ./setup
|
bash ./setup
|
||||||
```
|
|
||||||
|
|
||||||
5. Rode o bot pela primeira vez (você deve rodar da raiz, não dentro de `src`):
|
# 5. Rode o bot
|
||||||
```bash
|
|
||||||
node ./src/main.js
|
|
||||||
```
|
|
||||||
Ele vai pedir para que escaneie o QR Code com seu celular.
|
|
||||||
|
|
||||||
No WhatsApp:
|
|
||||||
Menu (três pontos) > Dispositivos conectados > Conectar um dispositivo
|
|
||||||
|
|
||||||
# Instalação (Windows)
|
|
||||||
|
|
||||||
O uso desse bot foi pensado para rodar em um terminal Linux. No entanto, você pode usar o Git Bash, que simula um terminal Linux com Bash real:
|
|
||||||
|
|
||||||
1. Para baixar o Git Bash: https://git-scm.com/install/windows
|
|
||||||
Selecione a versão que deseja (portátil ou instalador)
|
|
||||||
|
|
||||||
2. Para baixar o Node.js: https://nodejs.org/pt-br/download
|
|
||||||
Role a tela e selecione "Instalador Windows (.msi)"
|
|
||||||
Ou se preferir, use um gerenciador de pacotes como mostra no conteúdo inicial
|
|
||||||
|
|
||||||
Depois de instalar ambos, abra o Git Bash e execute exatamente os mesmos comandos mostrados na seção Linux.
|
|
||||||
|
|
||||||
# Uso
|
|
||||||
|
|
||||||
Feito a instalação, você pode executar o bot apenas rodando:
|
|
||||||
```bash
|
|
||||||
node ./src/main.js
|
node ./src/main.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## Atualizações
|
📱 **Escaneie o QR Code** no WhatsApp: Menu → Dispositivos conectados → Conectar um dispositivo
|
||||||
|
|
||||||
|
> **⚡ Pronto!** Veja a [documentação completa](docs/INSTALACAO.md) para mais detalhes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Uso
|
||||||
|
|
||||||
É recomendável sempre ter a versão mais recente do ManyBot. Para isso, temos um utilitário logo na raíz. Para executar:
|
|
||||||
```bash
|
```bash
|
||||||
|
# Iniciar o bot
|
||||||
|
node ./src/main.js
|
||||||
|
|
||||||
|
# Atualizar para a versão mais recente
|
||||||
bash ./update
|
bash ./update
|
||||||
|
|
||||||
|
# Descobrir IDs de chats
|
||||||
|
node src/utils/get_id.js
|
||||||
```
|
```
|
||||||
|
|
||||||
## Criando um serviço (opcional)
|
---
|
||||||
|
|
||||||
Se estiver rodando numa VPS ou apenas quer mais controle, é recomendável criar um serviço systemd. Siga os passos abaixo para saber como criar, habilitar e gerenciar um.
|
## 🔌 Plugins
|
||||||
|
|
||||||
1. Configurando o diretório
|
O ManyBot é construído em torno de um sistema de plugins. O kernel apenas conecta ao WhatsApp e distribui as mensagens — os plugins decidem o que fazer.
|
||||||
|
|
||||||
Primeiro passo é garantir que o diretório do ManyBot esteja no local adequado, é recomendável guardar em `/root/manybot` (os passos a seguir supõem que esteja essa localização)
|
### Gerenciando Plugins com ManyPlug
|
||||||
|
|
||||||
2. Criando o serviço
|
Instale e gerencie plugins usando o **ManyPlug CLI**:
|
||||||
|
|
||||||
Abra o arquivo:
|
|
||||||
```bash
|
```bash
|
||||||
/etc/systemd/system/manybot.service
|
# Instalar o gerenciador
|
||||||
|
npm install -g @freakk.dev/manyplug
|
||||||
|
|
||||||
|
# Criar um novo plugin
|
||||||
|
cd src/plugins
|
||||||
|
manyplug init meu-plugin --category utility
|
||||||
|
|
||||||
|
# Instalar de outro diretório
|
||||||
|
manyplug install --local ../outro-plugin
|
||||||
|
|
||||||
|
# Listar plugins instalados
|
||||||
|
manyplug list
|
||||||
```
|
```
|
||||||
|
|
||||||
E cole o seguinte conteúdo:
|
### Criar um Plugin
|
||||||
```conf
|
|
||||||
[Unit]
|
|
||||||
Description=ManyBot
|
|
||||||
After=network.target
|
|
||||||
|
|
||||||
[Service]
|
```javascript
|
||||||
ExecStart=/usr/bin/env node /root/manybot/src/main.js
|
|
||||||
WorkingDirectory=/root/manybot
|
|
||||||
Restart=always
|
|
||||||
Environment=NODE_ENV=production
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
WantedBy=multi-user.target
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Iniciando e habilitando o serviço:
|
|
||||||
|
|
||||||
Primeiro reinicie o daemon do systemd:
|
|
||||||
```bash
|
|
||||||
systemctl daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
Inicie o serviço:
|
|
||||||
```bash
|
|
||||||
systemctl start manybot
|
|
||||||
```
|
|
||||||
|
|
||||||
Habilite para que ele seja iniciado junto com o seu sistema (opcional):
|
|
||||||
```bash
|
|
||||||
systemctl enable manybot
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Gerenciando o serviço:
|
|
||||||
|
|
||||||
Ver logs:
|
|
||||||
```bash
|
|
||||||
journalctl -u manybot
|
|
||||||
```
|
|
||||||
|
|
||||||
Em tempo real:
|
|
||||||
```bash
|
|
||||||
journalctl -u manybot -f
|
|
||||||
```
|
|
||||||
|
|
||||||
Parar o serviço:
|
|
||||||
```bash
|
|
||||||
systemctl stop manybot
|
|
||||||
```
|
|
||||||
|
|
||||||
Reiniciar o serviço:
|
|
||||||
```bash
|
|
||||||
systemctl restart manybot
|
|
||||||
```
|
|
||||||
|
|
||||||
Saiba mais sobre como gerenciar serviços em: https://www.digitalocean.com/community/tutorials/how-to-use-systemctl-to-manage-systemd-services-and-units-pt
|
|
||||||
Sobre o journalctl: https://www.digitalocean.com/community/tutorials/how-to-use-journalctl-to-view-and-manipulate-systemd-logs-pt
|
|
||||||
|
|
||||||
# Plugins
|
|
||||||
|
|
||||||
O ManyBot é construído em torno de um sistema de plugins. O núcleo do bot (kernel) apenas conecta ao WhatsApp e distribui as mensagens — quem decide o que fazer com elas são os plugins.
|
|
||||||
|
|
||||||
Isso significa que você pode adicionar, remover ou criar funcionalidades sem tocar no código principal do bot.
|
|
||||||
|
|
||||||
## Plugins incluídos
|
|
||||||
|
|
||||||
O ManyBot vem com alguns plugins prontos para uso, como:
|
|
||||||
|
|
||||||
- **video** — baixa um vídeo da internet e envia no chat (`!video <link>`)
|
|
||||||
- **audio** — baixa o áudio de um vídeo e envia como mensagem de voz (`!audio <link>`)
|
|
||||||
- **figurinha** — converte imagens, GIFs e vídeos em figurinhas (`!figurinha`)
|
|
||||||
- **adivinhacao** — jogo de adivinhação de um número entre 1 e 100 (`!adivinhação começar`)
|
|
||||||
- **forca** — clássico jogo da forca (`!forca começar`)
|
|
||||||
- **many** — exibe a lista de comandos disponíveis (`!many`)
|
|
||||||
- **obrigado** — responde agradecimentos (`!obrigado`, `!valeu`, `!brigado`)
|
|
||||||
|
|
||||||
Para ativar ou desativar qualquer um deles, basta editar a lista `PLUGINS` no `manybot.conf`.
|
|
||||||
|
|
||||||
## Criando um plugin
|
|
||||||
|
|
||||||
Cada plugin é uma pasta dentro de `plugins/` com um arquivo `index.js`. O bot carrega automaticamente todos os plugins listados no `manybot.conf`.
|
|
||||||
|
|
||||||
A estrutura mínima de um plugin:
|
|
||||||
|
|
||||||
```
|
|
||||||
plugins/
|
|
||||||
└── meu-plugin/
|
|
||||||
└── index.js
|
|
||||||
```
|
|
||||||
|
|
||||||
O `index.js` deve exportar uma função `default` que o kernel chama a cada mensagem recebida. A função recebe `{ msg, api }` e decide por conta própria se age ou ignora:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// plugins/meu-plugin/index.js
|
// plugins/meu-plugin/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
import { CMD_PREFIX } from "../../config.js"
|
|
||||||
|
|
||||||
export default async function ({ msg, api }) {
|
export default async function ({ msg, api }) {
|
||||||
if (!msg.is(CMD_PREFIX + "oi")) return;
|
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||||
|
|
||||||
await msg.reply("Olá! 👋");
|
await msg.reply("Olá! 👋");
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### O objeto `msg`
|
Veja mais na [documentação de plugins](docs/PLUGINS.md).
|
||||||
|
|
||||||
Contém as informações da mensagem recebida:
|
---
|
||||||
|
|
||||||
| Propriedade | Descrição |
|
## 📚 Documentação
|
||||||
|---|---|
|
|
||||||
| `msg.body` | Texto da mensagem |
|
|
||||||
| `msg.args` | Tokens da mensagem — `["!video", "https://..."]` |
|
|
||||||
| `msg.type` | Tipo — `"chat"`, `"image"`, `"video"`, `"audio"`, `"sticker"` |
|
|
||||||
| `msg.sender` | ID de quem enviou |
|
|
||||||
| `msg.senderName` | Nome de quem enviou |
|
|
||||||
| `msg.fromMe` | `true` se foi o próprio bot que enviou |
|
|
||||||
| `msg.hasMedia` | `true` se a mensagem tem mídia |
|
|
||||||
| `msg.hasReply` | `true` se é uma resposta a outra mensagem |
|
|
||||||
| `msg.isGif` | `true` se a mídia é um GIF |
|
|
||||||
| `msg.is(cmd)` | Retorna `true` se a mensagem começa com `cmd` |
|
|
||||||
| `msg.reply(text)` | Responde à mensagem com quote |
|
|
||||||
| `msg.downloadMedia()` | Baixa a mídia — retorna `{ mimetype, data }` |
|
|
||||||
| `msg.getReply()` | Retorna a mensagem citada, ou `null` |
|
|
||||||
|
|
||||||
### O objeto `api`
|
- [📥 Instalação Completa](docs/INSTALACAO.md) — Linux, Windows, Termux
|
||||||
|
- [⚙️ Configuração](docs/CONFIGURACAO.md) — Todas as opções do `manybot.conf`
|
||||||
|
- [🔌 Criando Plugins](docs/PLUGINS.md) — Guia completo de desenvolvimento
|
||||||
|
- [🛠️ API de Plugins](docs/API.md) — Referência de objetos `msg` e `api`
|
||||||
|
|
||||||
Contém tudo que o plugin pode fazer — enviar mensagens, acessar outros plugins, registrar logs:
|
## 🌍 Internacionalização
|
||||||
|
|
||||||
| Método | Descrição |
|
O ManyBot suporta múltiplos idiomas. Configure no `manybot.conf`:
|
||||||
|---|---|
|
|
||||||
| `api.send(text)` | Envia texto no chat |
|
|
||||||
| `api.sendVideo(filePath)` | Envia um vídeo a partir de um arquivo local |
|
|
||||||
| `api.sendAudio(filePath)` | Envia um áudio a partir de um arquivo local |
|
|
||||||
| `api.sendImage(filePath, caption?)` | Envia uma imagem a partir de um arquivo local |
|
|
||||||
| `api.sendSticker(bufferOuPath)` | Envia uma figurinha — aceita `Buffer` ou caminho |
|
|
||||||
| `api.getPlugin(name)` | Retorna a API pública de outro plugin |
|
|
||||||
| `api.chat.id` | ID do chat atual |
|
|
||||||
| `api.chat.name` | Nome do chat atual |
|
|
||||||
| `api.chat.isGroup` | `true` se é um grupo |
|
|
||||||
| `api.log.info(...)` | Loga uma mensagem informativa |
|
|
||||||
| `api.log.warn(...)` | Loga um aviso |
|
|
||||||
| `api.log.error(...)` | Loga um erro |
|
|
||||||
|
|
||||||
### Lendo o manybot.conf no plugin
|
```bash
|
||||||
|
LANGUAGE=pt # Português
|
||||||
Se o seu plugin precisar de configurações próprias, você pode adicioná-las diretamente no `manybot.conf` e importá-las no código:
|
LANGUAGE=en # English
|
||||||
|
LANGUAGE=es # Español
|
||||||
```js
|
|
||||||
import { MEU_PREFIXO } from "../../src/config.js";
|
|
||||||
|
|
||||||
const prefixo = MEU_PREFIXO ?? "padrão";
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Expondo uma API para outros plugins
|
- **Padrão:** Inglês (`en`)
|
||||||
|
- **Fallback:** Se o idioma selecionado não existir, o bot usa inglês
|
||||||
|
|
||||||
Um plugin pode expor funções para que outros plugins as utilizem. Para isso, basta exportar um objeto `api`:
|
---
|
||||||
|
|
||||||
```js
|
## 📋 Requisitos
|
||||||
// plugins/utilidades/index.js
|
|
||||||
|
|
||||||
export const api = {
|
- **Node.js** 18+
|
||||||
formatarData: (date) => date.toLocaleDateString("pt-BR"),
|
- **NPM** 9+
|
||||||
};
|
- **Linux** ou **Windows** (via Git Bash)
|
||||||
|
|
||||||
export default async function ({ msg }) {
|
> ⚠️ Android/iOS e Termux têm suporte experimental sem garantias.
|
||||||
// lógica normal do plugin
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Outro plugin pode chamar:
|
---
|
||||||
|
|
||||||
```js
|
## 📝 Licença
|
||||||
const utils = api.getPlugin("utilidades");
|
|
||||||
utils.formatarData(new Date());
|
|
||||||
```
|
|
||||||
|
|
||||||
### Erros no plugin
|
Distribuído sob a licença **GPLv3**. Veja [LICENSE](LICENSE) para mais detalhes.
|
||||||
|
|
||||||
Se um plugin lançar um erro, o kernel o desativa automaticamente e loga o problema — o restante dos plugins continua funcionando normalmente. Isso garante que um plugin com bug não derruba o bot inteiro.
|
---
|
||||||
|
|
||||||
# Considerações
|
<div align="center">
|
||||||
|
|
||||||
ManyBot é distribuído sob a licença GPLv3. Você pode usar, modificar e redistribuir o software conforme os termos da licença.
|
**[⬆ Voltar ao topo](#)**
|
||||||
|
|
||||||
Saiba mais sobre as permissões lendo o arquivo [LICENSE](LICENSE) ou em: https://www.gnu.org/licenses/quick-guide-gplv3.pt-br.html
|
</div>
|
||||||
|
|||||||
194
README_EN.md
Normal file
194
README_EN.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong>100% Local WhatsApp Bot, no official API</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="#-features">Features</a> •
|
||||||
|
<a href="#-quick-install">Install</a> •
|
||||||
|
<a href="#-usage">Usage</a> •
|
||||||
|
<a href="#-plugins">Plugins</a> •
|
||||||
|
<a href="#-documentation">Documentation</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="README.md">🇧🇷 Português</a> · <a href="README_EN.md">🇺🇸 English</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
|
||||||
|
<img src="https://img.shields.io/badge/npm-9+-CB3837?logo=npm&logoColor=white" alt="npm 9+">
|
||||||
|
<img src="https://img.shields.io/badge/License-GPL--v3-blue.svg" alt="License: GPL v3">
|
||||||
|
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows-lightgrey" alt="Platform">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<img src="https://img.shields.io/badge/whatsapp--web.js-%2325D366?logo=whatsapp&logoColor=white" alt="whatsapp-web.js">
|
||||||
|
<img src="https://img.shields.io/badge/headless-Automated-green" alt="Headless">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
> 🟢 **Official Instance Online**
|
||||||
|
> Want to use ManyBot without installing? Add the official bot:
|
||||||
|
>
|
||||||
|
> **+55 (16) 99459-1903**
|
||||||
|
>
|
||||||
|
> Online 24h (when possible) · Availability not guaranteed
|
||||||
|
>
|
||||||
|
> By adding, you agree to the [Terms of Use](TERMS_en-us.md)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✨ Features
|
||||||
|
|
||||||
|
- **100% Local** — No dependency on the official WhatsApp API
|
||||||
|
- **Multi-chat** — Support for multiple chats in a single session
|
||||||
|
- **Plugin System** — Add, remove, or create features without touching the core
|
||||||
|
- **Headless** — Runs in the background without a GUI
|
||||||
|
- **Easy Configuration** — Simple and intuitive config file
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Install
|
||||||
|
|
||||||
|
### Option 1: Use the Official Bot (No install)
|
||||||
|
|
||||||
|
Add the number **+55 (16) 99459-1903** to your contacts and send `!many` to see available commands.
|
||||||
|
|
||||||
|
**Status:** 🟢 Online (24h when possible, but no guarantee)
|
||||||
|
|
||||||
|
> ⚠️ **Important:** By using the official bot, you agree to the [Terms of Use](TERMS_en-us.md). Read before adding!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Option 2: Install Your Own Version
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone the repository
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
|
||||||
|
# 2. Create the config file
|
||||||
|
cp manybot.conf.example manybot.conf
|
||||||
|
|
||||||
|
# 3. Configure as needed (see documentation)
|
||||||
|
nano manybot.conf
|
||||||
|
|
||||||
|
# 4. Run the install script
|
||||||
|
bash ./setup
|
||||||
|
|
||||||
|
# 5. Run the bot
|
||||||
|
node ./src/main.js
|
||||||
|
```
|
||||||
|
|
||||||
|
📱 **Scan the QR Code** on WhatsApp: Menu → Linked Devices → Link a Device
|
||||||
|
|
||||||
|
> **⚡ Done!** See the [full documentation](docs/INSTALLATION.md) for more details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 💻 Usage
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start the bot
|
||||||
|
node ./src/main.js
|
||||||
|
|
||||||
|
# Update to the latest version
|
||||||
|
bash ./update
|
||||||
|
|
||||||
|
# Discover chat IDs
|
||||||
|
node src/utils/get_id.js
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 Plugins
|
||||||
|
|
||||||
|
ManyBot is built around a plugin system. The kernel only connects to WhatsApp and distributes messages — plugins decide what to do.
|
||||||
|
|
||||||
|
### Managing Plugins with ManyPlug
|
||||||
|
|
||||||
|
Install and manage plugins using the **ManyPlug CLI**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install the plugin manager
|
||||||
|
npm install -g @freakk.dev/manyplug
|
||||||
|
|
||||||
|
# Create a new plugin
|
||||||
|
cd src/plugins
|
||||||
|
manyplug init my-plugin --category utility
|
||||||
|
|
||||||
|
# Install from another directory
|
||||||
|
manyplug install --local ../another-plugin
|
||||||
|
|
||||||
|
# List installed plugins
|
||||||
|
manyplug list
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a Plugin
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/my-plugin/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "hello")) return;
|
||||||
|
await msg.reply("Hello! 👋");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See more in the [plugin documentation](docs/PLUGINS_EN.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentation
|
||||||
|
|
||||||
|
- [📥 Full Installation](docs/INSTALLATION.md) — Linux, Windows, Termux
|
||||||
|
- [⚙️ Configuration](docs/CONFIGURATION.md) — All `manybot.conf` options
|
||||||
|
- [🔌 Creating Plugins](docs/PLUGINS.md) — Complete development guide
|
||||||
|
- [🛠️ Plugin API](docs/API.md) — Reference for `msg` and `api` objects
|
||||||
|
|
||||||
|
## 🌍 Internationalization
|
||||||
|
|
||||||
|
ManyBot supports multiple languages. Configure in `manybot.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LANGUAGE=pt # Português
|
||||||
|
LANGUAGE=en # English
|
||||||
|
LANGUAGE=es # Español
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Default:** English (`en`)
|
||||||
|
- **Fallback:** If selected language doesn't exist, bot falls back to English
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Requirements
|
||||||
|
|
||||||
|
- **Node.js** 18+
|
||||||
|
- **NPM** 9+
|
||||||
|
- **Linux** or **Windows** (via Git Bash)
|
||||||
|
|
||||||
|
> ⚠️ Android/iOS and Termux have experimental support with no guarantees.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 License
|
||||||
|
|
||||||
|
Distributed under the **GPLv3** license. See [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**[⬆ Back to top](#)**
|
||||||
|
|
||||||
|
</div>
|
||||||
219
TERMOS_pt-br.md
Normal file
219
TERMOS_pt-br.md
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
## Termos de Uso
|
||||||
|
|
||||||
|
> Este arquivo poderá ser movido no futuro, assim que o site oficial for lançado.
|
||||||
|
|
||||||
|
### 1. Natureza do Software
|
||||||
|
|
||||||
|
Este projeto é uma ferramenta de automação de código aberto para plataformas de mensagens.
|
||||||
|
|
||||||
|
Por padrão, o sistema principal não gera mídia nem conteúdo. No entanto, ele pode suportar plug-ins, extensões ou modificações que adicionem tais funcionalidades (incluindo a geração de figurinhas ou mídia).
|
||||||
|
|
||||||
|
Ele pode ser usado:
|
||||||
|
|
||||||
|
* Como uma instância auto-hospedada (local)
|
||||||
|
* Por meio de uma instância hospedada oficialmente operada pelo desenvolvedor
|
||||||
|
|
||||||
|
O desenvolvedor fornece este software estritamente como uma ferramenta técnica e não cria, controla ou endossa conteúdo gerado pelo usuário.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Âmbito de Responsabilidade
|
||||||
|
|
||||||
|
#### 2.1 Instância Hospedada Oficialmente
|
||||||
|
|
||||||
|
Ao usar a instância oficial:
|
||||||
|
|
||||||
|
* Mensagens e mídia podem passar pela infraestrutura do desenvolvedor
|
||||||
|
* O conteúdo é processado automaticamente e não é monitorado ativamente
|
||||||
|
* Os dados são processados e, em seguida, descartados permanentemente
|
||||||
|
|
||||||
|
O desenvolvedor não assume responsabilidade por qualquer conteúdo processado pelo sistema.
|
||||||
|
|
||||||
|
#### 2.2 Versões auto-hospedadas / modificadas
|
||||||
|
|
||||||
|
Se você executar, modificar ou distribuir sua própria versão:
|
||||||
|
|
||||||
|
* Você é totalmente responsável pela sua instância
|
||||||
|
* O desenvolvedor não tem controle sobre sua implantação
|
||||||
|
* Quaisquer plug-ins, modificações ou extensões são de sua responsabilidade
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Responsabilidade do usuário
|
||||||
|
|
||||||
|
Ao utilizar este software, você concorda que:
|
||||||
|
|
||||||
|
* Você é o único responsável por todo o conteúdo que gerar, processar ou compartilhar
|
||||||
|
* Você cumprirá todas as leis e regulamentos aplicáveis
|
||||||
|
* Você não utilizará este software para fins ilegais, prejudiciais ou abusivos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Usos proibidos
|
||||||
|
|
||||||
|
Os seguintes usos são estritamente proibidos:
|
||||||
|
|
||||||
|
* Atividades ilegais de qualquer tipo
|
||||||
|
* Conteúdo que viole as leis aplicáveis
|
||||||
|
* Conteúdo abusivo, prejudicial ou explorador
|
||||||
|
* Uso indevido da plataforma para prejudicar terceiros
|
||||||
|
|
||||||
|
O desenvolvedor não tolera tal uso, mesmo que não seja possível evitá-lo totalmente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Isenção de Responsabilidade
|
||||||
|
|
||||||
|
O desenvolvedor não se responsabiliza por:
|
||||||
|
|
||||||
|
* Qualquer conteúdo gerado pelos usuários
|
||||||
|
* Qualquer uso indevido do software
|
||||||
|
* Quaisquer danos ou consequências legais resultantes do uso
|
||||||
|
|
||||||
|
Isso inclui, mas não se limita a:
|
||||||
|
|
||||||
|
* Conteúdo ofensivo ou inadequado
|
||||||
|
* Conteúdo ilegal criado ou distribuído pelos usuários
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Ausência de monitoramento ativo
|
||||||
|
|
||||||
|
O sistema opera automaticamente e não é monitorado ativamente o tempo todo.
|
||||||
|
|
||||||
|
O desenvolvedor não é obrigado a revisar todas as atividades, mas pode tomar medidas quando for relatado ou detectado uso indevido.
|
||||||
|
|
||||||
|
O tempo de resposta não é garantido.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Denúncias e aplicação de medidas
|
||||||
|
|
||||||
|
Os usuários podem denunciar o uso indevido por meio do canal de contato fornecido (por exemplo, e-mail).
|
||||||
|
|
||||||
|
Ao receber denúncias válidas, o desenvolvedor pode, a seu exclusivo critério:
|
||||||
|
|
||||||
|
* Bloquear usuários
|
||||||
|
* Remover o bot de grupos
|
||||||
|
* Restringir o acesso ao serviço
|
||||||
|
|
||||||
|
Com exceção de instâncias auto-hospedadas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Tratamento de dados e privacidade
|
||||||
|
|
||||||
|
* Mensagens e arquivos de mídia podem ser processados temporariamente
|
||||||
|
* O conteúdo não é armazenado permanentemente pelo sistema principal
|
||||||
|
* Os registros podem incluir metadados e/ou conteúdo para fins operacionais
|
||||||
|
* Alguns plug-ins ou modificações de terceiros podem armazenar dados de forma independente
|
||||||
|
|
||||||
|
O desenvolvedor não se responsabiliza pelo tratamento de dados realizado por plug-ins de terceiros ou versões modificadas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Serviços de terceiros
|
||||||
|
|
||||||
|
Este software depende de plataformas e bibliotecas de terceiros (por exemplo, ferramentas de automação do WhatsApp Web).
|
||||||
|
|
||||||
|
O uso deste software pode violar os termos de serviço dessas plataformas.
|
||||||
|
|
||||||
|
O desenvolvedor não se responsabiliza por:
|
||||||
|
|
||||||
|
* Banimentos de conta
|
||||||
|
* Restrições de serviço
|
||||||
|
* Quaisquer consequências impostas por plataformas de terceiros
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Plug-ins e modificações
|
||||||
|
|
||||||
|
Este software suporta plug-ins e permite forks do código-fonte.
|
||||||
|
|
||||||
|
Esses plug-ins podem introduzir recursos, incluindo, mas não se limitando a, geração de mídia ou figurinhas.
|
||||||
|
|
||||||
|
O desenvolvedor:
|
||||||
|
|
||||||
|
* Analisa o código-fonte de todos os plug-ins no repositório oficial
|
||||||
|
* Não analisa bifurcações, versões de terceiros e repositórios
|
||||||
|
* Aprova apenas plug-ins que concordem com os termos
|
||||||
|
|
||||||
|
O uso de plug-ins e forks de terceiros é de sua responsabilidade.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Uso comercial
|
||||||
|
|
||||||
|
Este software é fornecido como freeware (software gratuito).
|
||||||
|
|
||||||
|
O uso comercial não é permitido, a menos que o software seja significativamente modificado e renomeado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 12. Marca e Identidade
|
||||||
|
|
||||||
|
O nome do projeto, a identidade e os elementos associados são de uso exclusivo.
|
||||||
|
|
||||||
|
É proibido o uso não autorizado da identidade, da marca ou da representação do projeto como uma instância oficial.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 13. Disponibilidade do Serviço oficial
|
||||||
|
|
||||||
|
O serviço hospedado oficial pode:
|
||||||
|
|
||||||
|
* Estar indisponível a qualquer momento
|
||||||
|
* Conter bugs ou erros
|
||||||
|
* Ser modificado ou descontinuado sem aviso prévio
|
||||||
|
|
||||||
|
Não são fornecidas garantias de tempo de atividade ou confiabilidade.
|
||||||
|
|
||||||
|
Alertas de alterações e manutenção são enviados para os chats onde o bot está instalado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 14. Cessação do Acesso
|
||||||
|
|
||||||
|
O desenvolvedor reserva-se o direito de:
|
||||||
|
|
||||||
|
* Bloquear usuários
|
||||||
|
* Negar acesso
|
||||||
|
* Desativar o serviço a qualquer momento
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 15. Ausência de Garantia
|
||||||
|
|
||||||
|
Este software é fornecido “no estado em que se encontra”, sem qualquer tipo de garantia.
|
||||||
|
|
||||||
|
Não são oferecidas garantias quanto a:
|
||||||
|
|
||||||
|
* Funcionalidade
|
||||||
|
* Disponibilidade
|
||||||
|
* Segurança
|
||||||
|
* Adequação a qualquer finalidade
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 16. Aceitação dos Termos
|
||||||
|
|
||||||
|
Ao utilizar este software ou qualquer instância oficial, você concorda com estes Termos de Uso.
|
||||||
|
|
||||||
|
Caso não concorde, você não deve utilizar o software.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 17. Alterações aos Termos
|
||||||
|
|
||||||
|
Estes termos podem ser atualizados a qualquer momento sem aviso prévio.
|
||||||
|
|
||||||
|
O uso continuado do software implica a aceitação dos termos atualizados, mesmo em versões antigas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 18. Jurisdição
|
||||||
|
|
||||||
|
Estes termos serão regidos e interpretados de acordo com as leis do Brasil.
|
||||||
|
|
||||||
|
Quaisquer disputas decorrentes do uso deste software estarão sujeitas à jurisdição aplicável no Brasil.
|
||||||
|
|
||||||
218
TERMS_en-us.md
Normal file
218
TERMS_en-us.md
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
## Terms of Use
|
||||||
|
|
||||||
|
> This is file may be moved in the future as the official webpage is launched.
|
||||||
|
|
||||||
|
### 1. Nature of the Software
|
||||||
|
|
||||||
|
This project is an open-source automation tool for messaging platforms.
|
||||||
|
|
||||||
|
By default, the core system does not generate media or content. However, it may support plugins, extensions, or modifications that add such functionality (including sticker or media generation).
|
||||||
|
|
||||||
|
It may be used:
|
||||||
|
|
||||||
|
* As a self-hosted (local) instance
|
||||||
|
* Through an official hosted instance operated by the developer
|
||||||
|
|
||||||
|
The developer provides this software strictly as a technical tool and does not create, control, or endorse user-generated content.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Scope of Responsibility
|
||||||
|
|
||||||
|
#### 2.1 Official Hosted Instance
|
||||||
|
|
||||||
|
When using the official hosted bot:
|
||||||
|
|
||||||
|
* Messages and media may pass through the developer's infrastructure
|
||||||
|
* Content is processed automatically and is not actively monitored
|
||||||
|
* Data is processed and then permanently discarded
|
||||||
|
|
||||||
|
The developer does not assume responsibility for any content processed by the system.
|
||||||
|
|
||||||
|
#### 2.2 Self-Hosted / Modified Versions
|
||||||
|
|
||||||
|
If you run, modify, or distribute your own version:
|
||||||
|
|
||||||
|
* You are fully responsible for your instance
|
||||||
|
* The developer has no control over your deployment
|
||||||
|
* Any plugins, modifications, or extensions are your responsibility
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. User Responsibility
|
||||||
|
|
||||||
|
By using this software, you agree that:
|
||||||
|
|
||||||
|
* You are solely responsible for all content you generate, process, or share
|
||||||
|
* You will comply with all applicable laws and regulations
|
||||||
|
* You will not use this software for illegal, harmful, or abusive purposes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. Prohibited Use
|
||||||
|
|
||||||
|
The following uses are strictly prohibited:
|
||||||
|
|
||||||
|
* Illegal activities of any kind
|
||||||
|
* Content that violates applicable laws
|
||||||
|
* Abusive, harmful, or exploitative content
|
||||||
|
* Misuse of the platform to harm others
|
||||||
|
|
||||||
|
The developer does not tolerate such use, even if it cannot be fully prevented.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. No Liability
|
||||||
|
|
||||||
|
The developer shall not be held liable for:
|
||||||
|
|
||||||
|
* Any content generated by users
|
||||||
|
* Any misuse of the software
|
||||||
|
* Any damages or legal consequences resulting from usage
|
||||||
|
|
||||||
|
This includes, but is not limited to:
|
||||||
|
|
||||||
|
* Offensive or inappropriate content
|
||||||
|
* Illegal content created or distributed by users
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. No Active Monitoring
|
||||||
|
|
||||||
|
The system operates automatically and is not actively monitored at all times.
|
||||||
|
|
||||||
|
The developer is not required to review all activity but may take action when misuse is reported or detected.
|
||||||
|
|
||||||
|
Response time is not guaranteed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. Reporting and Enforcement
|
||||||
|
|
||||||
|
Users may report misuse through the provided contact channel (e.g., email).
|
||||||
|
|
||||||
|
Upon receiving valid reports, the developer may, at their sole discretion:
|
||||||
|
|
||||||
|
* Block users
|
||||||
|
* Remove the bot from groups
|
||||||
|
* Restrict access to the service
|
||||||
|
|
||||||
|
Except for self-hosted instances.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. Data Handling and Privacy
|
||||||
|
|
||||||
|
* Messages and media may be temporarily processed
|
||||||
|
* Content is not permanently stored by the core system
|
||||||
|
* Logs may include metadata and/or content for operational purposes
|
||||||
|
* Some plugins or third-party modifications may store data independently
|
||||||
|
|
||||||
|
The developer is not responsible for data handling performed by third-party plugins or modified versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. Third-Party Services
|
||||||
|
|
||||||
|
This software depends on third-party platforms and libraries (e.g., WhatsApp Web automation tools).
|
||||||
|
|
||||||
|
Use of this software may violate the terms of service of such platforms.
|
||||||
|
|
||||||
|
The developer is not responsible for:
|
||||||
|
|
||||||
|
* Account bans
|
||||||
|
* Service restrictions
|
||||||
|
* Any consequences imposed by third-party platforms
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 10. Plugins and Modifications
|
||||||
|
|
||||||
|
This software supports plugins and allows forks fo the source code.
|
||||||
|
|
||||||
|
Such plugins may introduce features including, but not limited to, media or sticker generation.
|
||||||
|
|
||||||
|
The developer:
|
||||||
|
|
||||||
|
* Reviews the source code of all plugins on the official repository
|
||||||
|
* Does not reviews forks and third-party versions and repositories
|
||||||
|
* Approves only plugins who agree with the terms
|
||||||
|
|
||||||
|
The use of plugins and third-party forks is your responsibility.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. Commercial Use
|
||||||
|
|
||||||
|
This software is provided as freeware.
|
||||||
|
|
||||||
|
Commercial use is not permitted unless the software is significantly modified and rebranded.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 12. Branding and Identity
|
||||||
|
|
||||||
|
The project name, identity, and associated elements are reserved.
|
||||||
|
|
||||||
|
Unauthorized use of the project's identity, branding, or representation as an official instance is prohibited.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 13. Service Availability
|
||||||
|
|
||||||
|
The official hosted service may:
|
||||||
|
|
||||||
|
* Be unavailable at any time
|
||||||
|
* Contain bugs or errors
|
||||||
|
* Be modified or discontinued
|
||||||
|
|
||||||
|
No uptime or reliability guarantees are provided.
|
||||||
|
|
||||||
|
Change and maintenance alerts are sent to the chats where the bot is installed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 14. Termination of Access
|
||||||
|
|
||||||
|
The developer reserves the right to:
|
||||||
|
|
||||||
|
* Block users
|
||||||
|
* Deny access
|
||||||
|
* Shut down the service at any time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 15. No Warranty
|
||||||
|
|
||||||
|
This software is provided "as is", without warranty of any kind.
|
||||||
|
|
||||||
|
No guarantees are made regarding:
|
||||||
|
|
||||||
|
* Functionality
|
||||||
|
* Availability
|
||||||
|
* Security
|
||||||
|
* Fitness for any purpose
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 16. Acceptance of Terms
|
||||||
|
|
||||||
|
By using this software or any official instance, you agree to these Terms of Use.
|
||||||
|
|
||||||
|
If you do not agree, you must not use the software.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 17. Changes to Terms
|
||||||
|
|
||||||
|
These terms may be updated at any time without prior notice.
|
||||||
|
|
||||||
|
Continued use of the software implies acceptance of the updated terms, even in old versions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 18. Jurisdiction
|
||||||
|
|
||||||
|
These terms shall be governed by and interpreted in accordance with the laws of Brazil.
|
||||||
|
|
||||||
|
Any disputes arising from the use of this software shall be subject to the applicable jurisdiction within Brazil.
|
||||||
361
deploy.sh
361
deploy.sh
@@ -1,35 +1,348 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
# ferramenta de desenvolvimento apenas, pode apagar se quiser
|
# =============================================================================
|
||||||
# ./deploy <commit> <branch> <version (if branch=master)>
|
# deploy — script de release com gates de qualidade e versionamento semântico
|
||||||
|
#
|
||||||
|
# Uso:
|
||||||
|
# ./deploy → commit automático + push (branch atual)
|
||||||
|
# ./deploy --release patch → bump patch + tag + push para master
|
||||||
|
# ./deploy --release minor → bump minor + tag + push para master
|
||||||
|
# ./deploy --release major → bump major + tag + push para master
|
||||||
|
# ./deploy --dry-run → simula tudo sem executar nada
|
||||||
|
# ./deploy --help → mostra esta ajuda
|
||||||
|
#
|
||||||
|
# Conventional Commits esperados:
|
||||||
|
# feat: nova funcionalidade → candidate a minor bump
|
||||||
|
# fix: correção de bug → candidate a patch bump
|
||||||
|
# docs: só documentação → sem bump de versão
|
||||||
|
# chore: manutenção → sem bump de versão
|
||||||
|
# BREAKING CHANGE: no rodapé → candidate a major bump
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
COMMIT_MSG="$1"
|
# ──────────────────────────────────────────
|
||||||
BRANCH="$2"
|
# Cores e utilitários
|
||||||
VERSION="$3"
|
# ──────────────────────────────────────────
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
if [ -z "$COMMIT_MSG" ] || [ -z "$BRANCH" ]; then
|
info() { echo -e "${BLUE}▸${RESET} $*"; }
|
||||||
echo "Uso: ./deploy <commit> <branch> [version if branch=master]"
|
success() { echo -e "${GREEN}✔${RESET} $*"; }
|
||||||
exit 1
|
warn() { echo -e "${YELLOW}⚠${RESET} $*"; }
|
||||||
|
error() { echo -e "${RED}✖${RESET} $*" >&2; }
|
||||||
|
die() { error "$*"; exit 1; }
|
||||||
|
step() { echo -e "\n${BOLD}$*${RESET}"; }
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# Argumentos
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
RELEASE_TYPE=""
|
||||||
|
DRY_RUN=false
|
||||||
|
|
||||||
|
show_help() {
|
||||||
|
sed -n '/^# Uso:/,/^# =/p' "$0" | grep '^#' | sed 's/^# \?//'
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--release)
|
||||||
|
[[ -z "${2-}" ]] && die "--release requer: patch | minor | major"
|
||||||
|
RELEASE_TYPE="$2"
|
||||||
|
[[ "$RELEASE_TYPE" =~ ^(patch|minor|major)$ ]] || die "Tipo inválido: '$RELEASE_TYPE'. Use patch, minor ou major."
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--dry-run) DRY_RUN=true; shift ;;
|
||||||
|
--help|-h) show_help ;;
|
||||||
|
*) die "Argumento desconhecido: '$1'. Use --help." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# Wrapper para dry-run
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
run() {
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo -e " ${YELLOW}[dry-run]${RESET} $*"
|
||||||
|
else
|
||||||
|
"$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# GATE 1 — Ambiente Git
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
step "[ 1/6 ] Verificando ambiente Git..."
|
||||||
|
|
||||||
|
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|
||||||
|
|| die "Não é um repositório Git."
|
||||||
|
|
||||||
|
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null) \
|
||||||
|
|| die "Não foi possível detectar a branch atual (HEAD detached?)."
|
||||||
|
|
||||||
|
# Releases só saem da master/main
|
||||||
|
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||||
|
[[ "$BRANCH" =~ ^(master|main)$ ]] \
|
||||||
|
|| die "--release só pode ser executado na branch master/main (atual: $BRANCH)."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# mudar para a branch
|
success "Branch: ${BOLD}$BRANCH${RESET}"
|
||||||
git checkout $BRANCH || { echo "Error ao change to $BRANCH"; exit 1; }
|
|
||||||
|
|
||||||
# se for master, atualizar versão
|
# ──────────────────────────────────────────
|
||||||
if [ "$BRANCH" == "master" ] && [ -n "$VERSION" ]; then
|
# GATE 2 — Working tree limpa
|
||||||
echo "Updating version to $VERSION"
|
# ──────────────────────────────────────────
|
||||||
git tag $VERSION
|
function worktree() {
|
||||||
npm version $VERSION --no-git-tag-version
|
# Em modo release, working tree tem de estar absolutamente limpa
|
||||||
git add package.json
|
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||||
git commit -m "Bump version to $VERSION"
|
warn "Arquivos não commitados:"
|
||||||
git push origin $VERSION
|
[[ -n "$STAGED" ]] && echo -e " ${GREEN}Staged:${RESET}\n$(echo "$STAGED" | sed 's/^/ /')"
|
||||||
|
[[ -n "$MODIFIED" ]] && echo -e " ${YELLOW}Modificados:${RESET}\n$(echo "$MODIFIED" | sed 's/^/ /')"
|
||||||
|
[[ -n "$UNTRACKED" ]] && echo -e " ${RED}Não rastreados:${RESET}\n$(echo "$UNTRACKED" | sed 's/^/ /')"
|
||||||
|
die "Working tree suja. Faça commit ou stash antes de um release."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Seletor interativo de arquivos ──────────────────────────────────────
|
||||||
|
# Monta lista indexada: staged (S), modificados (M), não rastreados (?)
|
||||||
|
declare -a ALL_FILES
|
||||||
|
declare -a ALL_LABELS
|
||||||
|
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${GREEN}staged${RESET}"); done <<< "$STAGED"
|
||||||
|
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${YELLOW}modificado${RESET}"); done <<< "$MODIFIED"
|
||||||
|
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${RED}novo${RESET}"); done <<< "$UNTRACKED"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo -e "${BOLD}Mudanças detectadas:${RESET}"
|
||||||
|
for i in "${!ALL_FILES[@]}"; do
|
||||||
|
printf " %2d) %b %s\n" "$((i+1))" "${ALL_LABELS[$i]}" "${ALL_FILES[$i]}"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
declare -a SELECTED_FILES
|
||||||
|
|
||||||
|
echo -e "Digite os números para adicionar ao commit, ${BOLD}all${RESET} para todos, ou ${BOLD}done${RESET} para encerrar:"
|
||||||
|
while true; do
|
||||||
|
echo -ne "${BLUE}▸${RESET} "
|
||||||
|
read -r INPUT
|
||||||
|
|
||||||
|
case "$INPUT" in
|
||||||
|
done|"")
|
||||||
|
[[ ${#SELECTED_FILES[@]} -eq 0 ]] && die "Nenhum arquivo selecionado. Operação cancelada."
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
all)
|
||||||
|
SELECTED_FILES=("${ALL_FILES[@]}")
|
||||||
|
echo -e " ${GREEN}✔${RESET} Todos os arquivos adicionados."
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
# Aceita múltiplos números na mesma linha (ex: "1 3 5")
|
||||||
|
for TOKEN in $INPUT; do
|
||||||
|
if [[ "$TOKEN" =~ ^[0-9]+$ ]] && (( TOKEN >= 1 && TOKEN <= ${#ALL_FILES[@]} )); then
|
||||||
|
FILE="${ALL_FILES[$((TOKEN-1))]}"
|
||||||
|
# Evita duplicatas
|
||||||
|
if printf '%s\n' "${SELECTED_FILES[@]+"${SELECTED_FILES[@]}"}" | grep -qxF "$FILE"; then
|
||||||
|
warn "$FILE já está na lista."
|
||||||
|
else
|
||||||
|
SELECTED_FILES+=("$FILE")
|
||||||
|
echo -e " ${GREEN}✔${RESET} adicionado: ${BOLD}$FILE${RESET}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Entrada inválida: '$TOKEN' — use um número entre 1 e ${#ALL_FILES[@]}, 'all' ou 'done'."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "Arquivos no commit:"
|
||||||
|
for f in "${SELECTED_FILES[@]}"; do echo " $f"; done
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# ── Mensagem de commit ──────────────────────────────────────────────────
|
||||||
|
echo "Tipos de commit (Conventional Commits):"
|
||||||
|
echo " feat → nova funcionalidade"
|
||||||
|
echo " fix → correção de bug"
|
||||||
|
echo " docs → só documentação"
|
||||||
|
echo " refactor → refatoração sem mudança de comportamento"
|
||||||
|
echo " test → adição/correção de testes"
|
||||||
|
echo " chore → manutenção, dependências, CI"
|
||||||
|
echo " style → formatação, whitespace"
|
||||||
|
echo ""
|
||||||
|
echo -n "Tipo: "
|
||||||
|
read -r COMMIT_TYPE
|
||||||
|
[[ "$COMMIT_TYPE" =~ ^(feat|fix|docs|refactor|test|chore|style)$ ]] \
|
||||||
|
|| die "Tipo inválido. Use um dos listados acima."
|
||||||
|
|
||||||
|
echo -n "Escopo (opcional, ex: auth, api, ui — Enter para pular): "
|
||||||
|
read -r COMMIT_SCOPE
|
||||||
|
|
||||||
|
echo -n "Descrição curta: "
|
||||||
|
read -r COMMIT_DESC
|
||||||
|
[[ -z "$COMMIT_DESC" ]] && die "Descrição não pode ser vazia."
|
||||||
|
|
||||||
|
echo -n "Há breaking changes? [s/N]: "
|
||||||
|
read -r BREAKING
|
||||||
|
BREAKING_FOOTER=""
|
||||||
|
if [[ "$BREAKING" =~ ^[sS]$ ]]; then
|
||||||
|
echo -n "Descreva o breaking change: "
|
||||||
|
read -r BREAKING_MSG
|
||||||
|
BREAKING_FOOTER=$'\n\nBREAKING CHANGE: '"$BREAKING_MSG"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -n "$COMMIT_SCOPE" ]]; then
|
||||||
|
COMMIT_MSG="${COMMIT_TYPE}(${COMMIT_SCOPE}): ${COMMIT_DESC}${BREAKING_FOOTER}"
|
||||||
|
else
|
||||||
|
COMMIT_MSG="${COMMIT_TYPE}: ${COMMIT_DESC}${BREAKING_FOOTER}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "Mensagem: ${BOLD}${COMMIT_MSG}${RESET}"
|
||||||
|
|
||||||
|
for f in "${SELECTED_FILES[@]}"; do run git add -- "$f"; done
|
||||||
|
run git commit -m "$COMMIT_MSG"
|
||||||
|
success "Commit criado."
|
||||||
|
}
|
||||||
|
|
||||||
|
step "[ 2/6 ] Verificando working tree..."
|
||||||
|
|
||||||
|
function thereschanges(){
|
||||||
|
UNTRACKED=$(git ls-files --others --exclude-standard)
|
||||||
|
MODIFIED=$(git diff --name-only)
|
||||||
|
STAGED=$(git diff --cached --name-only)
|
||||||
|
if [[ -n "$UNTRACKED" || -n "$MODIFIED" || -n "$STAGED" ]]; then
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
if thereschanges; then
|
||||||
|
worktree
|
||||||
|
if thereschanges; then
|
||||||
|
echo -n "Deseja criar outro commit? [s/N]: "
|
||||||
|
read -r OTHERCOMMIT
|
||||||
|
[[ "$OTHERCOMMIT" =~ ^[sS]$ ]] && worktree
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
success "Working tree limpa."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# adicionar alterações e commit
|
# ──────────────────────────────────────────
|
||||||
git add .
|
# GATE 3 — Testes e qualidade
|
||||||
git commit -m "$COMMIT_MSG"
|
# ──────────────────────────────────────────
|
||||||
|
step "[ 3/6 ] Executando gates de qualidade..."
|
||||||
|
|
||||||
# push
|
run_if_exists() {
|
||||||
git push origin $BRANCH
|
local label="$1"
|
||||||
|
local cmd="$2"
|
||||||
|
if eval "$cmd" >/dev/null 2>&1; then
|
||||||
|
info "$label encontrado. Executando..."
|
||||||
|
if $DRY_RUN; then
|
||||||
|
echo -e " ${YELLOW}[dry-run]${RESET} $cmd"
|
||||||
|
else
|
||||||
|
eval "$cmd" || die "$label falhou. Corrija antes de continuar."
|
||||||
|
fi
|
||||||
|
success "$label passou."
|
||||||
|
else
|
||||||
|
warn "$label não encontrado — pulando."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
echo "Deploy completed."
|
# Lint
|
||||||
|
if [[ -f "package.json" ]]; then
|
||||||
|
if node -e "require('./package.json').scripts?.lint" >/dev/null 2>&1; then
|
||||||
|
run_if_exists "Lint (npm)" "npm run lint --if-present"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
[[ -f ".flake8" || -f "setup.cfg" || -f "pyproject.toml" ]] && \
|
||||||
|
run_if_exists "Lint (flake8)" "command -v flake8 && flake8 ."
|
||||||
|
|
||||||
|
# Testes
|
||||||
|
run_if_exists "Testes (npm)" "node -e \"require('./package.json').scripts?.test\" && npm test --if-present"
|
||||||
|
|
||||||
|
# Build (só bloqueia em release)
|
||||||
|
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||||
|
run_if_exists "Build (npm)" "node -e \"require('./package.json').scripts?.build\" && npm run build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
success "Gates de qualidade concluídos."
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# GATE 4 — Sincronia com remoto
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
step "[ 4/6 ] Verificando sincronia com remoto..."
|
||||||
|
|
||||||
|
if git remote get-url origin >/dev/null 2>&1; then
|
||||||
|
run git fetch origin --quiet
|
||||||
|
|
||||||
|
LOCAL=$(git rev-parse HEAD)
|
||||||
|
REMOTE=$(git rev-parse "origin/$BRANCH" 2>/dev/null || echo "")
|
||||||
|
|
||||||
|
if [[ -n "$REMOTE" && "$LOCAL" != "$REMOTE" ]]; then
|
||||||
|
BEHIND=$(git rev-list --count HEAD.."origin/$BRANCH")
|
||||||
|
AHEAD=$(git rev-list --count "origin/$BRANCH"..HEAD)
|
||||||
|
|
||||||
|
[[ "$BEHIND" -gt 0 ]] && die "Branch está $BEHIND commit(s) atrás do remoto. Faça pull antes."
|
||||||
|
[[ "$AHEAD" -gt 0 ]] && info "Branch está $AHEAD commit(s) à frente do remoto — ok, será enviado."
|
||||||
|
else
|
||||||
|
success "Branch sincronizada com o remoto."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Nenhum remoto 'origin' configurado — pulando verificação."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# GATE 5 — Versionamento (somente --release)
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||||
|
step "[ 5/6 ] Versionando release ($RELEASE_TYPE)..."
|
||||||
|
|
||||||
|
if [[ -f "package.json" ]]; then
|
||||||
|
CURRENT_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "desconhecida")
|
||||||
|
info "Versão atual: ${BOLD}$CURRENT_VERSION${RESET}"
|
||||||
|
run npm version "$RELEASE_TYPE" -m "chore(release): %s"
|
||||||
|
NEW_VERSION=$(node -p "require('./package.json').version" 2>/dev/null || echo "nova")
|
||||||
|
success "Nova versão: ${BOLD}$NEW_VERSION${RESET}"
|
||||||
|
else
|
||||||
|
# Fallback: tag Git manual
|
||||||
|
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
|
info "Última tag: $LAST_TAG"
|
||||||
|
IFS='.' read -r MAJOR MINOR PATCH <<< "${LAST_TAG#v}"
|
||||||
|
case "$RELEASE_TYPE" in
|
||||||
|
major) MAJOR=$((MAJOR+1)); MINOR=0; PATCH=0 ;;
|
||||||
|
minor) MINOR=$((MINOR+1)); PATCH=0 ;;
|
||||||
|
patch) PATCH=$((PATCH+1)) ;;
|
||||||
|
esac
|
||||||
|
NEW_TAG="v${MAJOR}.${MINOR}.${PATCH}"
|
||||||
|
run git tag -a "$NEW_TAG" -m "chore(release): $NEW_TAG"
|
||||||
|
success "Tag criada: ${BOLD}$NEW_TAG${RESET}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
step "[ 5/6 ] Versionamento..."
|
||||||
|
info "Modo push simples (sem --release). Nenhuma tag será criada."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# GATE 6 — Push
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
step "[ 6/6 ] Enviando para o remoto..."
|
||||||
|
|
||||||
|
run git push origin "$BRANCH" --follow-tags
|
||||||
|
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
# Resumo final
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
echo ""
|
||||||
|
echo -e "${GREEN}${BOLD}═════════════════════════════════${RESET}"
|
||||||
|
echo -e "${GREEN}${BOLD} Deploy concluído com sucesso!${RESET}"
|
||||||
|
echo -e "${GREEN}${BOLD}═════════════════════════════════${RESET}"
|
||||||
|
echo -e " Branch : ${BOLD}$BRANCH${RESET}"
|
||||||
|
[[ -n "$RELEASE_TYPE" ]] && \
|
||||||
|
echo -e " Release: ${BOLD}$RELEASE_TYPE${RESET} → ${BOLD}${NEW_VERSION:-$NEW_TAG}${RESET}"
|
||||||
|
$DRY_RUN && echo -e "\n ${YELLOW}(dry-run: nenhuma alteração foi feita de fato)${RESET}"
|
||||||
|
echo ""
|
||||||
|
|||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
manybot:
|
||||||
|
build: .
|
||||||
|
container_name: manybot
|
||||||
|
volumes:
|
||||||
|
- ./session:/app/session
|
||||||
|
- ./manybot.conf:/app/manybot.conf:ro
|
||||||
|
- ./src/plugins:/app/src/plugins
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
stdin_open: true
|
||||||
|
tty: true
|
||||||
|
restart: unless-stopped
|
||||||
277
docs/API (en).md
Normal file
277
docs/API (en).md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# 🛠️ API Reference
|
||||||
|
|
||||||
|
Complete documentation of objects available in plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The `msg` Object
|
||||||
|
|
||||||
|
Contains information about the received message.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `msg.body` | `string` | Full text of the message |
|
||||||
|
| `msg.args` | `string[]` | Message tokens. E.g.: `"!video url"` → `["!video", "url"]` |
|
||||||
|
| `msg.type` | `string` | Message type: `chat`, `image`, `video`, `audio`, `sticker`, `ptt` (voice), `document`, `location` |
|
||||||
|
| `msg.sender` | `string` | Sender ID (format: `NUMBER@c.us` or `NUMBER@g.us`) |
|
||||||
|
| `msg.senderName` | `string` | Display name of the sender |
|
||||||
|
| `msg.fromMe` | `boolean` | `true` if the bot itself sent the message |
|
||||||
|
| `msg.hasMedia` | `boolean` | `true` if the message contains media |
|
||||||
|
| `msg.hasReply` | `boolean` | `true` if the message is a reply to another |
|
||||||
|
| `msg.isGif` | `boolean` | `true` if the media is a GIF |
|
||||||
|
| `msg.timestamp` | `number` | Unix timestamp of the message |
|
||||||
|
|
||||||
|
### Methods
|
||||||
|
|
||||||
|
#### `msg.is(cmd)`
|
||||||
|
|
||||||
|
Checks whether the message starts with the specified command.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (msg.is(CMD_PREFIX + "video")) {
|
||||||
|
// Execute command
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `msg.reply(text)`
|
||||||
|
|
||||||
|
Replies to the current message with a quote (citation).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await msg.reply("Reply with citation!");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `text` (string): Text of the reply
|
||||||
|
|
||||||
|
**Returns:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `msg.downloadMedia()`
|
||||||
|
|
||||||
|
Downloads the media from the message.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
// Returns: { mimetype: "image/jpeg", data: "base64string..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `Promise<{ mimetype: string, data: string } | null>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `msg.getReply()`
|
||||||
|
|
||||||
|
Returns the message that was quoted.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const quotedMsg = msg.getReply();
|
||||||
|
if (quotedMsg) {
|
||||||
|
console.log(quotedMsg.body);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Returns:** `msg object | null`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## The `api` Object
|
||||||
|
|
||||||
|
Contains methods for interacting with WhatsApp and other plugins.
|
||||||
|
|
||||||
|
### Properties
|
||||||
|
|
||||||
|
#### `api.chat`
|
||||||
|
|
||||||
|
Information about the current chat.
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `api.chat.id` | `string` | Chat ID |
|
||||||
|
| `api.chat.name` | `string` | Chat name |
|
||||||
|
| `api.chat.isGroup` | `boolean` | `true` if it is a group |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Send Methods
|
||||||
|
|
||||||
|
#### `api.send(text)`
|
||||||
|
|
||||||
|
Sends a text message.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.send("Message sent!");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `text` (string): Text to send
|
||||||
|
|
||||||
|
**Returns:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendVideo(filePath)`
|
||||||
|
|
||||||
|
Sends a video from the file system.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.sendVideo("/path/to/video.mp4");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `filePath` (string): Path to the file
|
||||||
|
|
||||||
|
**Returns:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendAudio(filePath)`
|
||||||
|
|
||||||
|
Sends audio as a voice message (PTT).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.sendAudio("/path/to/audio.mp3");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `filePath` (string): Path to the file
|
||||||
|
|
||||||
|
**Returns:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendImage(filePath, caption?)`
|
||||||
|
|
||||||
|
Sends an image.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.sendImage("/path/to/image.jpg", "Optional caption");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `filePath` (string): Path to the file
|
||||||
|
- `caption` (string, optional): Image caption
|
||||||
|
|
||||||
|
**Returns:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendSticker(bufferOrPath)`
|
||||||
|
|
||||||
|
Sends a sticker. Accepts a `Buffer` or a file path.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// With Buffer
|
||||||
|
const buffer = fs.readFileSync("image.png");
|
||||||
|
await api.sendSticker(buffer);
|
||||||
|
|
||||||
|
// With path
|
||||||
|
await api.sendSticker("/path/to/image.png");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `bufferOrPath` (`Buffer` | `string`): Image data or file path
|
||||||
|
|
||||||
|
**Returns:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Plugin Methods
|
||||||
|
|
||||||
|
#### `api.getPlugin(name)`
|
||||||
|
|
||||||
|
Accesses the public API of another plugin.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const utils = api.getPlugin("utilities");
|
||||||
|
const data = utils.formatDate(new Date());
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `name` (string): Plugin name
|
||||||
|
|
||||||
|
**Returns:** `object | undefined`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Log Methods
|
||||||
|
|
||||||
|
#### `api.log.info(...args)`
|
||||||
|
|
||||||
|
Informational log.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
api.log.info("Message received:", msg.body);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api.log.warn(...args)`
|
||||||
|
|
||||||
|
Warning log.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
api.log.warn("Missing config, using default");
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api.log.error(...args)`
|
||||||
|
|
||||||
|
Error log.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
api.log.error("Failed to process:", error);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configuration Object
|
||||||
|
|
||||||
|
Import settings from `manybot.conf`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { CMD_PREFIX, CLIENT_ID, CHATS, PLUGINS } from "../../config.js";
|
||||||
|
|
||||||
|
// Custom configurations also work
|
||||||
|
import { MY_PREFIX } from "../../config.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
// Ignore messages from the bot itself
|
||||||
|
if (msg.fromMe) return;
|
||||||
|
|
||||||
|
// Command: !echo
|
||||||
|
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||||
|
|
||||||
|
api.log.info("Echo command received from:", msg.senderName);
|
||||||
|
|
||||||
|
// If it has media, download and resend
|
||||||
|
if (msg.hasMedia) {
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
await api.sendSticker(media.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a reply, echo the quoted message
|
||||||
|
if (msg.hasReply) {
|
||||||
|
const quoted = msg.getReply();
|
||||||
|
await msg.reply(`You quoted: "${quoted.body}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default response
|
||||||
|
await msg.reply("Send media or reply to a message!");
|
||||||
|
}
|
||||||
|
```
|
||||||
277
docs/API.md
Normal file
277
docs/API.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# 🛠️ Referência da API
|
||||||
|
|
||||||
|
Documentação completa dos objetos disponíveis nos plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objeto `msg`
|
||||||
|
|
||||||
|
Contém informações da mensagem recebida.
|
||||||
|
|
||||||
|
### Propriedades
|
||||||
|
|
||||||
|
| Propriedade | Tipo | Descrição |
|
||||||
|
|-------------|------|-----------|
|
||||||
|
| `msg.body` | `string` | Texto completo da mensagem |
|
||||||
|
| `msg.args` | `string[]` | Tokens da mensagem. Ex: `"!video url"` → `["!video", "url"]` |
|
||||||
|
| `msg.type` | `string` | Tipo da mensagem: `chat`, `image`, `video`, `audio`, `sticker`, `ptt` (voz), `document`, `location` |
|
||||||
|
| `msg.sender` | `string` | ID do remetente (formato: `NUMERO@c.us` ou `NUMERO@g.us`) |
|
||||||
|
| `msg.senderName` | `string` | Nome de exibição do remetente |
|
||||||
|
| `msg.fromMe` | `boolean` | `true` se o próprio bot enviou a mensagem |
|
||||||
|
| `msg.hasMedia` | `boolean` | `true` se a mensagem contém mídia |
|
||||||
|
| `msg.hasReply` | `boolean` | `true` se é uma resposta a outra mensagem |
|
||||||
|
| `msg.isGif` | `boolean` | `true` se a mídia é um GIF |
|
||||||
|
| `msg.timestamp` | `number` | Timestamp Unix da mensagem |
|
||||||
|
|
||||||
|
### Métodos
|
||||||
|
|
||||||
|
#### `msg.is(cmd)`
|
||||||
|
|
||||||
|
Verifica se a mensagem começa com o comando especificado.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (msg.is(CMD_PREFIX + "video")) {
|
||||||
|
// Executa comando
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Retorno:** `boolean`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `msg.reply(text)`
|
||||||
|
|
||||||
|
Responde à mensagem atual com quote (citação).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await msg.reply("Resposta com citação!");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `text` (string): Texto da resposta
|
||||||
|
|
||||||
|
**Retorno:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `msg.downloadMedia()`
|
||||||
|
|
||||||
|
Baixa a mídia da mensagem.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
// Retorna: { mimetype: "image/jpeg", data: "base64string..." }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Retorno:** `Promise<{ mimetype: string, data: string } | null>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `msg.getReply()`
|
||||||
|
|
||||||
|
Retorna a mensagem que foi citada.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const mensagemCitada = msg.getReply();
|
||||||
|
if (mensagemCitada) {
|
||||||
|
console.log(mensagemCitada.body);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Retorno:** `msg object | null`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objeto `api`
|
||||||
|
|
||||||
|
Contém métodos para interagir com o WhatsApp e outros plugins.
|
||||||
|
|
||||||
|
### Propriedades
|
||||||
|
|
||||||
|
#### `api.chat`
|
||||||
|
|
||||||
|
Informações do chat atual.
|
||||||
|
|
||||||
|
| Propriedade | Tipo | Descrição |
|
||||||
|
|-------------|------|-----------|
|
||||||
|
| `api.chat.id` | `string` | ID do chat |
|
||||||
|
| `api.chat.name` | `string` | Nome do chat |
|
||||||
|
| `api.chat.isGroup` | `boolean` | `true` se é grupo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Métodos de Envio
|
||||||
|
|
||||||
|
#### `api.send(text)`
|
||||||
|
|
||||||
|
Envia uma mensagem de texto.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.send("Mensagem enviada!");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `text` (string): Texto a enviar
|
||||||
|
|
||||||
|
**Retorno:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendVideo(filePath)`
|
||||||
|
|
||||||
|
Envia um vídeo do sistema de arquivos.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.sendVideo("/caminho/para/video.mp4");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `filePath` (string): Caminho para o arquivo
|
||||||
|
|
||||||
|
**Retorno:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendAudio(filePath)`
|
||||||
|
|
||||||
|
Envia um áudio como mensagem de voz (PTT).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.sendAudio("/caminho/para/audio.mp3");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `filePath` (string): Caminho para o arquivo
|
||||||
|
|
||||||
|
**Retorno:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendImage(filePath, caption?)`
|
||||||
|
|
||||||
|
Envia uma imagem.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
await api.sendImage("/caminho/para/imagem.jpg", "Legenda opcional");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `filePath` (string): Caminho para o arquivo
|
||||||
|
- `caption` (string, opcional): Legenda da imagem
|
||||||
|
|
||||||
|
**Retorno:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### `api.sendSticker(bufferOuPath)`
|
||||||
|
|
||||||
|
Envia uma figurinha. Aceita `Buffer` ou caminho para arquivo.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Com Buffer
|
||||||
|
const buffer = fs.readFileSync("imagem.png");
|
||||||
|
await api.sendSticker(buffer);
|
||||||
|
|
||||||
|
// Com caminho
|
||||||
|
await api.sendSticker("/caminho/para/imagem.png");
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `bufferOuPath` (`Buffer` | `string`): Dados da imagem ou caminho
|
||||||
|
|
||||||
|
**Retorno:** `Promise<void>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Métodos de Plugin
|
||||||
|
|
||||||
|
#### `api.getPlugin(name)`
|
||||||
|
|
||||||
|
Acessa a API pública de outro plugin.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const utils = api.getPlugin("utilidades");
|
||||||
|
const data = utils.formatarData(new Date());
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parâmetros:**
|
||||||
|
- `name` (string): Nome do plugin
|
||||||
|
|
||||||
|
**Retorno:** `object | undefined`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Métodos de Log
|
||||||
|
|
||||||
|
#### `api.log.info(...args)`
|
||||||
|
|
||||||
|
Log informativo.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
api.log.info("Mensagem recebida:", msg.body);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api.log.warn(...args)`
|
||||||
|
|
||||||
|
Log de aviso.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
api.log.warn("Configuração ausente, usando padrão");
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `api.log.error(...args)`
|
||||||
|
|
||||||
|
Log de erro.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
api.log.error("Falha ao processar:", erro);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objeto de Configuração
|
||||||
|
|
||||||
|
Importe configurações do `manybot.conf`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { CMD_PREFIX, CLIENT_ID, CHATS, PLUGINS } from "../../config.js";
|
||||||
|
|
||||||
|
// Configurações personalizadas também funcionam
|
||||||
|
import { MEU_PREFIXO } from "../../config.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exemplo Completo
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
import fs from "fs";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
// Ignora mensagens do próprio bot
|
||||||
|
if (msg.fromMe) return;
|
||||||
|
|
||||||
|
// Comando: !eco
|
||||||
|
if (!msg.is(CMD_PREFIX + "eco")) return;
|
||||||
|
|
||||||
|
api.log.info("Comando eco recebido de:", msg.senderName);
|
||||||
|
|
||||||
|
// Se tem mídia, baixa e reenvia
|
||||||
|
if (msg.hasMedia) {
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
await api.sendSticker(media.data);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se é resposta, ecoa a mensagem citada
|
||||||
|
if (msg.hasReply) {
|
||||||
|
const citada = msg.getReply();
|
||||||
|
await msg.reply(`Você citou: "${citada.body}"`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resposta padrão
|
||||||
|
await msg.reply("Envie uma mídia ou responda uma mensagem!");
|
||||||
|
}
|
||||||
|
```
|
||||||
146
docs/CONFIGURACAO.md
Normal file
146
docs/CONFIGURACAO.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# ⚙️ Configuração
|
||||||
|
|
||||||
|
Guia completo do arquivo `manybot.conf`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estrutura Básica
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Comentários começam com '#'
|
||||||
|
|
||||||
|
CLIENT_ID=bot_permanente
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=pt
|
||||||
|
CHATS=[]
|
||||||
|
PLUGINS=[]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Opções
|
||||||
|
|
||||||
|
### CLIENT_ID
|
||||||
|
|
||||||
|
Identificador único da sessão do bot.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLIENT_ID=bot_permanente
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Padrão:** `bot_permanente`
|
||||||
|
- **Uso:** Cria uma pasta `session/` com esse nome para armazenar dados de autenticação
|
||||||
|
|
||||||
|
### CMD_PREFIX
|
||||||
|
|
||||||
|
Caractere que indica o início de um comando.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CMD_PREFIX=!
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Padrão:** `!`
|
||||||
|
- **Exemplo:** Com prefixo `!`, o comando é `!figurinha`. Com `.`, seria `.figurinha`.
|
||||||
|
|
||||||
|
### LANGUAGE
|
||||||
|
|
||||||
|
Idioma das mensagens do bot.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LANGUAGE=pt
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Padrão:** `en` (inglês)
|
||||||
|
- **Opções:** `en` (inglês), `pt` (português), `es` (espanhol)
|
||||||
|
- **Nota:** Se o idioma selecionado não existir, o bot usará inglês como fallback
|
||||||
|
|
||||||
|
### CHATS
|
||||||
|
|
||||||
|
Lista de IDs de chats onde o bot responderá.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CHATS=[
|
||||||
|
123456789@c.us, # Chat privado
|
||||||
|
123456789@g.us # Grupo
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Padrão:** `[]` (vazio = responde em todos)
|
||||||
|
- **Formato:**
|
||||||
|
- Privado: `NUMERO@c.us`
|
||||||
|
- Grupo: `NUMERO@g.us`
|
||||||
|
|
||||||
|
#### Como descobrir o ID
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node src/utils/get_id.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Escaneie o QR Code e mande uma mensagem no chat. O ID aparecerá no terminal.
|
||||||
|
|
||||||
|
> Nota: O utilitário usa um `CLIENT_ID` separado para não conflitar com a sessão principal.
|
||||||
|
|
||||||
|
### PLUGINS
|
||||||
|
|
||||||
|
Lista de plugins a serem carregados.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PLUGINS=[
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
figurinha,
|
||||||
|
adivinhacao,
|
||||||
|
forca,
|
||||||
|
many,
|
||||||
|
obrigado
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Padrão:** `[]` (nenhum)
|
||||||
|
- Cada nome corresponde a uma pasta em `src/plugins/`
|
||||||
|
- Remova ou comente para desativar sem apagar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Configurações Personalizadas
|
||||||
|
|
||||||
|
Você pode adicionar suas próprias variáveis para plugins:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# manybot.conf
|
||||||
|
MEU_PREFIXO=>
|
||||||
|
API_KEY=minha_chave
|
||||||
|
```
|
||||||
|
|
||||||
|
E acessar no código do plugin:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { MEU_PREFIXO, API_KEY } from "../../config.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exemplo Completo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ManyBot Configuration
|
||||||
|
|
||||||
|
CLIENT_ID=meu_bot_prod
|
||||||
|
CMD_PREFIX=/
|
||||||
|
LANGUAGE=pt
|
||||||
|
|
||||||
|
CHATS=[
|
||||||
|
5511999999999@c.us,
|
||||||
|
5511888888888-123456789@g.us
|
||||||
|
]
|
||||||
|
|
||||||
|
PLUGINS=[
|
||||||
|
figurinha,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
many
|
||||||
|
]
|
||||||
|
|
||||||
|
# Configurações extras
|
||||||
|
ADMIN_NUMBER=5511999999999@c.us
|
||||||
|
```
|
||||||
146
docs/CONFIGURATION.md
Normal file
146
docs/CONFIGURATION.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Configuration
|
||||||
|
|
||||||
|
Complete guide for the `manybot.conf` file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Basic Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Comments start with '#'
|
||||||
|
|
||||||
|
CLIENT_ID=bot_permanente
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=en
|
||||||
|
CHATS=[]
|
||||||
|
PLUGINS=[]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
### CLIENT_ID
|
||||||
|
|
||||||
|
Unique identifier for the bot session.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CLIENT_ID=my_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Default:** `bot_permanente`
|
||||||
|
- **Usage:** Creates a `session/` folder with this name to store authentication data
|
||||||
|
|
||||||
|
### CMD_PREFIX
|
||||||
|
|
||||||
|
Character that indicates the start of a command.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CMD_PREFIX=!
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Default:** `!`
|
||||||
|
- **Example:** With prefix `!`, the command is `!sticker`. With `.`, it would be `.sticker`.
|
||||||
|
|
||||||
|
### LANGUAGE
|
||||||
|
|
||||||
|
Bot message language.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LANGUAGE=en
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Default:** `en` (English)
|
||||||
|
- **Options:** `en` (English), `pt` (Portuguese), `es` (Spanish)
|
||||||
|
- **Note:** If the selected language doesn't exist, the bot will fall back to English
|
||||||
|
|
||||||
|
### CHATS
|
||||||
|
|
||||||
|
List of chat IDs where the bot will respond.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CHATS=[
|
||||||
|
123456789@c.us, # Private chat
|
||||||
|
123456789@g.us # Group
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Default:** `[]` (empty = responds to all)
|
||||||
|
- **Format:**
|
||||||
|
- Private: `NUMBER@c.us`
|
||||||
|
- Group: `NUMBER@g.us`
|
||||||
|
|
||||||
|
#### How to discover the ID
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node src/utils/get_id.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Scan the QR Code and send a message in the chat. The ID will appear in the terminal.
|
||||||
|
|
||||||
|
> Note: The utility uses a separate `CLIENT_ID` to avoid conflicting with the main session.
|
||||||
|
|
||||||
|
### PLUGINS
|
||||||
|
|
||||||
|
List of plugins to be loaded.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PLUGINS=[
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
sticker,
|
||||||
|
guess,
|
||||||
|
hangman,
|
||||||
|
many,
|
||||||
|
thanks
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Default:** `[]` (none)
|
||||||
|
- Each name corresponds to a folder in `src/plugins/`
|
||||||
|
- Remove or comment to disable without deleting
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Custom Settings
|
||||||
|
|
||||||
|
You can add your own variables for plugins:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# manybot.conf
|
||||||
|
MY_PREFIX=>
|
||||||
|
API_KEY=my_key
|
||||||
|
```
|
||||||
|
|
||||||
|
And access in the plugin code:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { MY_PREFIX, API_KEY } from "../../config.js";
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Example
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ManyBot Configuration
|
||||||
|
|
||||||
|
CLIENT_ID=my_bot_prod
|
||||||
|
CMD_PREFIX=/
|
||||||
|
LANGUAGE=en
|
||||||
|
|
||||||
|
CHATS=[
|
||||||
|
5511999999999@c.us,
|
||||||
|
5511888888888-123456789@g.us
|
||||||
|
]
|
||||||
|
|
||||||
|
PLUGINS=[
|
||||||
|
sticker,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
many
|
||||||
|
]
|
||||||
|
|
||||||
|
# Extra settings
|
||||||
|
ADMIN_NUMBER=5511999999999@c.us
|
||||||
|
```
|
||||||
190
docs/INSTALACAO.md
Normal file
190
docs/INSTALACAO.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# 📥 Instalação
|
||||||
|
|
||||||
|
Guia completo de instalação do ManyBot em diferentes plataformas.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📑 Índice
|
||||||
|
|
||||||
|
- [Docker](#docker) (Recomendado)
|
||||||
|
- [Linux](#linux)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Termux (Android)](#termux-android)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
A maneira mais fácil e recomendada de rodar o ManyBot.
|
||||||
|
|
||||||
|
### Pré-requisitos
|
||||||
|
|
||||||
|
- [Docker](https://docs.docker.com/get-docker/)
|
||||||
|
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
|
### Instalação
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone o repositório
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
|
||||||
|
# 2. Crie o arquivo de configuração
|
||||||
|
cp manybot.conf.example manybot.conf
|
||||||
|
nano manybot.conf
|
||||||
|
|
||||||
|
# 3. Inicie com Docker
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 4. Veja os logs para escanear o QR Code
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
**Escaneie o QR Code** que aparecerá nos logs.
|
||||||
|
|
||||||
|
### Comandos úteis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Ver logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Parar o bot
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Atualizar
|
||||||
|
git pull
|
||||||
|
docker-compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
### 1. Clone o repositório
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure o bot
|
||||||
|
|
||||||
|
Crie o arquivo de configuração:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
touch manybot.conf
|
||||||
|
nano manybot.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Exemplo de configuração:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Comentários com '#'
|
||||||
|
|
||||||
|
CLIENT_ID=bot_permanente
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=pt
|
||||||
|
CHATS=[
|
||||||
|
123456789@c.us,
|
||||||
|
123456789@g.us
|
||||||
|
]
|
||||||
|
PLUGINS=[
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
figurinha,
|
||||||
|
adivinhacao
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Detalhes:**
|
||||||
|
- `CLIENT_ID`: ID da sessão (padrão: `bot_permanente`)
|
||||||
|
- `CMD_PREFIX`: Prefixo dos comandos (padrão: `!`)
|
||||||
|
- `LANGUAGE`: Idioma do bot - `pt`, `en` ou `es` (padrão: `en`)
|
||||||
|
- `CHATS`: IDs dos chats permitidos (deixe vazio para todos)
|
||||||
|
- `PLUGINS`: Lista de plugins ativos
|
||||||
|
|
||||||
|
### 3. Execute a instalação
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./setup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Primeira execução
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node ./src/main.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Escaneie o QR Code no WhatsApp:
|
||||||
|
|
||||||
|
**Menu → Dispositivos conectados → Conectar um dispositivo**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
O ManyBot foi pensado para Linux, mas funciona no Windows via **Git Bash**.
|
||||||
|
|
||||||
|
### Pré-requisitos
|
||||||
|
|
||||||
|
1. **Git Bash**: https://git-scm.com/download/win
|
||||||
|
2. **Node.js**: https://nodejs.org (escolha "Instalador Windows (.msi)")
|
||||||
|
|
||||||
|
### Instalação
|
||||||
|
|
||||||
|
Após instalar ambos, abra o **Git Bash** e siga os mesmos passos da [instalação Linux](#linux).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Termux (Android)
|
||||||
|
|
||||||
|
> ⚠️ **Aviso:** Suporte experimental. Não há garantia de funcionamento.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Instale o Termux pela F-Droid (não use Play Store)
|
||||||
|
# https://f-droid.org/packages/com.termux/
|
||||||
|
|
||||||
|
# Atualize pacotes
|
||||||
|
pkg update && pkg upgrade
|
||||||
|
|
||||||
|
# Instale dependências
|
||||||
|
pkg install nodejs git
|
||||||
|
|
||||||
|
# Clone e instale
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
```
|
||||||
|
|
||||||
|
Siga os passos de configuração Linux a partir do passo 2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Resolução de Problemas
|
||||||
|
|
||||||
|
### Erro ao escanear QR Code
|
||||||
|
|
||||||
|
- Limpe os dados do Chrome/Chromium do Termux
|
||||||
|
- Delete a pasta `session/` e tente novamente
|
||||||
|
|
||||||
|
### Bot não responde comandos
|
||||||
|
|
||||||
|
- Verifique o `CMD_PREFIX` no `manybot.conf`
|
||||||
|
- Confira se o plugin está na lista `PLUGINS`
|
||||||
|
|
||||||
|
### Erros de instalação
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Limpe a cache do npm
|
||||||
|
npm cache clean --force
|
||||||
|
|
||||||
|
# Reinstale dependências
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Próximos Passos
|
||||||
|
|
||||||
|
- [Configuração avançada](./CONFIGURACAO.md)
|
||||||
|
- [Criando plugins](./PLUGINS.md)
|
||||||
190
docs/INSTALLATION.md
Normal file
190
docs/INSTALLATION.md
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
# Installation
|
||||||
|
|
||||||
|
Complete installation guide for ManyBot on different platforms.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Index
|
||||||
|
|
||||||
|
- [Docker](#docker) (Recommended)
|
||||||
|
- [Linux](#linux)
|
||||||
|
- [Windows](#windows)
|
||||||
|
- [Termux (Android)](#termux-android)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
The easiest and recommended way to run ManyBot.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Docker](https://docs.docker.com/get-docker/)
|
||||||
|
- [Docker Compose](https://docs.docker.com/compose/install/)
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Clone the repository
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
|
||||||
|
# 2. Create the configuration file
|
||||||
|
cp manybot.conf.example manybot.conf
|
||||||
|
nano manybot.conf
|
||||||
|
|
||||||
|
# 3. Start with Docker
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# 4. Watch logs to scan QR Code
|
||||||
|
docker-compose logs -f
|
||||||
|
```
|
||||||
|
|
||||||
|
**Scan the QR Code** that appears in the logs.
|
||||||
|
|
||||||
|
### Useful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View logs
|
||||||
|
docker-compose logs -f
|
||||||
|
|
||||||
|
# Stop the bot
|
||||||
|
docker-compose down
|
||||||
|
|
||||||
|
# Update
|
||||||
|
git pull
|
||||||
|
docker-compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Linux
|
||||||
|
|
||||||
|
### 1. Clone the repository
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure the bot
|
||||||
|
|
||||||
|
Create the configuration file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
touch manybot.conf
|
||||||
|
nano manybot.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Example configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Comments with '#'
|
||||||
|
|
||||||
|
CLIENT_ID=bot_permanente
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=en
|
||||||
|
CHATS=[
|
||||||
|
123456789@c.us,
|
||||||
|
123456789@g.us
|
||||||
|
]
|
||||||
|
PLUGINS=[
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
sticker,
|
||||||
|
guess
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Details:**
|
||||||
|
- `CLIENT_ID`: Session ID (default: `bot_permanente`)
|
||||||
|
- `CMD_PREFIX`: Command prefix (default: `!`)
|
||||||
|
- `LANGUAGE`: Bot language - `pt`, `en`, or `es` (default: `en`)
|
||||||
|
- `CHATS`: Allowed chat IDs (leave empty for all)
|
||||||
|
- `PLUGINS`: List of active plugins
|
||||||
|
|
||||||
|
### 3. Run installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash ./setup
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. First run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node ./src/main.js
|
||||||
|
```
|
||||||
|
|
||||||
|
Scan the QR Code in WhatsApp:
|
||||||
|
|
||||||
|
**Menu → Linked Devices → Link a Device**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Windows
|
||||||
|
|
||||||
|
ManyBot was designed for Linux, but works on Windows via **Git Bash**.
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
1. **Git Bash**: https://git-scm.com/download/win
|
||||||
|
2. **Node.js**: https://nodejs.org (choose "Windows Installer (.msi)")
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
|
||||||
|
After installing both, open **Git Bash** and follow the same steps as the [Linux installation](#linux).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Termux (Android)
|
||||||
|
|
||||||
|
> ⚠️ **Warning:** Experimental support. No guarantee of functionality.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Install Termux from F-Droid (don't use Play Store)
|
||||||
|
# https://f-droid.org/packages/com.termux/
|
||||||
|
|
||||||
|
# Update packages
|
||||||
|
pkg update && pkg upgrade
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pkg install nodejs git
|
||||||
|
|
||||||
|
# Clone and install
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manybot
|
||||||
|
cd manybot
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the Linux configuration steps from step 2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### QR Code scanning error
|
||||||
|
|
||||||
|
- Clear Chrome/Chromium data from Termux
|
||||||
|
- Delete the `session/` folder and try again
|
||||||
|
|
||||||
|
### Bot not responding to commands
|
||||||
|
|
||||||
|
- Check `CMD_PREFIX` in `manybot.conf`
|
||||||
|
- Make sure the plugin is in the `PLUGINS` list
|
||||||
|
|
||||||
|
### Installation errors
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean npm cache
|
||||||
|
npm cache clean --force
|
||||||
|
|
||||||
|
# Reinstall dependencies
|
||||||
|
rm -rf node_modules package-lock.json
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
- [Advanced configuration](./CONFIGURATION.md)
|
||||||
|
- [Creating plugins](./PLUGINS_EN.md)
|
||||||
317
docs/PLUGINS (en).md
Normal file
317
docs/PLUGINS (en).md
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
# 🔌 Creating Plugins
|
||||||
|
|
||||||
|
Complete guide to creating plugins in ManyBot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📑 Index
|
||||||
|
|
||||||
|
- [Basic Structure](#basic-structure)
|
||||||
|
- [Plugin Manifest](#plugin-manifest-manyplug-json)
|
||||||
|
- [Creating Your First Plugin](#creating-your-first-plugin)
|
||||||
|
- [Object API](#object-api)
|
||||||
|
- [Exposing API](#exposing-api-to-other-plugins)
|
||||||
|
- [Translating Your Plugin](#translating-your-plugin)
|
||||||
|
- [Error Handling](#error-handling)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Basic Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/plugins/
|
||||||
|
└── my-plugin/
|
||||||
|
├── index.js
|
||||||
|
├── manyplug.json
|
||||||
|
└── locale/ (optional)
|
||||||
|
├── en.json
|
||||||
|
├── pt.json
|
||||||
|
└── es.json
|
||||||
|
```
|
||||||
|
|
||||||
|
`index.js` must export a `default` function that receives `{ msg, api }`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
// Your logic here
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugin Manifest (manyplug.json)
|
||||||
|
|
||||||
|
Every plugin should have a `manyplug.json` at its root. It describes the plugin and declares any extra npm dependencies it needs.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "my-plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"service": false,
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `name` | `string` | Plugin identifier, must match the folder name |
|
||||||
|
| `version` | `string` | Semantic version (e.g., `"1.0.0"`) |
|
||||||
|
| `category` | `string` | Plugin category: `utility`, `media`, `game`, `humor`, `info` |
|
||||||
|
| `service` | `boolean` | `true` if the plugin runs in the background (scheduler, listener). `false` if triggered by a command or event |
|
||||||
|
| `dependencies` | `object` | Extra npm packages required by the plugin, same format as `package.json` |
|
||||||
|
|
||||||
|
### Example with dependencies
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "weather",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"service": false,
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
After adding dependencies, run `npm install` at the project root to install them.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Creating Your First Plugin
|
||||||
|
|
||||||
|
### Example 1: Simple command
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/greeting/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
// Only responds if the message starts with "!hi"
|
||||||
|
if (!msg.is(CMD_PREFIX + "hi")) return;
|
||||||
|
|
||||||
|
await msg.reply("Hello! 👋");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 2: Command with arguments
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/calculate/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "calculate")) return;
|
||||||
|
|
||||||
|
// msg.args = ["!calculate", "5", "+", "3"]
|
||||||
|
const [, a, operator, b] = msg.args;
|
||||||
|
|
||||||
|
let result;
|
||||||
|
switch (operator) {
|
||||||
|
case "+": result = Number(a) + Number(b); break;
|
||||||
|
case "-": result = Number(a) - Number(b); break;
|
||||||
|
case "*": result = Number(a) * Number(b); break;
|
||||||
|
case "/": result = Number(a) / Number(b); break;
|
||||||
|
default: return msg.reply("Invalid operator!");
|
||||||
|
}
|
||||||
|
|
||||||
|
await msg.reply(`Result: ${result}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example 3: Processing media
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/echo-media/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||||
|
|
||||||
|
// Checks if the message has media
|
||||||
|
if (!msg.hasMedia) {
|
||||||
|
return msg.reply("Send a media file with the command!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Downloads the media
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
|
||||||
|
// Resends in the chat
|
||||||
|
await api.sendSticker(media.data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Object API
|
||||||
|
|
||||||
|
### `msg` Object
|
||||||
|
|
||||||
|
| Property | Type | Description |
|
||||||
|
|----------|------|-------------|
|
||||||
|
| `msg.body` | `string` | Message text |
|
||||||
|
| `msg.args` | `string[]` | Message tokens |
|
||||||
|
| `msg.type` | `string` | Type: `chat`, `image`, `video`, `audio`, `sticker` |
|
||||||
|
| `msg.sender` | `string` | Sender ID |
|
||||||
|
| `msg.senderName` | `string` | Sender name |
|
||||||
|
| `msg.fromMe` | `boolean` | Whether the bot sent it |
|
||||||
|
| `msg.hasMedia` | `boolean` | Whether it has media |
|
||||||
|
| `msg.hasReply` | `boolean` | Whether it is a reply |
|
||||||
|
| `msg.isGif` | `boolean` | Whether it is a GIF |
|
||||||
|
| `msg.is(cmd)` | `function` | Checks if starts with command |
|
||||||
|
| `msg.reply(text)` | `function` | Replies with quote |
|
||||||
|
| `msg.downloadMedia()` | `function` | Returns `{ mimetype, data }` |
|
||||||
|
| `msg.getReply()` | `function` | Returns quoted message |
|
||||||
|
|
||||||
|
### `api` Object
|
||||||
|
|
||||||
|
| Method | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `api.send(text)` | Sends text |
|
||||||
|
| `api.sendVideo(path)` | Sends video |
|
||||||
|
| `api.sendAudio(path)` | Sends audio (voice) |
|
||||||
|
| `api.sendImage(path, caption?)` | Sends image |
|
||||||
|
| `api.sendSticker(bufferOrPath)` | Sends sticker |
|
||||||
|
| `api.getPlugin(name)` | Accesses another plugin |
|
||||||
|
| `api.chat.id` | Chat ID |
|
||||||
|
| `api.chat.name` | Chat name |
|
||||||
|
| `api.chat.isGroup` | Whether it is a group |
|
||||||
|
| `api.log.info(...)` | Info log |
|
||||||
|
| `api.log.warn(...)` | Warning log |
|
||||||
|
| `api.log.error(...)` | Error log |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Exposing API to Other Plugins
|
||||||
|
|
||||||
|
A plugin can export functions for others to use:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/utilities/index.js
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
export const api = {
|
||||||
|
formatDate: (date) => date.toLocaleDateString("en-US"),
|
||||||
|
formatCurrency: (value) => `$${value.toFixed(2)}`,
|
||||||
|
wait: (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normal plugin logic
|
||||||
|
export default async function ({ msg }) {
|
||||||
|
if (msg.is("!ping")) {
|
||||||
|
await msg.reply("pong!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Another plugin using it:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/other/index.js
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
const utils = api.getPlugin("utilities");
|
||||||
|
|
||||||
|
const date = utils.formatDate(new Date());
|
||||||
|
await msg.reply(`Today is ${date}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Translating Your Plugin
|
||||||
|
|
||||||
|
Each plugin can have its own translations, completely independent from the bot core. The bot locale (set in `manybot.conf`) is used automatically.
|
||||||
|
|
||||||
|
### Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
src/plugins/
|
||||||
|
└── my-plugin/
|
||||||
|
├── index.js
|
||||||
|
└── locale/
|
||||||
|
├── en.json
|
||||||
|
├── pt.json
|
||||||
|
└── es.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### locale/en.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hello": "Hello, {{name}}! 👋",
|
||||||
|
"error": {
|
||||||
|
"notFound": "Item not found."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### index.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||||
|
|
||||||
|
const { t } = createPluginI18n(import.meta.url);
|
||||||
|
|
||||||
|
export default async function ({ msg }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "hi")) return;
|
||||||
|
|
||||||
|
// Simple key
|
||||||
|
await msg.reply(t("hello", { name: msg.senderName }));
|
||||||
|
|
||||||
|
// Nested key
|
||||||
|
await msg.reply(t("error.notFound"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Notes
|
||||||
|
|
||||||
|
- If the configured locale has no translation file, falls back to `en.json`.
|
||||||
|
- If the key doesn't exist in any file, the key itself is returned as-is.
|
||||||
|
- Use `{{variable}}` syntax for interpolation.
|
||||||
|
- Each plugin manages its own translations — never import `t` from the bot core.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
If a plugin throws an error, the kernel automatically disables it:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
try {
|
||||||
|
// Code that might fail
|
||||||
|
const result = await somethingRisky();
|
||||||
|
await msg.reply(result);
|
||||||
|
} catch (error) {
|
||||||
|
// Logs the error and notifies
|
||||||
|
api.log.error("Plugin error:", error);
|
||||||
|
await msg.reply("Oops! Something went wrong.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Enabling the Plugin
|
||||||
|
|
||||||
|
After creating it, add to `manybot.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PLUGINS=[
|
||||||
|
# ... other plugins
|
||||||
|
my-plugin
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the bot to load it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## See Also
|
||||||
|
|
||||||
|
- [API Reference](./API.md)
|
||||||
|
- [Plugin examples](../src/plugins/)
|
||||||
360
docs/PLUGINS.md
Normal file
360
docs/PLUGINS.md
Normal file
@@ -0,0 +1,360 @@
|
|||||||
|
# 🔌 Criando Plugins
|
||||||
|
|
||||||
|
Guia completo para criar plugins no ManyBot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ManyPlug CLI
|
||||||
|
|
||||||
|
**ManyPlug** é a ferramenta oficial para gerenciar plugins do ManyBot. Com ela você pode criar, instalar e validar plugins facilmente.
|
||||||
|
|
||||||
|
### Instalação
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g @freakk.dev/manyplug
|
||||||
|
```
|
||||||
|
|
||||||
|
Ou para desenvolvimento:
|
||||||
|
```bash
|
||||||
|
git clone https://git.maneos.net/synt-xerror/manyplug
|
||||||
|
cd manyplug
|
||||||
|
npm link
|
||||||
|
```
|
||||||
|
|
||||||
|
### Comandos
|
||||||
|
|
||||||
|
| Comando | Descrição |
|
||||||
|
|---------|-----------|
|
||||||
|
| `manyplug init <nome>` | Cria estrutura de um novo plugin |
|
||||||
|
| `manyplug install [nome]` | Instala do registro ou `--local <caminho>` |
|
||||||
|
| `manyplug list` | Lista plugins instalados |
|
||||||
|
| `manyplug validate [caminho]` | Valida o manyplug.json |
|
||||||
|
|
||||||
|
### Exemplos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Criar novo plugin
|
||||||
|
cd src/plugins
|
||||||
|
manyplug init meu-plugin --category utility
|
||||||
|
|
||||||
|
# Instalar de outro diretório
|
||||||
|
manyplug install --local ../outro-plugin
|
||||||
|
|
||||||
|
# Validar manifesto
|
||||||
|
manyplug validate ./meu-plugin
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📑 Índice
|
||||||
|
|
||||||
|
- [ManyPlug CLI](#manyplug-cli)
|
||||||
|
- [Estrutura Básica](#estrutura-básica)
|
||||||
|
- [Manifesto do Plugin](#manifesto-do-plugin-manyplugjson)
|
||||||
|
- [Criando Seu Primeiro Plugin](#criando-seu-primeiro-plugin)
|
||||||
|
- [API de Objetos](#api-de-objetos)
|
||||||
|
- [Expondo API](#expondo-api-para-outros-plugins)
|
||||||
|
- [Traduzindo Seu Plugin](#traduzindo-seu-plugin)
|
||||||
|
- [Tratamento de Erros](#tratamento-de-erros)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Estrutura Básica
|
||||||
|
|
||||||
|
```
|
||||||
|
src/plugins/
|
||||||
|
└── meu-plugin/
|
||||||
|
├── index.js
|
||||||
|
├── manyplug.json
|
||||||
|
└── locale/ (opcional)
|
||||||
|
├── en.json
|
||||||
|
├── pt.json
|
||||||
|
└── es.json
|
||||||
|
```
|
||||||
|
|
||||||
|
O `index.js` deve exportar uma função `default` que recebe `{ msg, api }`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
// Sua lógica aqui
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manifesto do Plugin (manyplug.json)
|
||||||
|
|
||||||
|
Todo plugin deve ter um `manyplug.json` na raiz. Ele descreve o plugin e declara dependências npm extras que ele precisar.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "meu-plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"service": false,
|
||||||
|
"dependencies": {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Campos
|
||||||
|
|
||||||
|
| Campo | Tipo | Descrição |
|
||||||
|
|-------|------|-----------|
|
||||||
|
| `name` | `string` | Identificador do plugin, deve ser igual ao nome da pasta |
|
||||||
|
| `version` | `string` | Versão semântica (ex: `"1.0.0"`) |
|
||||||
|
| `category` | `string` | Categoria do plugin: `utility`, `media`, `game`, `humor`, `info` |
|
||||||
|
| `service` | `boolean` | `true` se o plugin roda em segundo plano (agendador, listener). `false` se acionado por comando ou evento |
|
||||||
|
| `dependencies` | `object` | Pacotes npm extras necessários, mesmo formato do `package.json` |
|
||||||
|
|
||||||
|
### Exemplo com dependências
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "clima",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"service": false,
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Após adicionar dependências, rode `npm install` na raiz do projeto para instalá-las.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Criando Seu Primeiro Plugin
|
||||||
|
|
||||||
|
### Exemplo 1: Comando simples
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/saudacao/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
// Só responde se a mensagem começar com "!oi"
|
||||||
|
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||||
|
|
||||||
|
await msg.reply("Olá! 👋");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemplo 2: Comando com argumentos
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/calcular/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "calcular")) return;
|
||||||
|
|
||||||
|
// msg.args = ["!calcular", "5", "+", "3"]
|
||||||
|
const [, a, operador, b] = msg.args;
|
||||||
|
|
||||||
|
let resultado;
|
||||||
|
switch (operador) {
|
||||||
|
case "+": resultado = Number(a) + Number(b); break;
|
||||||
|
case "-": resultado = Number(a) - Number(b); break;
|
||||||
|
case "*": resultado = Number(a) * Number(b); break;
|
||||||
|
case "/": resultado = Number(a) / Number(b); break;
|
||||||
|
default: return msg.reply("Operador inválido!");
|
||||||
|
}
|
||||||
|
|
||||||
|
await msg.reply(`Resultado: ${resultado}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Exemplo 3: Processando mídia
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/echo-media/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||||
|
|
||||||
|
// Verifica se a mensagem tem mídia
|
||||||
|
if (!msg.hasMedia) {
|
||||||
|
return msg.reply("Envie uma mídia com o comando!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baixa a mídia
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
|
||||||
|
// Reenvia no chat
|
||||||
|
await api.sendSticker(media.data);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API de Objetos
|
||||||
|
|
||||||
|
### Objeto `msg`
|
||||||
|
|
||||||
|
| Propriedade | Tipo | Descrição |
|
||||||
|
|-------------|------|-----------|
|
||||||
|
| `msg.body` | `string` | Texto da mensagem |
|
||||||
|
| `msg.args` | `string[]` | Tokens da mensagem |
|
||||||
|
| `msg.type` | `string` | Tipo: `chat`, `image`, `video`, `audio`, `sticker` |
|
||||||
|
| `msg.sender` | `string` | ID do remetente |
|
||||||
|
| `msg.senderName` | `string` | Nome do remetente |
|
||||||
|
| `msg.fromMe` | `boolean` | Se o bot enviou |
|
||||||
|
| `msg.hasMedia` | `boolean` | Se tem mídia |
|
||||||
|
| `msg.hasReply` | `boolean` | Se é resposta |
|
||||||
|
| `msg.isGif` | `boolean` | Se é GIF |
|
||||||
|
| `msg.is(cmd)` | `function` | Verifica se começa com comando |
|
||||||
|
| `msg.reply(text)` | `function` | Responde com quote |
|
||||||
|
| `msg.downloadMedia()` | `function` | Retorna `{ mimetype, data }` |
|
||||||
|
| `msg.getReply()` | `function` | Retorna mensagem citada |
|
||||||
|
|
||||||
|
### Objeto `api`
|
||||||
|
|
||||||
|
| Método | Descrição |
|
||||||
|
|--------|-----------|
|
||||||
|
| `api.send(text)` | Envia texto |
|
||||||
|
| `api.sendVideo(path)` | Envia vídeo |
|
||||||
|
| `api.sendAudio(path)` | Envia áudio (voz) |
|
||||||
|
| `api.sendImage(path, caption?)` | Envia imagem |
|
||||||
|
| `api.sendSticker(bufferOrPath)` | Envia figurinha |
|
||||||
|
| `api.getPlugin(name)` | Acessa outro plugin |
|
||||||
|
| `api.chat.id` | ID do chat |
|
||||||
|
| `api.chat.name` | Nome do chat |
|
||||||
|
| `api.chat.isGroup` | Se é grupo |
|
||||||
|
| `api.log.info(...)` | Log informativo |
|
||||||
|
| `api.log.warn(...)` | Log de aviso |
|
||||||
|
| `api.log.error(...)` | Log de erro |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Expondo API para Outros Plugins
|
||||||
|
|
||||||
|
Um plugin pode exportar funções para outros usarem:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/utilidades/index.js
|
||||||
|
|
||||||
|
// API pública
|
||||||
|
export const api = {
|
||||||
|
formatarData: (date) => date.toLocaleDateString("pt-BR"),
|
||||||
|
formatarMoeda: (valor) => `R$ ${valor.toFixed(2).replace(".", ",")}`,
|
||||||
|
esperar: (ms) => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Lógica normal do plugin
|
||||||
|
export default async function ({ msg }) {
|
||||||
|
if (msg.is("!ping")) {
|
||||||
|
await msg.reply("pong!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Outro plugin usando:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/outro/index.js
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
const utils = api.getPlugin("utilidades");
|
||||||
|
|
||||||
|
const data = utils.formatarData(new Date());
|
||||||
|
await msg.reply(`Hoje é ${data}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Traduzindo Seu Plugin
|
||||||
|
|
||||||
|
Cada plugin pode ter suas próprias traduções, completamente independentes do core do bot. O locale do bot (definido no `manybot.conf`) é usado automaticamente.
|
||||||
|
|
||||||
|
### Estrutura
|
||||||
|
|
||||||
|
```
|
||||||
|
src/plugins/
|
||||||
|
└── meu-plugin/
|
||||||
|
├── index.js
|
||||||
|
└── locale/
|
||||||
|
├── en.json
|
||||||
|
├── pt.json
|
||||||
|
└── es.json
|
||||||
|
```
|
||||||
|
|
||||||
|
### locale/pt.json
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"ola": "Olá, {{nome}}! 👋",
|
||||||
|
"erro": {
|
||||||
|
"naoEncontrado": "Item não encontrado."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### index.js
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||||
|
|
||||||
|
const { t } = createPluginI18n(import.meta.url);
|
||||||
|
|
||||||
|
export default async function ({ msg }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||||
|
|
||||||
|
// Chave simples
|
||||||
|
await msg.reply(t("ola", { nome: msg.senderName }));
|
||||||
|
|
||||||
|
// Chave aninhada
|
||||||
|
await msg.reply(t("erro.naoEncontrado"));
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Observações
|
||||||
|
|
||||||
|
- Se o locale configurado não tiver arquivo de tradução, cai automaticamente para `en.json`.
|
||||||
|
- Se a chave não existir em nenhum arquivo, a própria chave é retornada.
|
||||||
|
- Use a sintaxe `{{variavel}}` para interpolação.
|
||||||
|
- Cada plugin gerencia suas próprias traduções — nunca importe `t` do core do bot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tratamento de Erros
|
||||||
|
|
||||||
|
Se um plugin lançar erro, o kernel o desativa automaticamente:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
try {
|
||||||
|
// Código que pode falhar
|
||||||
|
const resultado = await algoPerigoso();
|
||||||
|
await msg.reply(resultado);
|
||||||
|
} catch (erro) {
|
||||||
|
// Loga o erro e notifica
|
||||||
|
api.log.error("Erro no plugin:", erro);
|
||||||
|
await msg.reply("Ops! Algo deu errado.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ativando o Plugin
|
||||||
|
|
||||||
|
Depois de criar, adicione ao `manybot.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
PLUGINS=[
|
||||||
|
# ... outros plugins
|
||||||
|
meu-plugin
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
Reinicie o bot para carregar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Veja Também
|
||||||
|
|
||||||
|
- [Referência da API](./API.md)
|
||||||
|
- [Exemplos de plugins](../src/plugins/)
|
||||||
267
man/man1/manybot-plugin.1
Normal file
267
man/man1/manybot-plugin.1
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
.TH MANYBOT-PLUGIN 1 "April 2026" "ManyBot 2.4.3" "User Commands"
|
||||||
|
.SH NAME
|
||||||
|
manybot-plugin \- ManyBot plugin development guide
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B manyplug.json
|
||||||
|
.I manifest file
|
||||||
|
.br
|
||||||
|
.I src/plugins/
|
||||||
|
.B plugin directory
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ManyBot plugins extend the bot's functionality without modifying the core
|
||||||
|
kernel. The kernel connects to WhatsApp and distributes messages to plugins,
|
||||||
|
which decide how to respond.
|
||||||
|
.PP
|
||||||
|
Each plugin lives in its own folder under
|
||||||
|
.I src/plugins/
|
||||||
|
with a
|
||||||
|
.B manyplug.json
|
||||||
|
manifest file and an
|
||||||
|
.B index.js
|
||||||
|
entry point.
|
||||||
|
.SH PLUGIN STRUCTURE
|
||||||
|
.nf
|
||||||
|
src/plugins/
|
||||||
|
\(do__ my-plugin/
|
||||||
|
\(bu__ manyplug.json # Plugin manifest
|
||||||
|
\(bu__ index.js # Main entry point
|
||||||
|
\(bu__ locale/ # Translations (optional)
|
||||||
|
\(bu__ en.json
|
||||||
|
\(bu__ pt.json
|
||||||
|
\(bu__ es.json
|
||||||
|
.fi
|
||||||
|
.SH MANIFEST (manyplug.json)
|
||||||
|
Every plugin must include a manifest describing its metadata:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
{
|
||||||
|
"name": "my-plugin",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"service": false,
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.TP
|
||||||
|
.B name
|
||||||
|
Plugin identifier. Must match the folder name.
|
||||||
|
.TP
|
||||||
|
.B version
|
||||||
|
Semantic version (e.g., "1.0.0").
|
||||||
|
.TP
|
||||||
|
.B category
|
||||||
|
Plugin type: \fButility\fR, \fBmedia\fR, \fBgame\fR, \fBhumor\fR, or \fBinfo\fR.
|
||||||
|
.TP
|
||||||
|
.B service
|
||||||
|
\fBtrue\fR for background plugins (schedulers, listeners).
|
||||||
|
\fBfalse\fR for command/event-triggered plugins.
|
||||||
|
.TP
|
||||||
|
.B dependencies
|
||||||
|
Extra npm packages required. Install with \fBnpm install\fR from project root.
|
||||||
|
.SH PLUGIN ENTRY POINT
|
||||||
|
The
|
||||||
|
.I index.js
|
||||||
|
file must export a default async function:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
.B "export default async function ({ msg, api }) {"
|
||||||
|
// Plugin logic here
|
||||||
|
.B "}"
|
||||||
|
.fi
|
||||||
|
.SS Parameters
|
||||||
|
.TP
|
||||||
|
.B msg
|
||||||
|
Message object containing:
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBbody\fR - Full message text
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBargs\fR - Array of message tokens
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBtype\fR - Message type: chat, image, video, audio, sticker, ptt, document, location
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsender\fR - Sender ID (NUMBER@c.us or NUMBER@g.us)
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsenderName\fR - Display name
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBfromMe\fR - True if bot sent the message
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBhasMedia\fR - True if contains media
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBhasReply\fR - True if it's a reply
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBisGif\fR - True if media is GIF
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBtimestamp\fR - Unix timestamp
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBis(cmd)\fR - Check if message starts with command
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBreply(text)\fR - Reply with quote
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBdownloadMedia()\fR - Download media (returns {mimetype, data})
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBgetReply()\fR - Get quoted message object
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.B api
|
||||||
|
API object containing:
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsend(text)\fR - Send text message
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsendVideo(path)\fR - Send video file
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsendAudio(path)\fR - Send audio as voice message
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsendImage(path, caption?)\fR - Send image with optional caption
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBsendSticker(bufferOrPath)\fR - Send sticker from buffer or file
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBgetPlugin(name)\fR - Access another plugin's public API
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBchat\fR - Chat info: \fBid\fR, \fBname\fR, \fBisGroup\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
\fBlog.info(...), log.warn(...), log.error(...)\fR - Logging methods
|
||||||
|
.RE
|
||||||
|
.SH EXAMPLES
|
||||||
|
.SS Simple Command
|
||||||
|
.nf
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "hi")) return;
|
||||||
|
await msg.reply("Hello! 👋");
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.SS Command with Arguments
|
||||||
|
.nf
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "calc")) return;
|
||||||
|
|
||||||
|
const [, a, op, b] = msg.args;
|
||||||
|
let result;
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case "+": result = Number(a) + Number(b); break;
|
||||||
|
case "-": result = Number(a) - Number(b); break;
|
||||||
|
default: return msg.reply("Invalid operator!");
|
||||||
|
}
|
||||||
|
|
||||||
|
await msg.reply(`Result: ${result}`);
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.SS Processing Media
|
||||||
|
.nf
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "echo")) return;
|
||||||
|
|
||||||
|
if (!msg.hasMedia) {
|
||||||
|
return msg.reply("Send media with the command!");
|
||||||
|
}
|
||||||
|
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
await api.sendSticker(media.data);
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.SS Exposing API to Other Plugins
|
||||||
|
.nf
|
||||||
|
// Public API for other plugins
|
||||||
|
export const api = {
|
||||||
|
formatDate: (d) => d.toLocaleDateString("en-US"),
|
||||||
|
wait: (ms) => new Promise(r => setTimeout(r, ms))
|
||||||
|
};
|
||||||
|
|
||||||
|
// Normal plugin logic
|
||||||
|
export default async function ({ msg }) {
|
||||||
|
if (msg.is("!ping")) await msg.reply("pong!");
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Used by another plugin:
|
||||||
|
.nf
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
const utils = api.getPlugin("utilities");
|
||||||
|
const date = utils.formatDate(new Date());
|
||||||
|
await msg.reply(`Today is ${date}`);
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.SH TRANSLATIONS
|
||||||
|
Plugins can include their own translations:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
import { createPluginI18n } from "../../utils/pluginI18n.js";
|
||||||
|
|
||||||
|
const { t } = createPluginI18n(import.meta.url);
|
||||||
|
|
||||||
|
export default async function ({ msg }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "hello")) return;
|
||||||
|
await msg.reply(t("greeting", { name: msg.senderName }));
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Locale file (\fIlocale/en.json\fR):
|
||||||
|
.nf
|
||||||
|
{
|
||||||
|
"greeting": "Hello, {{name}}! 👋"
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
If the configured locale has no translation file, falls back to \fBen.json\fR.
|
||||||
|
Use \fB{{variable}}\fR syntax for interpolation.
|
||||||
|
.SH ENABLING A PLUGIN
|
||||||
|
Add the plugin folder name to
|
||||||
|
.I manybot.confR:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
PLUGINS=[
|
||||||
|
many,
|
||||||
|
figurinha,
|
||||||
|
my-plugin
|
||||||
|
]
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Restart ManyBot to load the plugin.
|
||||||
|
.SH ERROR HANDLING
|
||||||
|
If a plugin throws an error, the kernel automatically disables it.
|
||||||
|
Use try/catch for graceful error handling:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
try {
|
||||||
|
const result = await riskyOperation();
|
||||||
|
await msg.reply(result);
|
||||||
|
} catch (error) {
|
||||||
|
api.log.error("Plugin error:", error);
|
||||||
|
await msg.reply("Oops! Something went wrong.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.fi
|
||||||
|
.SH CONFIGURATION ACCESS
|
||||||
|
Import settings from the main config:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
import { CMD_PREFIX, CLIENT_ID, CHATS, PLUGINS } from "../../config.js";
|
||||||
|
|
||||||
|
// Custom config values also work
|
||||||
|
import { MY_API_KEY } from "../../config.js";
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Add custom values to
|
||||||
|
.I manybot.confR:
|
||||||
|
.nf
|
||||||
|
MY_API_KEY=secret_key_here
|
||||||
|
.fi
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR manybot (1),
|
||||||
|
.BR manyplug (1),
|
||||||
|
.BR manybot.conf (5)
|
||||||
|
.SH AUTHOR
|
||||||
|
Written by synt-xerror.
|
||||||
|
.SH REPORTING BUGS
|
||||||
|
Report bugs at: https://github.com/synt-xerror/manybot/issues
|
||||||
193
man/man1/manybot.1
Normal file
193
man/man1/manybot.1
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
.TH MANYBOT 1 "April 2026" "ManyBot 2.4.3" "User Commands"
|
||||||
|
.SH NAME
|
||||||
|
manybot \- local WhatsApp bot with plugin system
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B node
|
||||||
|
.I ./src/main.js
|
||||||
|
.br
|
||||||
|
.B node
|
||||||
|
.I src/utils/get_id.js
|
||||||
|
.br
|
||||||
|
.B bash
|
||||||
|
.I ./setup
|
||||||
|
.br
|
||||||
|
.B bash
|
||||||
|
.I ./update
|
||||||
|
.SH DESCRIPTION
|
||||||
|
ManyBot is a 100%% local WhatsApp bot that operates without relying on the
|
||||||
|
official WhatsApp API. It uses whatsapp-web.js to connect as a regular
|
||||||
|
WhatsApp client and provides a modular plugin system for extending functionality.
|
||||||
|
.PP
|
||||||
|
The bot supports multiple chats in a single session, runs headless (without
|
||||||
|
a GUI), and can be configured through a simple configuration file.
|
||||||
|
.SH COMMANDS
|
||||||
|
.TP
|
||||||
|
.B !many
|
||||||
|
List all available commands from loaded plugins.
|
||||||
|
.TP
|
||||||
|
.B !figurinha
|
||||||
|
Convert images, GIFs, and videos to stickers.
|
||||||
|
.TP
|
||||||
|
.B !video \fIURL\fR
|
||||||
|
Download videos from supported platforms.
|
||||||
|
.TP
|
||||||
|
.B !audio \fIURL\fR
|
||||||
|
Download audio from videos and send as voice message.
|
||||||
|
.TP
|
||||||
|
.B !adivinhacao comecar
|
||||||
|
Start a guessing game (1-100).
|
||||||
|
.TP
|
||||||
|
.B !forca comecar
|
||||||
|
Start a hangman game.
|
||||||
|
.TP
|
||||||
|
.B !obrigado
|
||||||
|
Bot responds with a thank-you message.
|
||||||
|
.PP
|
||||||
|
Command prefix can be configured via
|
||||||
|
.B CMD_PREFIX
|
||||||
|
in
|
||||||
|
.IR manybot.conf .
|
||||||
|
Default is
|
||||||
|
.BR ! .
|
||||||
|
.SH CONFIGURATION
|
||||||
|
ManyBot uses a configuration file
|
||||||
|
.I manybot.conf
|
||||||
|
in the project root. Key options:
|
||||||
|
.TP
|
||||||
|
.B CLIENT_ID=\fIname\fR
|
||||||
|
Unique identifier for the bot session. Creates a session/\fIname\fR folder
|
||||||
|
for authentication data. Default: \fIbot_permanente\fR.
|
||||||
|
.TP
|
||||||
|
.B CMD_PREFIX=\fIchar\fR
|
||||||
|
Character prefixing all commands. Default: \fI!\fR.
|
||||||
|
.TP
|
||||||
|
.B LANGUAGE=\fIcode\fR
|
||||||
|
Bot interface language: \fBen\fR (English), \fBpt\fR (Portuguese), or \fBes\fR (Spanish).
|
||||||
|
Default: \fBen\fR.
|
||||||
|
.TP
|
||||||
|
.B CHATS=[\fIid1\fR, \fIid2\fR, ...]
|
||||||
|
List of allowed chat IDs. Empty array allows all chats.
|
||||||
|
Format: \fBnumber@c.us\fR for private chats, \fBnumber@g.us\fR for groups.
|
||||||
|
.TP
|
||||||
|
.B PLUGINS=[\fIname1\fR, \fIname2\fR, ...]
|
||||||
|
List of plugins to load at startup. Each name corresponds to a folder in
|
||||||
|
.IR src/plugins/ .
|
||||||
|
.PP
|
||||||
|
See
|
||||||
|
.BR manybot.conf (5)
|
||||||
|
for complete configuration reference.
|
||||||
|
.SH PLUGINS
|
||||||
|
Plugins extend ManyBot functionality without modifying the kernel. Each plugin
|
||||||
|
is a folder under
|
||||||
|
.I src/plugins/
|
||||||
|
containing:
|
||||||
|
.TP
|
||||||
|
.I index.js
|
||||||
|
Main plugin file exporting a default async function receiving \fB{ msg, api }\fR.
|
||||||
|
.TP
|
||||||
|
.I manyplug.json
|
||||||
|
Plugin manifest describing name, version, category, service status, and dependencies.
|
||||||
|
.TP
|
||||||
|
.I locale/
|
||||||
|
Optional translation files (\fBen.json\fR, \fBpt.json\fR, \fBes.json\fR).
|
||||||
|
.PP
|
||||||
|
Plugins receive two objects:
|
||||||
|
.TP
|
||||||
|
.B msg
|
||||||
|
Message information including \fBbody\fR, \fBargs\fR, \fBtype\fR, \fBsender\fR,
|
||||||
|
\fBhasMedia\fR, methods \fBis()\fR, \fBreply()\fR, \fBdownloadMedia()\fR.
|
||||||
|
.TP
|
||||||
|
.B api
|
||||||
|
Interaction methods including \fBsend()\fR, \fBsendVideo()\fR, \fBsendAudio()\fR,
|
||||||
|
\fBsendImage()\fR, \fBsendSticker()\fR, \fBgetPlugin()\fR, and \fBlog\fR methods.
|
||||||
|
.SH MANYPLUG
|
||||||
|
ManyPlug is the official CLI for managing ManyBot plugins. Install it with:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
$ npm install -g @freakk.dev/manyplug
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Common commands:
|
||||||
|
.TP
|
||||||
|
.B manyplug init \fIname\fR
|
||||||
|
Create a new plugin boilerplate in the current directory.
|
||||||
|
.TP
|
||||||
|
.B manyplug install [\fIname\fR]
|
||||||
|
Install a plugin from the registry or use \fB--local \fIpath\fR to install from a local directory.
|
||||||
|
.TP
|
||||||
|
.B manyplug list
|
||||||
|
List all installed plugins in \fIsrc/plugins/\fR.
|
||||||
|
.TP
|
||||||
|
.B manyplug validate [\fIpath\fR]
|
||||||
|
Validate the \fImanyplug.json\fR manifest file.
|
||||||
|
.PP
|
||||||
|
See \fBmanyplug(1)\fR for complete documentation.
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
.I manybot.conf
|
||||||
|
Main configuration file. See \fBmanybot.conf(5)\fR.
|
||||||
|
.TP
|
||||||
|
.I session/
|
||||||
|
Authentication data and WhatsApp session storage.
|
||||||
|
.TP
|
||||||
|
.I src/plugins/
|
||||||
|
Plugin directory containing all installed plugins.
|
||||||
|
.TP
|
||||||
|
.I src/main.js
|
||||||
|
Bot entry point.
|
||||||
|
.TP
|
||||||
|
.I logs/
|
||||||
|
Log files directory.
|
||||||
|
.TP
|
||||||
|
.I update.log
|
||||||
|
Update script log output.
|
||||||
|
.SH ENVIRONMENT
|
||||||
|
.TP
|
||||||
|
.B NODE_ENV
|
||||||
|
Set to \fBproduction\fR to disable development features.
|
||||||
|
.SH EXIT STATUS
|
||||||
|
.TP
|
||||||
|
.B 0
|
||||||
|
Success
|
||||||
|
.TP
|
||||||
|
.B 1
|
||||||
|
General error
|
||||||
|
.TP
|
||||||
|
.B 130
|
||||||
|
Interrupted by user (Ctrl+C)
|
||||||
|
.SH EXAMPLES
|
||||||
|
.SS First run
|
||||||
|
.nf
|
||||||
|
$ node ./src/main.js
|
||||||
|
# Scan QR code with WhatsApp:
|
||||||
|
# Menu \-> Linked Devices \-> Link a Device
|
||||||
|
.fi
|
||||||
|
.SS Get chat IDs
|
||||||
|
.nf
|
||||||
|
$ node src/utils/get_id.js
|
||||||
|
# Send a message in the target chat to see the ID
|
||||||
|
.fi
|
||||||
|
.SS Update to latest version
|
||||||
|
.nf
|
||||||
|
$ bash ./update
|
||||||
|
.fi
|
||||||
|
.SH SECURITY
|
||||||
|
\(bu Bot runs with same privileges as the user running it
|
||||||
|
.br
|
||||||
|
\(bu Session data stored in \fIsession/\fR should be protected (chmod 700)
|
||||||
|
.br
|
||||||
|
\(bu CHATS whitelist recommended to limit bot exposure
|
||||||
|
.br
|
||||||
|
\(bu No official WhatsApp API keys required or used
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR manybot.conf (5),
|
||||||
|
.BR manybot-plugin (1),
|
||||||
|
.BR manyplug (1)
|
||||||
|
.SH AUTHOR
|
||||||
|
Written by synt-xerror.
|
||||||
|
.SH COPYRIGHT
|
||||||
|
Licensed under GPLv3. See LICENSE file for details.
|
||||||
|
.br
|
||||||
|
https://github.com/synt-xerror/manybot
|
||||||
|
.SH BUGS
|
||||||
|
Report bugs at: https://github.com/synt-xerror/manybot/issues
|
||||||
225
man/man5/manybot.conf.5
Normal file
225
man/man5/manybot.conf.5
Normal file
@@ -0,0 +1,225 @@
|
|||||||
|
.TH MANYBOT.CONF 5 "April 2026" "ManyBot 2.4.3" "File Formats"
|
||||||
|
.SH NAME
|
||||||
|
manybot.conf \- ManyBot configuration file
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.I manybot.conf
|
||||||
|
.SH DESCRIPTION
|
||||||
|
The
|
||||||
|
.I manybot.conf
|
||||||
|
file configures the ManyBot WhatsApp bot. It uses a simple key-value format
|
||||||
|
with support for multiline lists. Comments start with \fB#\fR.
|
||||||
|
.PP
|
||||||
|
The file must be located in the project root directory, alongside
|
||||||
|
.IR package.json .
|
||||||
|
.SH FORMAT
|
||||||
|
.nf
|
||||||
|
# Comments start with '#'
|
||||||
|
|
||||||
|
KEY=value
|
||||||
|
KEY=[item1, item2, item3]
|
||||||
|
.fi
|
||||||
|
.SS Key-Value Pairs
|
||||||
|
Simple configuration values:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
CLIENT_ID=my_bot
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=en
|
||||||
|
.fi
|
||||||
|
.SS Multiline Lists
|
||||||
|
Arrays spanning multiple lines:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
CHATS=[
|
||||||
|
123456789@c.us,
|
||||||
|
123456789@g.us
|
||||||
|
]
|
||||||
|
|
||||||
|
PLUGINS=[
|
||||||
|
many,
|
||||||
|
figurinha,
|
||||||
|
audio,
|
||||||
|
video
|
||||||
|
]
|
||||||
|
.fi
|
||||||
|
.SH OPTIONS
|
||||||
|
.SS Core Settings
|
||||||
|
.TP
|
||||||
|
.B CLIENT_ID=\fIstring\fR
|
||||||
|
Unique identifier for the bot session.
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
Default: \fBbot_permanente\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
Creates a \fIsession/CLIENT_ID/\fR folder for authentication data
|
||||||
|
.IP \(bu 2
|
||||||
|
Changing this starts a new session (requires QR code rescan)
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.B CMD_PREFIX=\fIcharacter\fR
|
||||||
|
Character that prefixes all bot commands.
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
Default: \fB!\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
Example: \fB!\fR makes commands like \fB!video\fR, \fB!audio\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
Changing to \fB.\fR would make commands \fB.video\fR, \fB.audio\fR
|
||||||
|
.RE
|
||||||
|
.TP
|
||||||
|
.B LANGUAGE=\fIcode\fR
|
||||||
|
Bot interface language.
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
Default: \fBen\fR (English)
|
||||||
|
.IP \(bu 2
|
||||||
|
Options: \fBen\fR, \fBpt\fR (Portuguese), \fBes\fR (Spanish)
|
||||||
|
.IP \(bu 2
|
||||||
|
Fallback to English if selected language not found
|
||||||
|
.RE
|
||||||
|
.SS Chat Settings
|
||||||
|
.TP
|
||||||
|
.B CHATS=[\fIid1\fR, \fIid2\fR, ...]
|
||||||
|
Whitelist of chat IDs where the bot responds.
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
Default: \fB[]\fR (empty = respond to all chats)
|
||||||
|
.IP \(bu 2
|
||||||
|
Private chat format: \fBnumber@c.us\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
Group format: \fBnumber@g.us\fR or \fBnumber-number@g.us\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
Use \fBnode src/utils/get_id.js\fR to discover chat IDs
|
||||||
|
.RE
|
||||||
|
.SS Plugin Settings
|
||||||
|
.TP
|
||||||
|
.B PLUGINS=[\fIname1\fR, \fIname2\fR, ...]
|
||||||
|
List of plugins to load at startup.
|
||||||
|
.RS
|
||||||
|
.IP \(bu 2
|
||||||
|
Default: \fB[]\fR (no plugins loaded)
|
||||||
|
.IP \(bu 2
|
||||||
|
Each name must match a folder in \fIsrc/plugins/\fR
|
||||||
|
.IP \(bu 2
|
||||||
|
Order matters: plugins load in listed order
|
||||||
|
.IP \(bu 2
|
||||||
|
Comment out or remove to disable without deleting files
|
||||||
|
.RE
|
||||||
|
.SS Built-in Plugins
|
||||||
|
.TP
|
||||||
|
.B many
|
||||||
|
Lists all available commands. Required for \fB!many\fR to work.
|
||||||
|
.TP
|
||||||
|
.B figurinha
|
||||||
|
Converts images/GIFs/videos to WhatsApp stickers.
|
||||||
|
.TP
|
||||||
|
.B video
|
||||||
|
Downloads videos from URLs.
|
||||||
|
.TP
|
||||||
|
.B audio
|
||||||
|
Downloads audio from videos as voice messages.
|
||||||
|
.TP
|
||||||
|
.B adivinha\(,c\(oao
|
||||||
|
Guessing game (1-100).
|
||||||
|
.TP
|
||||||
|
.B forca
|
||||||
|
Hangman game.
|
||||||
|
.TP
|
||||||
|
.B obrigado
|
||||||
|
Responds to thank-you messages.
|
||||||
|
.SH CUSTOM SETTINGS
|
||||||
|
You can add any custom key-value pairs for use by plugins:
|
||||||
|
.PP
|
||||||
|
.nf
|
||||||
|
# In manybot.conf
|
||||||
|
ADMIN_NUMBER=5511999999999@c.us
|
||||||
|
API_KEY=your_secret_key
|
||||||
|
MAX_DOWNLOAD_SIZE=50MB
|
||||||
|
.fi
|
||||||
|
.PP
|
||||||
|
Access in plugins:
|
||||||
|
.nf
|
||||||
|
import { ADMIN_NUMBER, API_KEY, MAX_DOWNLOAD_SIZE } from "../../config.js";
|
||||||
|
.fi
|
||||||
|
.SH EXAMPLES
|
||||||
|
.SS Minimal Configuration
|
||||||
|
.nf
|
||||||
|
# Basic bot setup
|
||||||
|
CLIENT_ID=my_bot
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=en
|
||||||
|
|
||||||
|
PLUGINS=[
|
||||||
|
many
|
||||||
|
]
|
||||||
|
.fi
|
||||||
|
.SS Production Configuration
|
||||||
|
.nf
|
||||||
|
# Production bot with whitelist
|
||||||
|
CLIENT_ID=bot_prod
|
||||||
|
CMD_PREFIX=/
|
||||||
|
LANGUAGE=pt
|
||||||
|
|
||||||
|
CHATS=[
|
||||||
|
5511999999999@c.us,
|
||||||
|
5511888888888-123456789@g.us
|
||||||
|
]
|
||||||
|
|
||||||
|
PLUGINS=[
|
||||||
|
many,
|
||||||
|
figurinha,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
obrigado
|
||||||
|
]
|
||||||
|
|
||||||
|
# Custom settings
|
||||||
|
ADMIN_NUMBER=5511999999999@c.us
|
||||||
|
LOG_LEVEL=info
|
||||||
|
.fi
|
||||||
|
.SS Development Configuration
|
||||||
|
.nf
|
||||||
|
# Debug/development setup
|
||||||
|
CLIENT_ID=bot_dev
|
||||||
|
CMD_PREFIX=!
|
||||||
|
LANGUAGE=en
|
||||||
|
|
||||||
|
# Respond to all chats
|
||||||
|
CHATS=[]
|
||||||
|
|
||||||
|
# All plugins for testing
|
||||||
|
PLUGINS=[
|
||||||
|
many,
|
||||||
|
figurinha,
|
||||||
|
video,
|
||||||
|
audio,
|
||||||
|
adivinha\(,c\(oao,
|
||||||
|
forca,
|
||||||
|
obrigado
|
||||||
|
]
|
||||||
|
.fi
|
||||||
|
.SH FILES
|
||||||
|
.TP
|
||||||
|
.I manybot.conf
|
||||||
|
Main configuration file (must be created by user)
|
||||||
|
.TP
|
||||||
|
.I manybot.conf.example
|
||||||
|
Example configuration with documentation comments
|
||||||
|
.SH NOTES
|
||||||
|
\(bu Keys are case-sensitive
|
||||||
|
.br
|
||||||
|
\(bu Values are read as strings unless they're list syntax
|
||||||
|
.br
|
||||||
|
\(bu Inline comments supported: \fBKEY=value # comment\fR
|
||||||
|
.br
|
||||||
|
\(bu Multiline lists must end with \fB]\fR on its own line or last item line
|
||||||
|
.br
|
||||||
|
\(bu Whitespace in values is trimmed
|
||||||
|
.br
|
||||||
|
\(u Missing optional values use built-in defaults
|
||||||
|
.SH SEE ALSO
|
||||||
|
.BR manybot (1),
|
||||||
|
.BR manybot-plugin (1),
|
||||||
|
.BR manyplug (1)
|
||||||
|
.SH AUTHOR
|
||||||
|
Written by synt-xerror.
|
||||||
30
manybot.conf.example
Normal file
30
manybot.conf.example
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# ManyBot Configuration File
|
||||||
|
# Copy this file to manybot.conf and customize as needed
|
||||||
|
|
||||||
|
# Bot identification
|
||||||
|
CLIENT_ID=meu_bot
|
||||||
|
|
||||||
|
# Command prefix (default: !)
|
||||||
|
CMD_PREFIX=!
|
||||||
|
|
||||||
|
# Language setting (en, pt, es)
|
||||||
|
# Default: en (English)
|
||||||
|
LANGUAGE=en
|
||||||
|
|
||||||
|
# Allowed chats (leave empty for all chats)
|
||||||
|
# Format: chatId1@g.us, chatId2@g.us for groups
|
||||||
|
# Format: number@c.us for private chats
|
||||||
|
CHATS=[
|
||||||
|
]
|
||||||
|
|
||||||
|
# Active plugins
|
||||||
|
PLUGINS=[
|
||||||
|
many,
|
||||||
|
figurinha,
|
||||||
|
audio,
|
||||||
|
video,
|
||||||
|
adivinhação,
|
||||||
|
forca,
|
||||||
|
obrigado,
|
||||||
|
a
|
||||||
|
]
|
||||||
664
package-lock.json
generated
664
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "manybot",
|
"name": "manybot",
|
||||||
"version": "2.4.1",
|
"version": "2.4.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "manybot",
|
"name": "manybot",
|
||||||
"version": "2.4.1",
|
"version": "2.4.3",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-addon-api": "^7",
|
"node-addon-api": "^7",
|
||||||
"node-gyp": "^12.2.0",
|
"node-gyp": "^12.2.0",
|
||||||
@@ -14,6 +14,9 @@
|
|||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"wa-sticker-formatter": "^4.4.4",
|
"wa-sticker-formatter": "^4.4.4",
|
||||||
"whatsapp-web.js": "^1.24.0"
|
"whatsapp-web.js": "^1.24.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"conventional-changelog-cli": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
@@ -39,6 +42,33 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@conventional-changelog/git-client": {
|
||||||
|
"version": "2.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@conventional-changelog/git-client/-/git-client-2.6.0.tgz",
|
||||||
|
"integrity": "sha512-T+uPDciKf0/ioNNDpMGc8FDsehJClZP0yR3Q5MN6wE/Y/1QZ7F+80OgznnTCOlMEG4AV0LvH2UJi3C/nBnaBUg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@simple-libs/child-process-utils": "^1.0.0",
|
||||||
|
"@simple-libs/stream-utils": "^1.2.0",
|
||||||
|
"semver": "^7.5.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"conventional-commits-filter": "^5.0.0",
|
||||||
|
"conventional-commits-parser": "^6.3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"conventional-commits-filter": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"conventional-commits-parser": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@gar/promise-retry": {
|
"node_modules/@gar/promise-retry": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz",
|
||||||
@@ -51,6 +81,16 @@
|
|||||||
"node": "^20.17.0 || >=22.9.0"
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@hutson/parse-repository-url": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hutson/parse-repository-url/-/parse-repository-url-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-e5+YUKENATs1JgYHMzTr2MW/NDcXGfYFAuOQU8gJgF/kEh4EqKgfGrfLI67bMD4tbhZVlkigz/9YYwWcbOFthg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@isaacs/fs-minipass": {
|
"node_modules/@isaacs/fs-minipass": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||||
@@ -127,6 +167,35 @@
|
|||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@simple-libs/child-process-utils": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@simple-libs/child-process-utils/-/child-process-utils-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-/4R8QKnd/8agJynkNdJmNw2MBxuFTRcNFnE5Sg/G+jkSsV8/UBgULMzhizWWW42p8L5H7flImV2ATi79Ove2Tw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@simple-libs/stream-utils": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/dangreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@simple-libs/stream-utils": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://ko-fi.com/dangreen"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@tokenizer/token": {
|
"node_modules/@tokenizer/token": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
|
||||||
@@ -149,6 +218,13 @@
|
|||||||
"undici-types": "~7.18.0"
|
"undici-types": "~7.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/normalize-package-data": {
|
||||||
|
"version": "2.4.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
|
||||||
|
"integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/yauzl": {
|
"node_modules/@types/yauzl": {
|
||||||
"version": "2.10.3",
|
"version": "2.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
|
||||||
@@ -180,6 +256,13 @@
|
|||||||
"node": ">=6.5"
|
"node": ">=6.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/add-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/add-stream/-/add-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "7.1.4",
|
"version": "7.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
@@ -293,6 +376,13 @@
|
|||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||||
"license": "Python-2.0"
|
"license": "Python-2.0"
|
||||||
},
|
},
|
||||||
|
"node_modules/array-ify": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/ast-types": {
|
"node_modules/ast-types": {
|
||||||
"version": "0.13.4",
|
"version": "0.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
|
||||||
@@ -746,6 +836,17 @@
|
|||||||
"simple-swizzle": "^0.2.2"
|
"simple-swizzle": "^0.2.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/compare-func": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"array-ify": "^1.0.0",
|
||||||
|
"dot-prop": "^5.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/compress-commons": {
|
"node_modules/compress-commons": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz",
|
||||||
@@ -769,6 +870,227 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/conventional-changelog": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog/-/conventional-changelog-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-tuUH8H/19VjtD9Ig7l6TQRh+Z0Yt0NZ6w/cCkkyzUbGQTnUEmKfGtkC9gGfVgCfOL1Rzno5NgNF4KY8vR+Jo3w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"conventional-changelog-angular": "^8.0.0",
|
||||||
|
"conventional-changelog-atom": "^5.0.0",
|
||||||
|
"conventional-changelog-codemirror": "^5.0.0",
|
||||||
|
"conventional-changelog-conventionalcommits": "^8.0.0",
|
||||||
|
"conventional-changelog-core": "^8.0.0",
|
||||||
|
"conventional-changelog-ember": "^5.0.0",
|
||||||
|
"conventional-changelog-eslint": "^6.0.0",
|
||||||
|
"conventional-changelog-express": "^5.0.0",
|
||||||
|
"conventional-changelog-jquery": "^6.0.0",
|
||||||
|
"conventional-changelog-jshint": "^5.0.0",
|
||||||
|
"conventional-changelog-preset-loader": "^5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-angular": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"compare-func": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-atom": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-atom/-/conventional-changelog-atom-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-fw7GpI9jHNCWGBnTsPRI452ypQbNupGwsjrXfozvRNE0c92pJRpoj9rXfzDKUYJcsmk0H4XKaQjhjelwI9z27w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-cli": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-cli/-/conventional-changelog-cli-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-9Y8fucJe18/6ef6ZlyIlT2YQUbczvoQZZuYmDLaGvcSBP+M6h+LAvf7ON7waRxKJemcCII8Yqu5/8HEfskTxJQ==",
|
||||||
|
"deprecated": "This package is no longer maintained. Please use the conventional-changelog package instead.",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"add-stream": "^1.0.0",
|
||||||
|
"conventional-changelog": "^6.0.0",
|
||||||
|
"meow": "^13.0.0",
|
||||||
|
"tempfile": "^5.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"conventional-changelog": "cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-codemirror": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-codemirror/-/conventional-changelog-codemirror-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-iXhy63YczB+yWA9DrsYbquSYLvWKsK9M3WC+xQPEm8cOn4oXzKpmTp2uH3qi7+i10oTcGJTvq9lsBpZmMADaNg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-conventionalcommits": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-eOvlTO6OcySPyyyk8pKz2dP4jjElYunj9hn9/s0OB+gapTO8zwS9UQWrZ1pmF2hFs3vw1xhonOLGcGjy/zgsuA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"compare-func": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-core": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-EATUx5y9xewpEe10UEGNpbSHRC6cVZgO+hXQjofMqpy+gFIrcGvH3Fl6yk2VFKh7m+ffenup2N7SZJYpyD9evw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@hutson/parse-repository-url": "^5.0.0",
|
||||||
|
"add-stream": "^1.0.0",
|
||||||
|
"conventional-changelog-writer": "^8.0.0",
|
||||||
|
"conventional-commits-parser": "^6.0.0",
|
||||||
|
"git-raw-commits": "^5.0.0",
|
||||||
|
"git-semver-tags": "^8.0.0",
|
||||||
|
"hosted-git-info": "^7.0.0",
|
||||||
|
"normalize-package-data": "^6.0.0",
|
||||||
|
"read-package-up": "^11.0.0",
|
||||||
|
"read-pkg": "^9.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-ember": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-ember/-/conventional-changelog-ember-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-XNcgGcdJt7wh341BBML0CI8DKpqE5lKD1WahzFHGZFvKTzJr1rZW976cw7beqKLOBbzdrH9ZIkE/s2TfbOuM3g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-eslint": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-eslint/-/conventional-changelog-eslint-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-beWr3qzuEMN9gznMWa8PhTVfGkGXoq+XnUzViNXg5KygrgV728ZRqZngz3uPhz5+ayUhPrpNFYqIE0qHWz9NAw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-express": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-express/-/conventional-changelog-express-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-g/s9eLohrefYTSNQaB6+k0ONbiVx41YOKBbIOIM3ST/NtedAgppCJnrpKXVN9sOmpPkN4vjFwURlfvpEDUjoeg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-jquery": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-jquery/-/conventional-changelog-jquery-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-/sFhULybhFrMg+qc8MHHHSj7kTVMfx5C7rSM6Z9EjduVoAQJdGRq/wpv/SWPMQ+KPNSYHqDLwm/x2Z5hOcYvqQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-jshint": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-jshint/-/conventional-changelog-jshint-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-OaatyvHXP1fjI7Mx0b1IkmhbhTsVHsytnsQSkOj4rhGbFMoTcfvbwm/vAtCzRMXOxojK1EDMBBmBj1pM9KNy/Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"compare-func": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-preset-loader": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-changelog-writer": {
|
||||||
|
"version": "8.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-8.4.0.tgz",
|
||||||
|
"integrity": "sha512-HHBFkk1EECxxmCi4CTu091iuDpQv5/OavuCUAuZmrkWpmYfyD816nom1CvtfXJ/uYfAAjavgHvXHX291tSLK8g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@simple-libs/stream-utils": "^1.2.0",
|
||||||
|
"conventional-commits-filter": "^5.0.0",
|
||||||
|
"handlebars": "^4.7.7",
|
||||||
|
"meow": "^13.0.0",
|
||||||
|
"semver": "^7.5.2"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"conventional-changelog-writer": "dist/cli/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-commits-filter": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/conventional-commits-parser": {
|
||||||
|
"version": "6.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz",
|
||||||
|
"integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@simple-libs/stream-utils": "^1.2.0",
|
||||||
|
"meow": "^13.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"conventional-commits-parser": "dist/cli/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/core-util-is": {
|
"node_modules/core-util-is": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
|
||||||
@@ -908,6 +1230,19 @@
|
|||||||
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
"integrity": "sha512-nv7iKtNZQshSW2hKzYNr46nM/Cfh5SEvE2oV0/SEGgc9XupIY5ggf84Cz8eJIkBce7S3bmTAauFD6aysMpnqsQ==",
|
||||||
"license": "BSD-3-Clause"
|
"license": "BSD-3-Clause"
|
||||||
},
|
},
|
||||||
|
"node_modules/dot-prop": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-obj": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/duplexer2": {
|
"node_modules/duplexer2": {
|
||||||
"version": "0.1.4",
|
"version": "0.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz",
|
||||||
@@ -1156,6 +1491,19 @@
|
|||||||
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/find-up-simple": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fluent-ffmpeg": {
|
"node_modules/fluent-ffmpeg": {
|
||||||
"version": "2.1.3",
|
"version": "2.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz",
|
||||||
@@ -1289,6 +1637,40 @@
|
|||||||
"node": ">= 14"
|
"node": ">= 14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/git-raw-commits": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-Y+csSm2GD/PCSh6Isd/WiMjNAydu0VBiG9J7EdQsNA5P9uXvLayqjmTsNlK5Gs9IhblFZqOU0yid5Il5JPoLiQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@conventional-changelog/git-client": "^2.6.0",
|
||||||
|
"meow": "^13.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"git-raw-commits": "src/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/git-semver-tags": {
|
||||||
|
"version": "8.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-8.0.1.tgz",
|
||||||
|
"integrity": "sha512-zMbamckSNdlT4U48IMFa2Cn6FTzM+2yF6/gEmStPJI8PiLxd/bT6dw10+mc6u5Qe4fhrc/y9nU290FWjQhAV7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@conventional-changelog/git-client": "^2.6.0",
|
||||||
|
"meow": "^13.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"git-semver-tags": "src/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/github-from-package": {
|
"node_modules/github-from-package": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
|
||||||
@@ -1323,6 +1705,48 @@
|
|||||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/handlebars": {
|
||||||
|
"version": "4.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
|
||||||
|
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"minimist": "^1.2.5",
|
||||||
|
"neo-async": "^2.6.2",
|
||||||
|
"source-map": "^0.6.1",
|
||||||
|
"wordwrap": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"handlebars": "bin/handlebars"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.7"
|
||||||
|
},
|
||||||
|
"optionalDependencies": {
|
||||||
|
"uglify-js": "^3.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hosted-git-info": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"lru-cache": "^10.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.14.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hosted-git-info/node_modules/lru-cache": {
|
||||||
|
"version": "10.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
"node_modules/http-cache-semantics": {
|
"node_modules/http-cache-semantics": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||||
@@ -1432,6 +1856,19 @@
|
|||||||
"node": ">=0.8.19"
|
"node": ">=0.8.19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/index-to-position": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/inflight": {
|
"node_modules/inflight": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||||
@@ -1480,6 +1917,16 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-obj": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/isarray": {
|
"node_modules/isarray": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||||
@@ -1654,6 +2101,19 @@
|
|||||||
"node": "^20.17.0 || >=22.9.0"
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/meow": {
|
||||||
|
"version": "13.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz",
|
||||||
|
"integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mime": {
|
"node_modules/mime": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz",
|
||||||
@@ -1868,6 +2328,13 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/neo-async": {
|
||||||
|
"version": "2.6.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
|
||||||
|
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/netmask": {
|
"node_modules/netmask": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz",
|
||||||
@@ -1984,6 +2451,21 @@
|
|||||||
"node": "^20.17.0 || >=22.9.0"
|
"node": "^20.17.0 || >=22.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/normalize-package-data": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"dependencies": {
|
||||||
|
"hosted-git-info": "^7.0.0",
|
||||||
|
"semver": "^7.3.5",
|
||||||
|
"validate-npm-package-license": "^3.0.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.14.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
@@ -2334,6 +2816,62 @@
|
|||||||
"rc": "cli.js"
|
"rc": "cli.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/read-package-up": {
|
||||||
|
"version": "11.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz",
|
||||||
|
"integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"find-up-simple": "^1.0.0",
|
||||||
|
"read-pkg": "^9.0.0",
|
||||||
|
"type-fest": "^4.6.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/read-pkg": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/normalize-package-data": "^2.4.3",
|
||||||
|
"normalize-package-data": "^6.0.0",
|
||||||
|
"parse-json": "^8.0.0",
|
||||||
|
"type-fest": "^4.6.0",
|
||||||
|
"unicorn-magic": "^0.1.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/read-pkg/node_modules/parse-json": {
|
||||||
|
"version": "8.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz",
|
||||||
|
"integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/code-frame": "^7.26.2",
|
||||||
|
"index-to-position": "^1.1.0",
|
||||||
|
"type-fest": "^4.39.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readable-stream": {
|
"node_modules/readable-stream": {
|
||||||
"version": "3.6.2",
|
"version": "3.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
||||||
@@ -2673,12 +3211,48 @@
|
|||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/spdx-correct": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"spdx-expression-parse": "^3.0.0",
|
||||||
|
"spdx-license-ids": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/spdx-exceptions": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "CC-BY-3.0"
|
||||||
|
},
|
||||||
|
"node_modules/spdx-expression-parse": {
|
||||||
|
"version": "3.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
|
||||||
|
"integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"spdx-exceptions": "^2.1.0",
|
||||||
|
"spdx-license-ids": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/spdx-license-ids": {
|
||||||
|
"version": "3.0.23",
|
||||||
|
"resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz",
|
||||||
|
"integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "CC0-1.0"
|
||||||
|
},
|
||||||
"node_modules/ssri": {
|
"node_modules/ssri": {
|
||||||
"version": "13.0.1",
|
"version": "13.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz",
|
||||||
@@ -2830,6 +3404,32 @@
|
|||||||
"streamx": "^2.12.5"
|
"streamx": "^2.12.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/temp-dir": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tempfile": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/tempfile/-/tempfile-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-bX655WZI/F7EoTDw9JvQURqAXiPHi8o8+yFxPF2lWYyz1aHnmMRuXWqL6YB6GmeO0o4DIYWHLgGNi/X64T+X4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"temp-dir": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/text-decoder": {
|
"node_modules/text-decoder": {
|
||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
|
||||||
@@ -2906,12 +3506,39 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/type-fest": {
|
||||||
|
"version": "4.41.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz",
|
||||||
|
"integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/typed-query-selector": {
|
"node_modules/typed-query-selector": {
|
||||||
"version": "2.12.1",
|
"version": "2.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.1.tgz",
|
||||||
"integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
|
"integrity": "sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uglify-js": {
|
||||||
|
"version": "3.19.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
|
||||||
|
"integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "BSD-2-Clause",
|
||||||
|
"optional": true,
|
||||||
|
"bin": {
|
||||||
|
"uglifyjs": "bin/uglifyjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.18.2",
|
"version": "7.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||||
@@ -2919,6 +3546,19 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/unicorn-magic": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/unique-filename": {
|
"node_modules/unique-filename": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz",
|
||||||
@@ -3010,6 +3650,17 @@
|
|||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/validate-npm-package-license": {
|
||||||
|
"version": "3.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
|
||||||
|
"integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"spdx-correct": "^3.0.0",
|
||||||
|
"spdx-expression-parse": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/wa-sticker-formatter": {
|
"node_modules/wa-sticker-formatter": {
|
||||||
"version": "4.4.4",
|
"version": "4.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/wa-sticker-formatter/-/wa-sticker-formatter-4.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/wa-sticker-formatter/-/wa-sticker-formatter-4.4.4.tgz",
|
||||||
@@ -3087,6 +3738,13 @@
|
|||||||
"which": "bin/which"
|
"which": "bin/which"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wordwrap": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/wrap-ansi": {
|
"node_modules/wrap-ansi": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "manybot",
|
"name": "manybot",
|
||||||
"version": "2.4.1",
|
"version": "2.4.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"node-addon-api": "^7",
|
"node-addon-api": "^7",
|
||||||
@@ -9,5 +9,11 @@
|
|||||||
"qrcode-terminal": "^0.12.0",
|
"qrcode-terminal": "^0.12.0",
|
||||||
"wa-sticker-formatter": "^4.4.4",
|
"wa-sticker-formatter": "^4.4.4",
|
||||||
"whatsapp-web.js": "^1.24.0"
|
"whatsapp-web.js": "^1.24.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"conventional-changelog-cli": "^5.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
setup
56
setup
@@ -49,7 +49,8 @@ cat << "EOF"
|
|||||||
|___|
|
|___|
|
||||||
|
|
||||||
website: www.mlplovers.com.br/manybot
|
website: www.mlplovers.com.br/manybot
|
||||||
repo: github.com/synt-xerror/manybot
|
repos: git.maneos.net/synt-xerror/manybot
|
||||||
|
codeberg.org/synt-xerror/manybot
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
echo -e "${RESET}"
|
echo -e "${RESET}"
|
||||||
@@ -170,44 +171,33 @@ log_info "Instalando Chrome"
|
|||||||
npx puppeteer browsers install chrome
|
npx puppeteer browsers install chrome
|
||||||
|
|
||||||
# ------------------------
|
# ------------------------
|
||||||
# Diretórios
|
# ManyPlug CLI
|
||||||
# ------------------------
|
# ------------------------
|
||||||
log_info "Preparando diretórios"
|
log_info "Instalando ManyPlug CLI"
|
||||||
mkdir -p bin
|
|
||||||
log_debug "Diretório bin garantido"
|
|
||||||
|
|
||||||
# ------------------------
|
if ! command -v manyplug &>/dev/null; then
|
||||||
# Arquivos por plataforma
|
log_info "ManyPlug não encontrado, instalando globalmente..."
|
||||||
# ------------------------
|
npm install -g @freakk.dev/manyplug
|
||||||
log_info "Selecionando dependências binárias"
|
log_ok "ManyPlug instalado com sucesso"
|
||||||
|
|
||||||
files=()
|
|
||||||
if [[ "$PLATFORM" == "win" ]]; then
|
|
||||||
log_debug "Usando binários Windows"
|
|
||||||
files=(
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/yt-dlp.exe bin/yt-dlp.exe"
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/ffmpeg.exe bin/ffmpeg.exe"
|
|
||||||
)
|
|
||||||
else
|
else
|
||||||
log_debug "Usando binários Unix"
|
log_ok "ManyPlug já está instalado"
|
||||||
files=(
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/yt-dlp bin/yt-dlp"
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/ffmpeg bin/ffmpeg"
|
|
||||||
)
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x bin/*
|
|
||||||
log_debug "Total de arquivos para baixar: ${#files[@]}"
|
|
||||||
|
|
||||||
# ------------------------
|
# ------------------------
|
||||||
# Download
|
# Configuração de exemplo
|
||||||
# ------------------------
|
# ------------------------
|
||||||
for file in "${files[@]}"; do
|
log_info "Verificando configuração"
|
||||||
url="${file%% *}"
|
|
||||||
dest="${file##* }"
|
|
||||||
|
|
||||||
log_info "Processando dependência"
|
if [[ ! -f "$SCRIPT_DIR/manybot.conf" ]]; then
|
||||||
download_file "$url" "$dest"
|
if [[ -f "$SCRIPT_DIR/manybot.conf.example" ]]; then
|
||||||
done
|
cp "$SCRIPT_DIR/manybot.conf.example" "$SCRIPT_DIR/manybot.conf"
|
||||||
|
log_ok "Arquivo manybot.conf criado a partir do exemplo"
|
||||||
|
log_warn "Edite o manybot.conf para configurar seu bot antes de executar"
|
||||||
|
else
|
||||||
|
log_warn "Arquivo manybot.conf.example não encontrado"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
log_ok "manybot.conf já existe"
|
||||||
|
fi
|
||||||
|
|
||||||
log_ok "Setup concluído com sucesso.\nRode sempre na raíz: 'node src/main.js' (ou equivalente) para rodar o bot."
|
log_ok "Setup concluído com sucesso.\nRode sempre na raíz: 'node src/main.js' para rodar o bot."
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import os from "os";
|
import os from "os";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detecta se o processo está rodando dentro do Termux.
|
* Detect if running inside Termux.
|
||||||
*/
|
*/
|
||||||
export const isTermux =
|
export const isTermux =
|
||||||
(os.platform() === "linux" || os.platform() === "android") &&
|
(os.platform() === "linux" || os.platform() === "android") &&
|
||||||
process.env.PREFIX?.startsWith("/data/data/com.termux");
|
process.env.PREFIX?.startsWith("/data/data/com.termux");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retorna a config de Puppeteer adequada ao ambiente.
|
* Return Puppeteer config suitable for the environment.
|
||||||
* @returns {import("puppeteer").LaunchOptions}
|
* @returns {import("puppeteer").LaunchOptions}
|
||||||
*/
|
*/
|
||||||
export function resolvePuppeteerConfig() {
|
export function resolvePuppeteerConfig() {
|
||||||
|
|||||||
@@ -1,25 +1,26 @@
|
|||||||
import qrcode from "qrcode-terminal";
|
import qrcode from "qrcode-terminal";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
import { isTermux } from "./environment.js";
|
import { isTermux } from "./environment.js";
|
||||||
|
|
||||||
const QR_PATH = path.resolve("qr.png");
|
const QR_PATH = path.resolve("qr.png");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exibe ou salva o QR Code conforme o ambiente.
|
* Display or save QR Code based on environment.
|
||||||
* @param {string} qr — string bruta do evento "qr"
|
* @param {string} qr — raw string from "qr" event
|
||||||
*/
|
*/
|
||||||
export async function handleQR(qr) {
|
export async function handleQR(qr) {
|
||||||
if (isTermux) {
|
if (isTermux) {
|
||||||
try {
|
try {
|
||||||
await QRCode.toFile(QR_PATH, qr, { width: 400 });
|
await QRCode.toFile(QR_PATH, qr, { width: 400 });
|
||||||
logger.info(`QR Code salvo em: ${QR_PATH}`);
|
logger.info(t("system.qrSaved", { path: QR_PATH }));
|
||||||
logger.info(`Abra com: termux-open qr.png`);
|
logger.info(t("system.qrOpen"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error("Falha ao salvar QR Code:", err.message);
|
logger.error(t("system.qrSaveFailed"), err.message);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.info("Escaneie o QR Code abaixo:");
|
logger.info(t("system.qrScan"));
|
||||||
qrcode.generate(qr, { small: true });
|
qrcode.generate(qr, { small: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,19 +1,20 @@
|
|||||||
import pkg from "whatsapp-web.js";
|
import pkg from "whatsapp-web.js";
|
||||||
import { CLIENT_ID } from "../config.js";
|
import { CLIENT_ID } from "../config.js";
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
import { isTermux, resolvePuppeteerConfig } from "./environment.js";
|
import { isTermux, resolvePuppeteerConfig } from "./environment.js";
|
||||||
import { handleQR } from "./qrHandler.js";
|
import { handleQR } from "./qrHandler.js";
|
||||||
import { printBanner } from "./banner.js";
|
import { printBanner } from "./banner.js";
|
||||||
|
|
||||||
export const { Client, LocalAuth, MessageMedia } = pkg;
|
export const { Client, LocalAuth, MessageMedia } = pkg;
|
||||||
|
|
||||||
// ── Ambiente ─────────────────────────────────────────────────
|
// ── Environment ───────────────────────────────────────────────
|
||||||
logger.info(isTermux
|
logger.info(isTermux
|
||||||
? "Ambiente: Termux — usando Chromium do sistema"
|
? t("system.environmentTermux")
|
||||||
: `Ambiente: ${process.platform} — usando Puppeteer padrão`
|
: t("system.environment", { platform: process.platform, puppeteer: "system Puppeteer" })
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Instância ─────────────────────────────────────────────────
|
// ── Instance ──────────────────────────────────────────────────
|
||||||
export const client = new Client({
|
export const client = new Client({
|
||||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||||
puppeteer: {
|
puppeteer: {
|
||||||
@@ -27,20 +28,20 @@ export const client = new Client({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Eventos ───────────────────────────────────────────────────
|
// ── Events ────────────────────────────────────────────────────
|
||||||
client.on("qr", handleQR);
|
client.on("qr", handleQR);
|
||||||
|
|
||||||
client.on("ready", () => {
|
client.on("ready", () => {
|
||||||
printBanner();
|
printBanner();
|
||||||
logger.success("WhatsApp conectado e pronto!");
|
logger.success(t("system.connected"));
|
||||||
logger.info(`Client ID: ${CLIENT_ID}`);
|
logger.info(t("system.clientId", { id: CLIENT_ID }));
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("disconnected", (reason) => {
|
client.on("disconnected", (reason) => {
|
||||||
logger.warn(`Desconectado — motivo: ${reason}`);
|
logger.warn(t("system.disconnected", { reason }));
|
||||||
logger.info("Reconectando em 5s...");
|
logger.info(t("system.reconnecting", { seconds: 5 }));
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
logger.info("Reinicializando cliente...");
|
logger.info(t("system.reinitializing"));
|
||||||
client.initialize();
|
client.initialize();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* config.js
|
* config.js
|
||||||
*
|
*
|
||||||
* Lê e parseia o manybot.conf.
|
* Reads and parses manybot.conf.
|
||||||
* Suporta listas multilinhas e comentários inline.
|
* Supports multiline lists and inline comments.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
function parseConf(raw) {
|
function parseConf(raw) {
|
||||||
const lines = raw.split("\n");
|
const lines = raw.split("\n");
|
||||||
@@ -57,15 +59,23 @@ function parseConf(raw) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const raw = fs.readFileSync("manybot.conf", "utf8");
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const filePath = path.join(__dirname, "../manybot.conf");
|
||||||
|
|
||||||
|
const raw = fs.readFileSync(filePath, "utf8");
|
||||||
const config = parseConf(raw);
|
const config = parseConf(raw);
|
||||||
|
|
||||||
export const CLIENT_ID = config.CLIENT_ID ?? "bot_permanente";
|
export const CLIENT_ID = config.CLIENT_ID ?? "bot_permanente";
|
||||||
export const CMD_PREFIX = config.CMD_PREFIX ?? "!";
|
export const CMD_PREFIX = config.CMD_PREFIX ?? "!";
|
||||||
export const CHATS = config.CHATS ?? [];
|
export const CHATS = config.CHATS ?? [];
|
||||||
|
|
||||||
/** Lista de plugins ativos — ex: PLUGINS=[video, audio, hello] */
|
/** Active plugin list — e.g., PLUGINS=[video, audio, hello] */
|
||||||
export const PLUGINS = config.PLUGINS ?? [];
|
export const PLUGINS = config.PLUGINS ?? [];
|
||||||
|
|
||||||
/** Exporta o config completo para plugins que precisam de valores customizados */
|
/** Bot language — e.g., LANGUAGE=en (fallback: en) */
|
||||||
|
export const LANGUAGE = config.LANGUAGE ?? "en";
|
||||||
|
|
||||||
|
/** Export full config for plugins that need custom values */
|
||||||
export const CONFIG = config;
|
export const CONFIG = config;
|
||||||
@@ -1,18 +1,19 @@
|
|||||||
/**
|
/**
|
||||||
* src/download/queue.js
|
* src/download/queue.js
|
||||||
*
|
*
|
||||||
* Fila de execução sequencial para jobs pesados (downloads, conversões).
|
* Sequential execution queue for heavy jobs (downloads, conversions).
|
||||||
* Garante que apenas um job roda por vez — sem sobrecarregar yt-dlp ou ffmpeg.
|
* Ensures only one job runs at a time — without overloading yt-dlp or ffmpeg.
|
||||||
*
|
*
|
||||||
* O plugin passa uma `workFn` que faz tudo: baixar, converter, enviar.
|
* Plugin passes a `workFn` that does everything: download, convert, send.
|
||||||
* A fila só garante a sequência e trata erros.
|
* Queue only handles sequence and error handling.
|
||||||
*
|
*
|
||||||
* Uso:
|
* Usage:
|
||||||
* import { enqueue } from "../../src/download/queue.js";
|
* import { enqueue } from "../../src/download/queue.js";
|
||||||
* enqueue(async () => { ... toda a lógica do plugin ... }, onError);
|
* enqueue(async () => { ... all plugin logic ... }, onError);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
@@ -26,10 +27,10 @@ let queue = [];
|
|||||||
let processing = false;
|
let processing = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adiciona um job à fila e inicia o processamento se estiver idle.
|
* Add job to queue and start processing if idle.
|
||||||
*
|
*
|
||||||
* @param {Function} workFn — async () => void — toda a lógica do plugin
|
* @param {Function} workFn — async () => void — all plugin logic
|
||||||
* @param {Function} errorFn — async (err) => void — chamado se workFn lançar
|
* @param {Function} errorFn — async (err) => void — called if workFn throws
|
||||||
*/
|
*/
|
||||||
export function enqueue(workFn, errorFn) {
|
export function enqueue(workFn, errorFn) {
|
||||||
queue.push({ workFn, errorFn });
|
queue.push({ workFn, errorFn });
|
||||||
@@ -48,7 +49,7 @@ async function processJob({ workFn, errorFn }) {
|
|||||||
try {
|
try {
|
||||||
await workFn();
|
await workFn();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Falha no job — ${err.message}`);
|
logger.error(t("system.downloadJobFailed", { message: err.message }));
|
||||||
try { await errorFn(err); } catch { }
|
try { await errorFn(err); } catch { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
235
src/i18n/index.js
Normal file
235
src/i18n/index.js
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
/**
|
||||||
|
* i18n/index.js
|
||||||
|
*
|
||||||
|
* Internationalization system for ManyBot.
|
||||||
|
* Loads translations based on LANGUAGE configuration.
|
||||||
|
* Fallback is always English (en).
|
||||||
|
*
|
||||||
|
* Plugins can use createPluginT() to have isolated i18n.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { CONFIG } from "../config.js";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
const LOCALES_DIR = path.join(__dirname, "..", "locales");
|
||||||
|
|
||||||
|
// Default language (fallback)
|
||||||
|
const DEFAULT_LANG = "en";
|
||||||
|
|
||||||
|
// Cache of loaded translations
|
||||||
|
const translations = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a translation JSON file
|
||||||
|
* @param {string} lang - language code (en, pt, es)
|
||||||
|
* @returns {object|null}
|
||||||
|
*/
|
||||||
|
function loadLocale(lang) {
|
||||||
|
if (translations.has(lang)) {
|
||||||
|
return translations.get(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = path.join(LOCALES_DIR, `${lang}.json`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const content = fs.readFileSync(filePath, "utf8");
|
||||||
|
const data = JSON.parse(content);
|
||||||
|
translations.set(lang, data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[i18n] Failed to load locale ${lang}:`, err.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets configured language or default
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function getConfiguredLang() {
|
||||||
|
const lang = CONFIG.LANGUAGE?.trim().toLowerCase();
|
||||||
|
if (!lang) return DEFAULT_LANG;
|
||||||
|
|
||||||
|
// Check if file exists
|
||||||
|
const filePath = path.join(LOCALES_DIR, `${lang}.json`);
|
||||||
|
if (!fs.existsSync(filePath)) {
|
||||||
|
console.warn(`[i18n] Language "${lang}" not found, falling back to "${DEFAULT_LANG}"`);
|
||||||
|
return DEFAULT_LANG;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load languages
|
||||||
|
const currentLang = getConfiguredLang();
|
||||||
|
const currentTranslations = loadLocale(currentLang) || {};
|
||||||
|
const fallbackTranslations = loadLocale(DEFAULT_LANG) || {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a nested value from an object using dot path
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} key - path like "system.connected"
|
||||||
|
* @returns {string|undefined}
|
||||||
|
*/
|
||||||
|
function getNestedValue(obj, key) {
|
||||||
|
const parts = key.split(".");
|
||||||
|
let current = obj;
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (current === null || current === undefined || typeof current !== "object") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = current[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces placeholders {{key}} with values from context
|
||||||
|
* @param {string} str
|
||||||
|
* @param {object} context
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function interpolate(str, context = {}) {
|
||||||
|
return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
||||||
|
return context[key] !== undefined ? String(context[key]) : match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main translation function
|
||||||
|
* @param {string} key - translation key (e.g., "system.connected")
|
||||||
|
* @param {object} context - values to interpolate {{key}}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function t(key, context = {}) {
|
||||||
|
// Try current language first
|
||||||
|
let value = getNestedValue(currentTranslations, key);
|
||||||
|
|
||||||
|
// Fallback to English if not found
|
||||||
|
if (value === undefined) {
|
||||||
|
value = getNestedValue(fallbackTranslations, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not found, return the key
|
||||||
|
if (value === undefined) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not string, convert
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate values
|
||||||
|
return interpolate(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an isolated translation function for a plugin.
|
||||||
|
* Plugins should have their own locale/ folder with en.json, es.json, etc.
|
||||||
|
*
|
||||||
|
* Usage in plugin:
|
||||||
|
* import { createPluginT } from "../../i18n/index.js";
|
||||||
|
* const { t } = createPluginT(import.meta.url);
|
||||||
|
*
|
||||||
|
* Folder structure:
|
||||||
|
* myPlugin/
|
||||||
|
* index.js
|
||||||
|
* locale/
|
||||||
|
* en.json
|
||||||
|
* es.json
|
||||||
|
* pt.json
|
||||||
|
*
|
||||||
|
* @param {string} pluginMetaUrl - import.meta.url from the plugin
|
||||||
|
* @returns {{ t: Function, lang: string }}
|
||||||
|
*/
|
||||||
|
export function createPluginT(pluginMetaUrl) {
|
||||||
|
const pluginDir = path.dirname(fileURLToPath(pluginMetaUrl));
|
||||||
|
const pluginLocaleDir = path.join(pluginDir, "locale");
|
||||||
|
|
||||||
|
// Get bot's configured language
|
||||||
|
const targetLang = currentLang;
|
||||||
|
|
||||||
|
// Load plugin translations
|
||||||
|
let pluginTranslations = {};
|
||||||
|
let pluginFallback = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try to load the configured language
|
||||||
|
const targetPath = path.join(pluginLocaleDir, `${targetLang}.json`);
|
||||||
|
if (fs.existsSync(targetPath)) {
|
||||||
|
pluginTranslations = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always load English as fallback
|
||||||
|
const fallbackPath = path.join(pluginLocaleDir, `${DEFAULT_LANG}.json`);
|
||||||
|
if (fs.existsSync(fallbackPath)) {
|
||||||
|
pluginFallback = JSON.parse(fs.readFileSync(fallbackPath, "utf8"));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
// Silent fail - plugin may not have translations
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Plugin-specific translation function
|
||||||
|
* @param {string} key
|
||||||
|
* @param {object} context
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function pluginT(key, context = {}) {
|
||||||
|
// Try plugin's target language first
|
||||||
|
let value = getNestedValue(pluginTranslations, key);
|
||||||
|
|
||||||
|
// Fallback to plugin's English
|
||||||
|
if (value === undefined) {
|
||||||
|
value = getNestedValue(pluginFallback, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If still not found, return the key
|
||||||
|
if (value === undefined) {
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value !== "string") {
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return interpolate(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { t: pluginT, lang: targetLang };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reloads translations (useful for hot-reload)
|
||||||
|
*/
|
||||||
|
export function reloadTranslations() {
|
||||||
|
translations.clear();
|
||||||
|
const lang = getConfiguredLang();
|
||||||
|
const newTranslations = loadLocale(lang) || {};
|
||||||
|
const newFallback = loadLocale(DEFAULT_LANG) || {};
|
||||||
|
|
||||||
|
// Update references
|
||||||
|
Object.assign(currentTranslations, newTranslations);
|
||||||
|
Object.assign(fallbackTranslations, newFallback);
|
||||||
|
|
||||||
|
console.log(`[i18n] Translations reloaded for language: ${lang}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current language
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export function getCurrentLang() {
|
||||||
|
return currentLang;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { t, createPluginT, reloadTranslations, getCurrentLang };
|
||||||
@@ -1,39 +1,38 @@
|
|||||||
/**
|
/**
|
||||||
* messageHandler.js
|
* messageHandler.js
|
||||||
*
|
*
|
||||||
* Pipeline central de uma mensagem recebida.
|
* Central pipeline for received messages.
|
||||||
*
|
*
|
||||||
* Ordem:
|
* Order:
|
||||||
* 1. Filtra chats não permitidos (CHATS do .conf)
|
* 1. Filter allowed chats (CHATS from .conf)
|
||||||
* 2. Loga a mensagem
|
* — if CHATS is empty, accepts all chats
|
||||||
* 3. Passa o contexto para todos os plugins ativos
|
* 2. Log the message
|
||||||
|
* 3. Pass context to all active plugins
|
||||||
*
|
*
|
||||||
* O kernel não conhece nenhum comando — só distribui.
|
* Kernel knows no commands — only distributes.
|
||||||
* Cada plugin decide por conta própria se age ou ignora.
|
* Each plugin decides on its own whether to act or ignore.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CHATS } from "../config.js";
|
import { CHATS } from "../config.js";
|
||||||
import { getChatId } from "../utils/getChatId.js";
|
import { getChatId } from "../utils/getChatId.js";
|
||||||
import { buildApi } from "./pluginApi.js";
|
import { buildApi } from "./pluginApi.js";
|
||||||
import { pluginRegistry } from "./pluginLoader.js";
|
import { pluginRegistry } from "./pluginLoader.js";
|
||||||
import { runPlugin } from "./pluginGuard.js";
|
import { runPlugin } from "./pluginGuard.js";
|
||||||
import { buildMessageContext } from "../logger/messageContext.js";
|
import { buildMessageContext } from "../logger/messageContext.js";
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import client from "../client/whatsappClient.js";
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {import("whatsapp-web.js").Message} msg
|
|
||||||
*/
|
|
||||||
export async function handleMessage(msg) {
|
export async function handleMessage(msg) {
|
||||||
const chat = await msg.getChat();
|
const chat = await msg.getChat();
|
||||||
const chatId = getChatId(chat);
|
const chatId = getChatId(chat);
|
||||||
|
|
||||||
// CHATS vazio = aceita todos os chats
|
// CHATS empty = accepts all chats
|
||||||
if (CHATS.length > 0 && !CHATS.includes(chatId)) return;
|
if (CHATS.length > 0 && !CHATS.includes(chatId)) return;
|
||||||
|
|
||||||
const ctx = await buildMessageContext(msg, chat);
|
const ctx = await buildMessageContext(msg, chat);
|
||||||
logger.msg(ctx);
|
logger.msg(ctx);
|
||||||
|
|
||||||
const api = buildApi({ msg, chat, pluginRegistry });
|
const api = buildApi({ msg, chat, client, pluginRegistry });
|
||||||
const context = { msg: api.msg, chat: api.chat, api };
|
const context = { msg: api.msg, chat: api.chat, api };
|
||||||
|
|
||||||
for (const plugin of pluginRegistry.values()) {
|
for (const plugin of pluginRegistry.values()) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* pluginApi.js
|
* pluginApi.js
|
||||||
*
|
*
|
||||||
* Monta o objeto `api` que cada plugin recebe.
|
* Builds the `api` object each plugin receives.
|
||||||
* Plugins só podem fazer o que está aqui — nunca tocam no client diretamente.
|
* Plugins can only do what's here — never touch client directly.
|
||||||
*
|
*
|
||||||
* O `chat` já vem filtrado pelo kernel (só chats permitidos no .conf),
|
* `chat` is already filtered by kernel (only allowed chats from .conf),
|
||||||
* então plugins não precisam e não podem escolher destino.
|
* so plugins don't need and can't choose destination.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
@@ -20,51 +20,87 @@ const { MessageMedia } = pkg;
|
|||||||
* @param {Map<string, any>} params.pluginRegistry
|
* @param {Map<string, any>} params.pluginRegistry
|
||||||
* @returns {object} api
|
* @returns {object} api
|
||||||
*/
|
*/
|
||||||
export function buildApi({ msg, chat, pluginRegistry }) {
|
/**
|
||||||
|
* Setup API — without message context.
|
||||||
|
* Passed to plugin.setup(api) during initialization.
|
||||||
|
* Only has sendTo variants, log and schedule.
|
||||||
|
*/
|
||||||
|
export function buildSetupApi(client) {
|
||||||
|
return {
|
||||||
|
async sendTo(chatId, text) {
|
||||||
|
return client.sendMessage(chatId, text);
|
||||||
|
},
|
||||||
|
async sendVideoTo(chatId, filePath, caption = "") {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return client.sendMessage(chatId, media, { caption });
|
||||||
|
},
|
||||||
|
async sendAudioTo(chatId, filePath) {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return client.sendMessage(chatId, media, { sendAudioAsVoice: true });
|
||||||
|
},
|
||||||
|
async sendImageTo(chatId, filePath, caption = "") {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return client.sendMessage(chatId, media, { caption });
|
||||||
|
},
|
||||||
|
async sendStickerTo(chatId, source) {
|
||||||
|
const media = typeof source === "string"
|
||||||
|
? MessageMedia.fromFilePath(source)
|
||||||
|
: new MessageMedia("image/webp", source.toString("base64"));
|
||||||
|
return client.sendMessage(chatId, media, { sendMediaAsSticker: true });
|
||||||
|
},
|
||||||
|
log: {
|
||||||
|
info: (...a) => logger.info(...a),
|
||||||
|
warn: (...a) => logger.warn(...a),
|
||||||
|
error: (...a) => logger.error(...a),
|
||||||
|
success: (...a) => logger.success(...a),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildApi({ msg, chat, client, pluginRegistry }) {
|
||||||
|
|
||||||
// ── Helpers internos ──────────────────────────────────────
|
|
||||||
const currentChat = chat;
|
const currentChat = chat;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
// ── Leitura de mensagem ──────────────────────────────────
|
// ── Message reading ─────────────────────────────────────
|
||||||
|
|
||||||
msg: {
|
msg: {
|
||||||
/** Corpo da mensagem */
|
/** Message body */
|
||||||
body: msg.body ?? "",
|
body: msg.body ?? "",
|
||||||
|
|
||||||
/** Tipo: "chat", "image", "video", "audio", "ptt", "sticker", "document" */
|
/** Type: "chat", "image", "video", "audio", "ptt", "sticker", "document" */
|
||||||
type: msg.type,
|
type: msg.type,
|
||||||
|
|
||||||
/** true se a mensagem veio do próprio bot */
|
/** true if message came from bot itself */
|
||||||
fromMe: msg.fromMe,
|
fromMe: msg.fromMe,
|
||||||
|
|
||||||
/** ID de quem enviou (ex: "5511999999999@c.us") */
|
/** Sender ID (ex: "5511999999999@c.us") */
|
||||||
sender: msg.author || msg.from,
|
sender: msg.author || msg.from,
|
||||||
|
|
||||||
/** Nome de exibição de quem enviou */
|
/** Display name of sender */
|
||||||
senderName: msg._data?.notifyName || String(msg.from).replace(/(:\d+)?@.*$/, ""),
|
senderName: msg._data?.notifyName || String(msg.from).replace(/(:\d+)?@.*$/, ""),
|
||||||
|
|
||||||
/** Tokens: ["!video", "https://..."] */
|
/** Tokens: ["!video", "https://..."] */
|
||||||
args: msg.body?.trim().split(/\s+/) ?? [],
|
args: msg.body?.trim().split(/\s+/) ?? [],
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Verifica se a mensagem é um comando específico.
|
* Check if message is a specific command.
|
||||||
* @param {string} cmd — ex: "!hello"
|
* @param {string} cmd — ex: "!hello"
|
||||||
*/
|
*/
|
||||||
is(cmd) {
|
is(cmd) {
|
||||||
return msg.body?.trim().toLowerCase().startsWith(cmd.toLowerCase());
|
return msg.body?.trim().toLowerCase().startsWith(cmd.toLowerCase());
|
||||||
},
|
},
|
||||||
|
|
||||||
/** true se a mensagem tem mídia anexada */
|
/** true if message has attached media */
|
||||||
hasMedia: msg.hasMedia,
|
hasMedia: msg.hasMedia,
|
||||||
|
|
||||||
/** true se a mídia é um GIF (vídeo curto em loop) */
|
/** true if media is a GIF (short looping video) */
|
||||||
isGif: msg._data?.isGif ?? false,
|
isGif: msg._data?.isGif ?? false,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Baixa a mídia da mensagem.
|
* Download message media.
|
||||||
* Retorna um objeto neutro { mimetype, data } — sem expor MessageMedia.
|
* Returns neutral object { mimetype, data } — without exposing MessageMedia.
|
||||||
* @returns {Promise<{ mimetype: string, data: string } | null>}
|
* @returns {Promise<{ mimetype: string, data: string } | null>}
|
||||||
*/
|
*/
|
||||||
async downloadMedia() {
|
async downloadMedia() {
|
||||||
@@ -73,11 +109,11 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
return { mimetype: media.mimetype, data: media.data };
|
return { mimetype: media.mimetype, data: media.data };
|
||||||
},
|
},
|
||||||
|
|
||||||
/** true se a mensagem é uma resposta a outra */
|
/** true if message is a reply to another */
|
||||||
hasReply: msg.hasQuotedMsg,
|
hasReply: msg.hasQuotedMsg,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retorna a mensagem citada, se existir.
|
* Returns quoted message if exists.
|
||||||
* @returns {Promise<import("whatsapp-web.js").Message|null>}
|
* @returns {Promise<import("whatsapp-web.js").Message|null>}
|
||||||
*/
|
*/
|
||||||
async getReply() {
|
async getReply() {
|
||||||
@@ -86,7 +122,7 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Responde diretamente à mensagem (com quote).
|
* Reply directly to message (with quote).
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
*/
|
*/
|
||||||
async reply(text) {
|
async reply(text) {
|
||||||
@@ -94,10 +130,10 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Envio para o chat atual ──────────────────────────────
|
// ── Send to current chat ─────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envia texto simples.
|
* Send plain text.
|
||||||
* @param {string} text
|
* @param {string} text
|
||||||
*/
|
*/
|
||||||
async send(text) {
|
async send(text) {
|
||||||
@@ -105,7 +141,7 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envia uma mídia (imagem, vídeo, áudio, documento).
|
* Send media (image, video, audio, document).
|
||||||
* @param {import("whatsapp-web.js").MessageMedia} media
|
* @param {import("whatsapp-web.js").MessageMedia} media
|
||||||
* @param {string} [caption]
|
* @param {string} [caption]
|
||||||
*/
|
*/
|
||||||
@@ -114,7 +150,7 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envia um arquivo de vídeo a partir de um caminho local.
|
* Send video file from local path.
|
||||||
* @param {string} filePath
|
* @param {string} filePath
|
||||||
* @param {string} [caption]
|
* @param {string} [caption]
|
||||||
*/
|
*/
|
||||||
@@ -124,7 +160,7 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envia um arquivo de áudio a partir de um caminho local.
|
* Send audio file from local path.
|
||||||
* @param {string} filePath
|
* @param {string} filePath
|
||||||
*/
|
*/
|
||||||
async sendAudio(filePath) {
|
async sendAudio(filePath) {
|
||||||
@@ -133,7 +169,7 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envia uma imagem a partir de um caminho local.
|
* Send image from local path.
|
||||||
* @param {string} filePath
|
* @param {string} filePath
|
||||||
* @param {string} [caption]
|
* @param {string} [caption]
|
||||||
*/
|
*/
|
||||||
@@ -143,9 +179,9 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Envia uma figurinha (sticker).
|
* Send a sticker.
|
||||||
* Aceita filePath (string) ou buffer (Buffer) — o plugin nunca precisa
|
* Accepts filePath (string) or buffer (Buffer) — plugin never needs
|
||||||
* saber que MessageMedia existe.
|
* to know MessageMedia exists.
|
||||||
* @param {string | Buffer} source
|
* @param {string | Buffer} source
|
||||||
*/
|
*/
|
||||||
async sendSticker(source) {
|
async sendSticker(source) {
|
||||||
@@ -155,12 +191,67 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
return currentChat.sendMessage(media, { sendMediaAsSticker: true });
|
return currentChat.sendMessage(media, { sendMediaAsSticker: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Acesso a outros plugins ──────────────────────────────
|
// ── Send to specific chat ───────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retorna a API pública de outro plugin (o que ele exportou em `exports`).
|
* Send text to specific chat by ID.
|
||||||
* Retorna null se o plugin não existir ou estiver desativado.
|
* @param {string} chatId
|
||||||
* @param {string} name — nome do plugin (pasta em /plugins)
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
async sendTo(chatId, text) {
|
||||||
|
return client.sendMessage(chatId, text);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send video to specific chat by ID.
|
||||||
|
* @param {string} chatId
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} [caption]
|
||||||
|
*/
|
||||||
|
async sendVideoTo(chatId, filePath, caption = "") {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return client.sendMessage(chatId, media, { caption });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send audio to specific chat by ID.
|
||||||
|
* @param {string} chatId
|
||||||
|
* @param {string} filePath
|
||||||
|
*/
|
||||||
|
async sendAudioTo(chatId, filePath) {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return client.sendMessage(chatId, media, { sendAudioAsVoice: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send image to specific chat by ID.
|
||||||
|
* @param {string} chatId
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} [caption]
|
||||||
|
*/
|
||||||
|
async sendImageTo(chatId, filePath, caption = "") {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return client.sendMessage(chatId, media, { caption });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send sticker to specific chat by ID.
|
||||||
|
* @param {string} chatId
|
||||||
|
* @param {string | Buffer} source
|
||||||
|
*/
|
||||||
|
async sendStickerTo(chatId, source) {
|
||||||
|
const media = typeof source === "string"
|
||||||
|
? MessageMedia.fromFilePath(source)
|
||||||
|
: new MessageMedia("image/webp", source.toString("base64"));
|
||||||
|
return client.sendMessage(chatId, media, { sendMediaAsSticker: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Access other plugins ─────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return public API of another plugin (what it exported in `exports`).
|
||||||
|
* Returns null if plugin doesn't exist or is disabled.
|
||||||
|
* @param {string} name — plugin name (folder in /plugins)
|
||||||
* @returns {any|null}
|
* @returns {any|null}
|
||||||
*/
|
*/
|
||||||
getPlugin(name) {
|
getPlugin(name) {
|
||||||
@@ -176,7 +267,7 @@ export function buildApi({ msg, chat, pluginRegistry }) {
|
|||||||
success: (...a) => logger.success(...a),
|
success: (...a) => logger.success(...a),
|
||||||
},
|
},
|
||||||
|
|
||||||
// ── Info do chat atual ───────────────────────────────────
|
// ── Current chat info ────────────────────────────────────
|
||||||
|
|
||||||
chat: {
|
chat: {
|
||||||
id: currentChat.id._serialized,
|
id: currentChat.id._serialized,
|
||||||
|
|||||||
@@ -1,20 +1,21 @@
|
|||||||
/**
|
/**
|
||||||
* pluginGuard.js
|
* pluginGuard.js
|
||||||
*
|
*
|
||||||
* Executa um plugin com segurança.
|
* Runs a plugin safely.
|
||||||
* Se o plugin lançar um erro:
|
* If plugin throws an error:
|
||||||
* - Loga o erro com contexto
|
* - Logs error with context
|
||||||
* - Marca o plugin como "error" no registry
|
* - Marks plugin as "error" in registry
|
||||||
* - Nunca derruba o bot
|
* - Never crashes the bot
|
||||||
*
|
*
|
||||||
* Plugins desativados ou com erro são ignorados silenciosamente.
|
* Disabled or errored plugins are silently ignored.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
import { pluginRegistry } from "./pluginLoader.js";
|
import { pluginRegistry } from "./pluginLoader.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object} plugin — entrada do pluginRegistry
|
* @param {object} plugin — pluginRegistry entry
|
||||||
* @param {object} context — { msg, chat, api }
|
* @param {object} context — { msg, chat, api }
|
||||||
*/
|
*/
|
||||||
export async function runPlugin(plugin, context) {
|
export async function runPlugin(plugin, context) {
|
||||||
@@ -23,14 +24,14 @@ export async function runPlugin(plugin, context) {
|
|||||||
try {
|
try {
|
||||||
await plugin.run(context);
|
await plugin.run(context);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Desativa o plugin para não continuar quebrando
|
// Disable plugin to prevent further breakage
|
||||||
plugin.status = "error";
|
plugin.status = "error";
|
||||||
plugin.error = err;
|
plugin.error = err;
|
||||||
pluginRegistry.set(plugin.name, plugin);
|
pluginRegistry.set(plugin.name, plugin);
|
||||||
|
|
||||||
logger.error(
|
logger.error(
|
||||||
`Plugin "${plugin.name}" desativado após erro: ${err.message}`,
|
t("system.pluginDisabledAfterError", { name: plugin.name, message: err.message }),
|
||||||
`\n Stack: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
`\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,26 +1,28 @@
|
|||||||
/**
|
/**
|
||||||
* pluginLoader.js
|
* pluginLoader.js
|
||||||
*
|
*
|
||||||
* Responsável por:
|
* Responsible for:
|
||||||
* 1. Ler quais plugins estão ativos no manybot.conf (PLUGINS=[...])
|
* 1. Reading active plugins from manybot.conf (PLUGINS=[...])
|
||||||
* 2. Carregar cada plugin da pasta /plugins
|
* 2. Loading each plugin from /plugins folder
|
||||||
* 3. Registrar no pluginRegistry com status e exports públicos
|
* 3. Registering in pluginRegistry with status and public exports
|
||||||
* 4. Expor o pluginRegistry para o kernel e para a pluginApi
|
* 4. Exposing pluginRegistry to kernel and pluginApi
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
const PLUGINS_DIR = path.resolve("src/plugins");
|
const PLUGINS_DIR = path.resolve("src/plugins");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cada entrada no registry:
|
* Each entry in registry:
|
||||||
* {
|
* {
|
||||||
* name: string,
|
* name: string,
|
||||||
* status: "active" | "disabled" | "error",
|
* status: "active" | "disabled" | "error",
|
||||||
* run: async function({ msg, chat, api }) — a função default do plugin
|
* run: async function({ msg, chat, api }) — plugin default function
|
||||||
* exports: any — o que o plugin expôs via `export const api = { ... }`
|
* exports: any — what plugin exposed via `export const api = { ... }`
|
||||||
* error: Error | null
|
* error: Error | null
|
||||||
* }
|
* }
|
||||||
*
|
*
|
||||||
@@ -29,14 +31,14 @@ const PLUGINS_DIR = path.resolve("src/plugins");
|
|||||||
export const pluginRegistry = new Map();
|
export const pluginRegistry = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Carrega todos os plugins ativos listados em `activePlugins`.
|
* Load all active plugins listed in `activePlugins`.
|
||||||
* Chamado uma vez na inicialização do bot.
|
* Called once during bot initialization.
|
||||||
*
|
*
|
||||||
* @param {string[]} activePlugins — nomes dos plugins ativos (do .conf)
|
* @param {string[]} activePlugins — active plugin names (from .conf)
|
||||||
*/
|
*/
|
||||||
export async function loadPlugins(activePlugins) {
|
export async function loadPlugins(activePlugins) {
|
||||||
if (!fs.existsSync(PLUGINS_DIR)) {
|
if (!fs.existsSync(PLUGINS_DIR)) {
|
||||||
logger.warn("Pasta /plugins não encontrada. Nenhum plugin carregado.");
|
logger.warn(t("system.pluginsFolderNotFound"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,7 +50,27 @@ export async function loadPlugins(activePlugins) {
|
|||||||
const ativos = [...pluginRegistry.values()].filter(p => p.status === "active").length;
|
const ativos = [...pluginRegistry.values()].filter(p => p.status === "active").length;
|
||||||
const erros = total - ativos;
|
const erros = total - ativos;
|
||||||
|
|
||||||
logger.success(`Plugins carregados: ${ativos} ativos${erros ? `, ${erros} com erro` : ""}`);
|
logger.success(t("system.pluginsLoaded", {
|
||||||
|
count: ativos,
|
||||||
|
errors: erros ? t("system.pluginsLoadedWithErrors", { count: erros }) : ""
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call setup(api) on all plugins that export it.
|
||||||
|
* Executed once after bot connects to WhatsApp.
|
||||||
|
*
|
||||||
|
* @param {object} api — api without message context (only sendTo, log, schedule...)
|
||||||
|
*/
|
||||||
|
export async function setupPlugins(api) {
|
||||||
|
for (const plugin of pluginRegistry.values()) {
|
||||||
|
if (plugin.status !== "active" || !plugin.setup) continue;
|
||||||
|
try {
|
||||||
|
await plugin.setup(api);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(t("system.pluginSetupFailed", { name: plugin.name, message: err.message }));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +81,7 @@ async function loadPlugin(name) {
|
|||||||
const pluginPath = path.join(PLUGINS_DIR, name, "index.js");
|
const pluginPath = path.join(PLUGINS_DIR, name, "index.js");
|
||||||
|
|
||||||
if (!fs.existsSync(pluginPath)) {
|
if (!fs.existsSync(pluginPath)) {
|
||||||
logger.warn(`Plugin "${name}" não encontrado em ${pluginPath}`);
|
logger.warn(t("system.pluginNotFound", { name, path: pluginPath }));
|
||||||
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,22 +89,23 @@ async function loadPlugin(name) {
|
|||||||
try {
|
try {
|
||||||
const mod = await import(pluginPath);
|
const mod = await import(pluginPath);
|
||||||
|
|
||||||
// O plugin deve exportar uma função default — essa é a função chamada a cada mensagem
|
// Plugin must export a default function — this is called on every message
|
||||||
if (typeof mod.default !== "function") {
|
if (typeof mod.default !== "function") {
|
||||||
throw new Error(`Plugin "${name}" não exporta uma função default`);
|
throw new Error(`Plugin "${name}" does not export a default function`);
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginRegistry.set(name, {
|
pluginRegistry.set(name, {
|
||||||
name,
|
name,
|
||||||
status: "active",
|
status: "active",
|
||||||
run: mod.default,
|
run: mod.default,
|
||||||
exports: mod.api ?? null, // exports públicos opcionais (api de outros plugins)
|
setup: mod.setup ?? null, // opcional — chamado uma vez na inicialização
|
||||||
|
exports: mod.api ?? null,
|
||||||
error: null,
|
error: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(`Plugin carregado: ${name}`);
|
logger.info(t("system.pluginLoaded", { name }));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Falha ao carregar plugin "${name}": ${err.message}`);
|
logger.error(t("system.pluginLoadFailed", { name, message: err.message }));
|
||||||
pluginRegistry.set(name, { name, status: "error", run: null, exports: null, error: err });
|
pluginRegistry.set(name, { name, status: "error", run: null, exports: null, error: err });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
99
src/kernel/pluginState.js
Normal file
99
src/kernel/pluginState.js
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* pluginState.js
|
||||||
|
*
|
||||||
|
* Tracks plugin execution state per chat.
|
||||||
|
* Used to implement the service vs non-service behavior:
|
||||||
|
* - Services (service: true) can run regardless of state
|
||||||
|
* - Non-services are blocked when another plugin is running in the same chat
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from "../logger/logger.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map<chatId, { pluginName: string, startedAt: Date }>
|
||||||
|
* Tracks which plugin is currently "holding the lock" in each chat
|
||||||
|
*/
|
||||||
|
const runningPlugins = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if any plugin is currently running in a specific chat
|
||||||
|
* @param {string} chatId - Chat ID (serialized)
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isPluginRunning(chatId) {
|
||||||
|
return runningPlugins.has(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get info about the plugin running in a chat
|
||||||
|
* @param {string} chatId - Chat ID (serialized)
|
||||||
|
* @returns {{ pluginName: string, startedAt: Date } | null}
|
||||||
|
*/
|
||||||
|
export function getRunningPlugin(chatId) {
|
||||||
|
return runningPlugins.get(chatId) ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a plugin as running in a chat
|
||||||
|
* @param {string} chatId - Chat ID (serialized)
|
||||||
|
* @param {string} pluginName - Name of the plugin taking the lock
|
||||||
|
*/
|
||||||
|
export function startPluginRun(chatId, pluginName) {
|
||||||
|
runningPlugins.set(chatId, {
|
||||||
|
pluginName,
|
||||||
|
startedAt: new Date()
|
||||||
|
});
|
||||||
|
logger.debug(`Plugin "${pluginName}" started in chat ${chatId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark a plugin as finished in a chat
|
||||||
|
* @param {string} chatId - Chat ID (serialized)
|
||||||
|
* @param {string} pluginName - Name of the plugin releasing the lock
|
||||||
|
*/
|
||||||
|
export function endPluginRun(chatId, pluginName) {
|
||||||
|
const current = runningPlugins.get(chatId);
|
||||||
|
if (current && current.pluginName === pluginName) {
|
||||||
|
runningPlugins.delete(chatId);
|
||||||
|
logger.debug(`Plugin "${pluginName}" ended in chat ${chatId}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force clear the running state for a chat
|
||||||
|
* Useful for cleanup or admin commands
|
||||||
|
* @param {string} chatId - Chat ID (serialized)
|
||||||
|
*/
|
||||||
|
export function clearPluginRun(chatId) {
|
||||||
|
runningPlugins.delete(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all chats where a specific plugin is running
|
||||||
|
* @param {string} pluginName - Plugin name
|
||||||
|
* @returns {string[]} Array of chat IDs
|
||||||
|
*/
|
||||||
|
export function getChatsWithPlugin(pluginName) {
|
||||||
|
const chats = [];
|
||||||
|
for (const [chatId, info] of runningPlugins.entries()) {
|
||||||
|
if (info.pluginName === pluginName) {
|
||||||
|
chats.push(chatId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get stats about running plugins
|
||||||
|
* @returns {{ total: number, byPlugin: Record<string, number> }}
|
||||||
|
*/
|
||||||
|
export function getStats() {
|
||||||
|
const byPlugin = {};
|
||||||
|
for (const info of runningPlugins.values()) {
|
||||||
|
byPlugin[info.pluginName] = (byPlugin[info.pluginName] || 0) + 1;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
total: runningPlugins.size,
|
||||||
|
byPlugin
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,30 +1,31 @@
|
|||||||
/**
|
/**
|
||||||
* scheduler.js
|
* scheduler.js
|
||||||
*
|
*
|
||||||
* Permite que plugins registrem tarefas agendadas via cron.
|
* Allows plugins to register scheduled tasks via cron.
|
||||||
* Usa node-cron por baixo, mas plugins nunca importam node-cron diretamente —
|
* Uses node-cron underneath, but plugins never import node-cron directly —
|
||||||
* eles chamam apenas api.schedule(cron, fn).
|
* they only call api.schedule(cron, fn).
|
||||||
*
|
*
|
||||||
* Uso no plugin:
|
* Usage in plugin:
|
||||||
* import { schedule } from "many";
|
* import { schedule } from "many";
|
||||||
* schedule("0 9 * * 1", async () => { await api.send("Bom dia!"); });
|
* schedule("0 9 * * 1", async () => { await api.send("Good morning!"); });
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import cron from "node-cron";
|
import cron from "node-cron";
|
||||||
import { logger } from "../logger/logger.js";
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
/** Lista de tasks ativas (para eventual teardown) */
|
/** List of active tasks (for eventual teardown) */
|
||||||
const tasks = [];
|
const tasks = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registra uma tarefa cron.
|
* Register a cron task.
|
||||||
* @param {string} expression — expressão cron ex: "0 9 * * 1"
|
* @param {string} expression — cron expression e.g., "0 9 * * 1"
|
||||||
* @param {Function} fn — função async a executar
|
* @param {Function} fn — async function to execute
|
||||||
* @param {string} pluginName — nome do plugin (para log)
|
* @param {string} pluginName — plugin name (for logging)
|
||||||
*/
|
*/
|
||||||
export function schedule(expression, fn, pluginName = "unknown") {
|
export function schedule(expression, fn, pluginName = "unknown") {
|
||||||
if (!cron.validate(expression)) {
|
if (!cron.validate(expression)) {
|
||||||
logger.warn(`Plugin "${pluginName}" registrou expressão cron inválida: "${expression}"`);
|
logger.warn(t("system.schedulerInvalidCron", { name: pluginName, expression }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,15 +33,15 @@ export function schedule(expression, fn, pluginName = "unknown") {
|
|||||||
try {
|
try {
|
||||||
await fn();
|
await fn();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Erro no agendamento do plugin "${pluginName}": ${err.message}`);
|
logger.error(t("system.schedulerError", { name: pluginName, message: err.message }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tasks.push({ pluginName, expression, task });
|
tasks.push({ pluginName, expression, task });
|
||||||
logger.info(`Agendamento registrado — plugin "${pluginName}" → "${expression}"`);
|
logger.info(t("system.schedulerRegistered", { name: pluginName, expression }));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Para todos os agendamentos (útil no shutdown) */
|
/** Stop all schedules (useful for shutdown) */
|
||||||
export function stopAll() {
|
export function stopAll() {
|
||||||
tasks.forEach(({ task }) => task.stop());
|
tasks.forEach(({ task }) => task.stop());
|
||||||
tasks.length = 0;
|
tasks.length = 0;
|
||||||
|
|||||||
56
src/locales/en.json
Normal file
56
src/locales/en.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"bot": {
|
||||||
|
"starting": "Starting ManyBot...",
|
||||||
|
"initialized": "Client initialized. Waiting for WhatsApp connection...",
|
||||||
|
"ready": "Bot is ready!",
|
||||||
|
"error": {
|
||||||
|
"uncaught": "Uncaught exception",
|
||||||
|
"unhandled": "Unhandled rejection"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"info": "INFO",
|
||||||
|
"success": "OK",
|
||||||
|
"warn": "WARN",
|
||||||
|
"error": "ERROR",
|
||||||
|
"msg": "MSG",
|
||||||
|
"cmd": "CMD",
|
||||||
|
"done": "DONE",
|
||||||
|
"context": {
|
||||||
|
"group": "group",
|
||||||
|
"from": "From",
|
||||||
|
"type": "Type",
|
||||||
|
"replyTo": "replies to"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"environment": "Environment: {{platform}} — using {{puppeteer}}",
|
||||||
|
"environmentTermux": "Environment: Termux — using system Chromium",
|
||||||
|
"connected": "WhatsApp connected and ready!",
|
||||||
|
"disconnected": "Disconnected — reason: {{reason}}",
|
||||||
|
"reconnecting": "Reconnecting in {{seconds}}s...",
|
||||||
|
"reinitializing": "Reinitializing client...",
|
||||||
|
"qrSaved": "QR Code saved to: {{path}}",
|
||||||
|
"qrOpen": "Open with: termux-open qr.png",
|
||||||
|
"qrSaveFailed": "Failed to save QR Code:",
|
||||||
|
"qrScan": "Scan the QR Code below:",
|
||||||
|
"clientId": "Client ID: {{id}}",
|
||||||
|
"pluginsFolderNotFound": "Plugins folder not found. No plugins loaded.",
|
||||||
|
"pluginsLoaded": "Plugins loaded: {{count}} active{{errors}}",
|
||||||
|
"pluginsLoadedWithErrors": ", {{count}} with error",
|
||||||
|
"pluginSetupFailed": "Plugin \"{{name}}\" setup failed: {{message}}",
|
||||||
|
"pluginNotFound": "Plugin \"{{name}}\" not found at {{path}}",
|
||||||
|
"pluginLoaded": "Plugin loaded: {{name}}",
|
||||||
|
"pluginLoadFailed": "Failed to load plugin \"{{name}}\": {{message}}",
|
||||||
|
"pluginDisabledAfterError": "Plugin \"{{name}}\" disabled after error: {{message}}",
|
||||||
|
"schedulerInvalidCron": "Plugin \"{{name}}\" registered invalid cron expression: \"{{expression}}\"",
|
||||||
|
"schedulerError": "Plugin \"{{name}}\" scheduling error: {{message}}",
|
||||||
|
"schedulerRegistered": "Schedule registered — plugin \"{{name}}\" → \"{{expression}}\"",
|
||||||
|
"downloadJobFailed": "Download job failed — {{message}}"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"pluginLoad": "Failed to load plugin",
|
||||||
|
"messageProcess": "Failed to process message",
|
||||||
|
"stack": "Stack"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/locales/es.json
Normal file
56
src/locales/es.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"bot": {
|
||||||
|
"starting": "Iniciando ManyBot...",
|
||||||
|
"initialized": "Cliente inicializado. Esperando conexión con WhatsApp...",
|
||||||
|
"ready": "¡Bot está listo!",
|
||||||
|
"error": {
|
||||||
|
"uncaught": "Excepción no capturada",
|
||||||
|
"unhandled": "Rechazo no manejado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"info": "INFO",
|
||||||
|
"success": "OK",
|
||||||
|
"warn": "WARN",
|
||||||
|
"error": "ERROR",
|
||||||
|
"msg": "MSG",
|
||||||
|
"cmd": "CMD",
|
||||||
|
"done": "DONE",
|
||||||
|
"context": {
|
||||||
|
"group": "grupo",
|
||||||
|
"from": "De",
|
||||||
|
"type": "Tipo",
|
||||||
|
"replyTo": "Responde a"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"environment": "Entorno: {{platform}} — usando {{puppeteer}}",
|
||||||
|
"environmentTermux": "Entorno: Termux — usando Chromium del sistema",
|
||||||
|
"connected": "¡WhatsApp conectado y listo!",
|
||||||
|
"disconnected": "Desconectado — motivo: {{reason}}",
|
||||||
|
"reconnecting": "Reconectando en {{seconds}}s...",
|
||||||
|
"reinitializing": "Reinicializando cliente...",
|
||||||
|
"qrSaved": "Código QR guardado en: {{path}}",
|
||||||
|
"qrOpen": "Abrir con: termux-open qr.png",
|
||||||
|
"qrSaveFailed": "Error al guardar el Código QR:",
|
||||||
|
"qrScan": "Escanea el Código QR abajo:",
|
||||||
|
"clientId": "Client ID: {{id}}",
|
||||||
|
"pluginsFolderNotFound": "Carpeta de plugins no encontrada. Ningún plugin cargado.",
|
||||||
|
"pluginsLoaded": "Plugins cargados: {{count}} activos{{errors}}",
|
||||||
|
"pluginsLoadedWithErrors": ", {{count}} con error",
|
||||||
|
"pluginSetupFailed": "Error en la configuración del plugin \"{{name}}\": {{message}}",
|
||||||
|
"pluginNotFound": "Plugin \"{{name}}\" no encontrado en {{path}}",
|
||||||
|
"pluginLoaded": "Plugin cargado: {{name}}",
|
||||||
|
"pluginLoadFailed": "Error al cargar el plugin \"{{name}}\": {{message}}",
|
||||||
|
"pluginDisabledAfterError": "Plugin \"{{name}}\" desactivado después del error: {{message}}",
|
||||||
|
"schedulerInvalidCron": "Plugin \"{{name}}\" registró expresión cron inválida: \"{{expression}}\"",
|
||||||
|
"schedulerError": "Error en la programación del plugin \"{{name}}\": {{message}}",
|
||||||
|
"schedulerRegistered": "Programación registrada — plugin \"{{name}}\" → \"{{expression}}\"",
|
||||||
|
"downloadJobFailed": "Error en el trabajo de descarga — {{message}}"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"pluginLoad": "Error al cargar el plugin",
|
||||||
|
"messageProcess": "Error al procesar el mensaje",
|
||||||
|
"stack": "Stack"
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/locales/pt.json
Normal file
56
src/locales/pt.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"bot": {
|
||||||
|
"starting": "Iniciando ManyBot...",
|
||||||
|
"initialized": "Cliente inicializado. Aguardando conexão com WhatsApp...",
|
||||||
|
"ready": "Bot está pronto!",
|
||||||
|
"error": {
|
||||||
|
"uncaught": "Exceção não capturada",
|
||||||
|
"unhandled": "Rejeição não tratada"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"log": {
|
||||||
|
"info": "INFO",
|
||||||
|
"success": "OK",
|
||||||
|
"warn": "WARN",
|
||||||
|
"error": "ERRO",
|
||||||
|
"msg": "MSG",
|
||||||
|
"cmd": "CMD",
|
||||||
|
"done": "DONE",
|
||||||
|
"context": {
|
||||||
|
"group": "grupo",
|
||||||
|
"from": "De",
|
||||||
|
"type": "Tipo",
|
||||||
|
"replyTo": "Responde"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": {
|
||||||
|
"environment": "Ambiente: {{platform}} — usando {{puppeteer}}",
|
||||||
|
"environmentTermux": "Ambiente: Termux — usando Chromium do sistema",
|
||||||
|
"connected": "WhatsApp conectado e pronto!",
|
||||||
|
"disconnected": "Desconectado — motivo: {{reason}}",
|
||||||
|
"reconnecting": "Reconectando em {{seconds}}s...",
|
||||||
|
"reinitializing": "Reinicializando cliente...",
|
||||||
|
"qrSaved": "QR Code salvo em: {{path}}",
|
||||||
|
"qrOpen": "Abra com: termux-open qr.png",
|
||||||
|
"qrSaveFailed": "Falha ao salvar QR Code:",
|
||||||
|
"qrScan": "Escaneie o QR Code abaixo:",
|
||||||
|
"clientId": "Client ID: {{id}}",
|
||||||
|
"pluginsFolderNotFound": "Pasta de plugins não encontrada. Nenhum plugin carregado.",
|
||||||
|
"pluginsLoaded": "Plugins carregados: {{count}} ativos{{errors}}",
|
||||||
|
"pluginsLoadedWithErrors": ", {{count}} com erro",
|
||||||
|
"pluginSetupFailed": "Falha na configuração do plugin \"{{name}}\": {{message}}",
|
||||||
|
"pluginNotFound": "Plugin \"{{name}}\" não encontrado em {{path}}",
|
||||||
|
"pluginLoaded": "Plugin carregado: {{name}}",
|
||||||
|
"pluginLoadFailed": "Falha ao carregar plugin \"{{name}}\": {{message}}",
|
||||||
|
"pluginDisabledAfterError": "Plugin \"{{name}}\" desativado após erro: {{message}}",
|
||||||
|
"schedulerInvalidCron": "Plugin \"{{name}}\" registrou expressão cron inválida: \"{{expression}}\"",
|
||||||
|
"schedulerError": "Erro no agendamento do plugin \"{{name}}\": {{message}}",
|
||||||
|
"schedulerRegistered": "Agendamento registrado — plugin \"{{name}}\" → \"{{expression}}\"",
|
||||||
|
"downloadJobFailed": "Falha no job de download — {{message}}"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"pluginLoad": "Falha ao carregar plugin",
|
||||||
|
"messageProcess": "Falha ao processar mensagem",
|
||||||
|
"stack": "Stack"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// ── Paleta ANSI ──────────────────────────────────────────────
|
// ── ANSI Palette ─────────────────────────────────────────────
|
||||||
export const c = {
|
export const c = {
|
||||||
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
|
||||||
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
|
||||||
@@ -6,28 +6,30 @@ export const c = {
|
|||||||
blue: "\x1b[34m", magenta: "\x1b[35m",
|
blue: "\x1b[34m", magenta: "\x1b[35m",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const SEP = `${c.gray}${"─".repeat(52)}${c.reset}`;
|
||||||
|
|
||||||
export const now = () =>
|
export const now = () =>
|
||||||
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" });
|
`[${new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" })}]`;
|
||||||
|
|
||||||
export const formatType = (type) => ({
|
export const formatType = (type) => ({
|
||||||
sticker: `${c.magenta}sticker${c.reset}`,
|
sticker: `${c.magenta}sticker${c.reset}`,
|
||||||
image: `${c.cyan}imagem${c.reset}`,
|
image: `${c.cyan}imagen${c.reset}`,
|
||||||
video: `${c.cyan}vídeo${c.reset}`,
|
video: `${c.cyan}video${c.reset}`,
|
||||||
audio: `${c.cyan}áudio${c.reset}`,
|
audio: `${c.cyan}audio${c.reset}`,
|
||||||
ptt: `${c.cyan}áudio${c.reset}`,
|
ptt: `${c.cyan}audio${c.reset}`,
|
||||||
document: `${c.cyan}arquivo${c.reset}`,
|
document: `${c.cyan}archivo${c.reset}`,
|
||||||
chat: `${c.white}texto${c.reset}`,
|
chat: `${c.white}texto${c.reset}`,
|
||||||
}[type] ?? `${c.gray}${type}${c.reset}`);
|
}[type] ?? `${c.gray}${type}${c.reset}`);
|
||||||
|
|
||||||
export const formatContext = (chatName, isGroup) =>
|
export const formatContext = (chatName, isGroup) =>
|
||||||
isGroup
|
isGroup
|
||||||
? `${c.bold}${chatName}${c.reset} ${c.dim}(grupo)${c.reset}`
|
? `${c.bold}${chatName}${c.reset} ${c.dim}(group)${c.reset}`
|
||||||
: `${c.bold}${chatName}${c.reset} ${c.dim}(privado)${c.reset}`;
|
: `${c.bold}${chatName}${c.reset} ${c.dim}(private)${c.reset}`;
|
||||||
|
|
||||||
export const formatBody = (body) =>
|
export const formatBody = (body, isCommand) =>
|
||||||
body?.trim()
|
body?.trim()
|
||||||
? `${c.green}"${body.length > 200 ? body.slice(0, 200) + "..." : body}"${c.reset}`
|
? `${isCommand ? c.yellow : c.green}"${body.length > 200 ? body.slice(0, 200) + "..." : body}"${c.reset}`
|
||||||
: `${c.dim}<mídia>${c.reset}`;
|
: `${c.dim}<media>${c.reset}`;
|
||||||
|
|
||||||
export const formatReply = (quotedName, quotedNumber, quotedPreview) =>
|
export const formatReply = (quotedName, quotedNumber, quotedPreview) =>
|
||||||
`\n${c.gray} ↩ Para: ${c.reset}${c.white}${quotedName}${c.reset} ${c.dim}+${quotedNumber}${c.reset}` +
|
`\n${c.gray} ↩ Para: ${c.reset}${c.white}${quotedName}${c.reset} ${c.dim}+${quotedNumber}${c.reset}` +
|
||||||
|
|||||||
@@ -2,38 +2,39 @@ import {
|
|||||||
c, now,
|
c, now,
|
||||||
formatType, formatContext, formatBody, formatReply,
|
formatType, formatContext, formatBody, formatReply,
|
||||||
} from "./formatter.js";
|
} from "./formatter.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logger central do ManyBot.
|
* ManyBot central logger.
|
||||||
* Cada método lida apenas com saída — sem lógica de negócio ou I/O externo.
|
* Each method only handles output — no business logic or external I/O.
|
||||||
*/
|
*/
|
||||||
export const logger = {
|
export const logger = {
|
||||||
info: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.cyan}INFO ${c.reset}`, ...a),
|
info: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.cyan}INFO ${c.reset}`, ...a),
|
||||||
success: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.green}OK ${c.reset}`, ...a),
|
success: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.green}OK ${c.reset}`, ...a),
|
||||||
warn: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.yellow}WARN ${c.reset}`, ...a),
|
warn: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.yellow}WARN ${c.reset}`, ...a),
|
||||||
error: (...a) => console.log(`${c.gray}[${now()}]${c.reset} ${c.red}ERROR ${c.reset}`, ...a),
|
error: (...a) => console.log(`${c.gray}${now()}${c.reset}${c.red}ERROR ${c.reset}`, ...a),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loga uma mensagem recebida a partir de um contexto já resolvido.
|
* Log a received message from a resolved context.
|
||||||
* @param {import("./messageContext.js").MessageContext} ctx
|
* @param {import("./messageContext.js").MessageContext} ctx
|
||||||
*/
|
*/
|
||||||
msg(ctx) {
|
msg(ctx) {
|
||||||
const { chatName, isGroup, senderName, senderNumber, type, body, quoted } = ctx;
|
const { chatName, isGroup, senderName, senderNumber, type, body, quoted } = ctx;
|
||||||
const context = isGroup ? `${chatName} (grupo)` : chatName;
|
const context = isGroup ? `${chatName} (${t("log.context.group")})` : chatName;
|
||||||
const reply = quoted ? ` → Responde ${quoted.name} +${quoted.number}: "${quoted.preview}"` : "";
|
const reply = quoted ? ` → ${t("log.context.replyTo")} ${quoted.name} +${quoted.number}: "${quoted.preview}"` : "";
|
||||||
console.log(`\n${c.gray}[${now()}]${c.reset} ${c.cyan}MSG${c.reset} ${context} ${c.gray}— De:${c.reset} ${c.white}${senderName}${c.reset} ${c.dim}+${senderNumber}${c.reset} ${c.gray}— Tipo:${c.reset} ${type} — ${c.green}"${body}"${c.reset}${c.gray}${reply}${c.reset}`);
|
console.log(`\n${c.gray}${now()}${c.reset}${c.cyan}MSG${c.reset} ${context} ${c.gray}— ${t("log.context.from")}:${c.reset} ${c.white}${senderName}${c.reset} ${c.dim}+${senderNumber}${c.reset} ${c.gray}— ${t("log.context.type")}:${c.reset} ${type} — ${c.green}"${body}"${c.reset}${c.gray}${reply}${c.reset}`);
|
||||||
},
|
},
|
||||||
|
|
||||||
cmd: (cmd, extra = "") =>
|
cmd: (cmd, extra = "") =>
|
||||||
console.log(
|
console.log(
|
||||||
`${c.gray}[${now()}]${c.reset} ${c.yellow}CMD ${c.reset}` +
|
`${c.gray}${now()}${c.reset}${c.yellow}CMD ${c.reset}` +
|
||||||
`${c.bold}${cmd}${c.reset}` +
|
`${c.bold}${cmd}${c.reset}` +
|
||||||
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
|
||||||
),
|
),
|
||||||
|
|
||||||
done: (cmd, detail = "") =>
|
done: (cmd, detail = "") =>
|
||||||
console.log(
|
console.log(
|
||||||
`${c.gray}[${now()}]${c.reset} ${c.green}DONE ${c.reset}` +
|
`${c.gray}${now()}${c.reset}${c.green}DONE ${c.reset}` +
|
||||||
`${c.dim}${cmd}${c.reset}` +
|
`${c.dim}${cmd}${c.reset}` +
|
||||||
(detail ? ` — ${detail}` : "")
|
(detail ? ` — ${detail}` : "")
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import client from "../client/whatsappClient.js";
|
import client from "../client/whatsappClient.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extrai o número limpo de uma mensagem.
|
* Extract clean number from message.
|
||||||
* @param {import("whatsapp-web.js").Message} msg
|
* @param {import("whatsapp-web.js").Message} msg
|
||||||
* @returns {Promise<string>}
|
* @returns {Promise<string>}
|
||||||
*/
|
*/
|
||||||
@@ -12,8 +12,8 @@ export async function getNumber(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Monta o contexto completo de uma mensagem para logging.
|
* Build full message context for logging.
|
||||||
* Resolve contato, quoted message e metadados do chat.
|
* Resolves contact, quoted message and chat metadata.
|
||||||
*
|
*
|
||||||
* @param {import("whatsapp-web.js").Message} msg
|
* @param {import("whatsapp-web.js").Message} msg
|
||||||
* @param {import("whatsapp-web.js").Chat} chat
|
* @param {import("whatsapp-web.js").Chat} chat
|
||||||
@@ -51,8 +51,8 @@ export async function buildMessageContext(msg, chat, botPrefix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve os dados da mensagem citada, se existir.
|
* Resolve quoted message data if exists.
|
||||||
* Retorna null em caso de erro ou ausência.
|
* Returns null on error or if not present.
|
||||||
*
|
*
|
||||||
* @param {import("whatsapp-web.js").Message} msg
|
* @param {import("whatsapp-web.js").Message} msg
|
||||||
* @returns {Promise<{ name: string, number: string, preview: string } | null>}
|
* @returns {Promise<{ name: string, number: string, preview: string } | null>}
|
||||||
@@ -68,7 +68,7 @@ async function resolveQuotedMessage(msg) {
|
|||||||
try {
|
try {
|
||||||
const contact = await client.getContactById(quoted.from);
|
const contact = await client.getContactById(quoted.from);
|
||||||
quotedName = contact?.pushname || contact?.formattedName || quotedNumber;
|
quotedName = contact?.pushname || contact?.formattedName || quotedNumber;
|
||||||
} catch { /* contato não encontrado — usa o número */ }
|
} catch { /* contact not found — use number */ }
|
||||||
|
|
||||||
const quotedPreview = quoted.body?.trim()
|
const quotedPreview = quoted.body?.trim()
|
||||||
? `"${quoted.body.length > 80 ? quoted.body.slice(0, 80) + "…" : quoted.body}"`
|
? `"${quoted.body.length > 80 ? quoted.body.slice(0, 80) + "…" : quoted.body}"`
|
||||||
|
|||||||
27
src/main.js
27
src/main.js
@@ -1,29 +1,31 @@
|
|||||||
/**
|
/**
|
||||||
* main.js
|
* main.js
|
||||||
*
|
*
|
||||||
* Ponto de entrada do ManyBot.
|
* ManyBot entry point.
|
||||||
* Inicializa o cliente WhatsApp e carrega os plugins.
|
* Initializes WhatsApp client and loads plugins.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import client from "./client/whatsappClient.js";
|
import client from "./client/whatsappClient.js";
|
||||||
import { handleMessage } from "./kernel/messageHandler.js";
|
import { handleMessage } from "./kernel/messageHandler.js";
|
||||||
import { loadPlugins } from "./kernel/pluginLoader.js";
|
import { loadPlugins, setupPlugins } from "./kernel/pluginLoader.js";
|
||||||
|
import { buildSetupApi } from "./kernel/pluginApi.js";
|
||||||
import { logger } from "./logger/logger.js";
|
import { logger } from "./logger/logger.js";
|
||||||
import { PLUGINS } from "./config.js";
|
import { PLUGINS } from "./config.js";
|
||||||
|
import { t } from "./i18n/index.js";
|
||||||
|
|
||||||
logger.info("Iniciando ManyBot...\n");
|
logger.info(t("bot.starting"));
|
||||||
|
|
||||||
// Rede de segurança global — nenhum erro deve derrubar o bot
|
// Global safety net — no error should crash the bot
|
||||||
process.on("uncaughtException", (err) => {
|
process.on("uncaughtException", (err) => {
|
||||||
logger.error(`uncaughtException — ${err.message}`, `\n Stack: ${err.stack?.split("\n")[1]?.trim() ?? ""}`);
|
logger.error(`${t("bot.error.uncaught")} — ${err.message}`, `\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
process.on("unhandledRejection", (reason) => {
|
process.on("unhandledRejection", (reason) => {
|
||||||
const msg = reason instanceof Error ? reason.message : String(reason);
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
||||||
logger.error(`unhandledRejection — ${msg}`);
|
logger.error(`${t("bot.error.unhandled")} — ${msg}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Carrega plugins antes de conectar
|
// Load plugins before connecting
|
||||||
await loadPlugins(PLUGINS);
|
await loadPlugins(PLUGINS);
|
||||||
|
|
||||||
client.on("message_create", async (msg) => {
|
client.on("message_create", async (msg) => {
|
||||||
@@ -31,12 +33,15 @@ client.on("message_create", async (msg) => {
|
|||||||
await handleMessage(msg);
|
await handleMessage(msg);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Falha ao processar — ${err.message}`,
|
`${t("errors.messageProcess")} — ${err.message}`,
|
||||||
`\n Stack: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
`\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
client.on("ready", async () => {
|
||||||
|
await setupPlugins(buildSetupApi(client));
|
||||||
|
});
|
||||||
client.initialize();
|
client.initialize();
|
||||||
console.log("\n");
|
console.log("\n");
|
||||||
logger.info("Cliente inicializado. Aguardando conexão com WhatsApp...");
|
logger.info(t("bot.initialized"));
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { forcaAtiva } from "../forca/index.js";
|
|
||||||
|
|
||||||
export default async function ({ msg }) {
|
|
||||||
if (msg.body.trim().toLowerCase() !== "a") return;
|
|
||||||
if (msg.args.length > 1) return;
|
|
||||||
if (forcaAtiva) return;
|
|
||||||
|
|
||||||
await msg.reply("B!");
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/adivinhacao/index.js
|
|
||||||
*
|
|
||||||
* Estado dos jogos fica aqui dentro — isolado no plugin.
|
|
||||||
* Múltiplos grupos jogam simultaneamente sem conflito.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const RANGE = { min: 1, max: 100 };
|
|
||||||
const jogosAtivos = new Map();
|
|
||||||
import { CMD_PREFIX } from "../../config.js"
|
|
||||||
|
|
||||||
const sorteio = () =>
|
|
||||||
Math.floor(Math.random() * (RANGE.max - RANGE.min + 1)) + RANGE.min;
|
|
||||||
|
|
||||||
export default async function ({ msg, api }) {
|
|
||||||
const chatId = api.chat.id;
|
|
||||||
|
|
||||||
// ── Comando adivinhação ──────────────────────────────────
|
|
||||||
if (msg.is(CMD_PREFIX + "adivinhação")) {
|
|
||||||
const sub = msg.args[1];
|
|
||||||
|
|
||||||
if (!sub) {
|
|
||||||
await api.send(
|
|
||||||
"🎮 *Jogo de adivinhação:*\n\n" +
|
|
||||||
`\`${CMD_PREFIX}adivinhação começar\` — inicia o jogo\n` +
|
|
||||||
`\`${CMD_PREFIX}adivinhação parar\` — encerra o jogo`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sub === "começar") {
|
|
||||||
jogosAtivos.set(chatId, sorteio());
|
|
||||||
await api.send(
|
|
||||||
"🎮 *Jogo iniciado!*\n\n" +
|
|
||||||
"Estou pensando em um número de 1 a 100.\n" +
|
|
||||||
"Tente adivinhar! 🤔"
|
|
||||||
);
|
|
||||||
api.log.info(CMD_PREFIX + "adivinhação — jogo iniciado");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sub === "parar") {
|
|
||||||
jogosAtivos.delete(chatId);
|
|
||||||
await api.send("🛑 Jogo encerrado.");
|
|
||||||
api.log.info(CMD_PREFIX + "adivinhação — jogo parado");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await api.send(
|
|
||||||
`❌ Subcomando *${sub}* não existe.\n\n` +
|
|
||||||
`Use ${CMD_PREFIX} + \`adivinhação começar\` ou ${CMD_PREFIX} + \`adivinhação parar\`.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Tentativas durante o jogo ─────────────────────────────
|
|
||||||
const numero = jogosAtivos.get(chatId);
|
|
||||||
if (numero === undefined) return;
|
|
||||||
|
|
||||||
const tentativa = msg.body.trim();
|
|
||||||
if (!/^\d+$/.test(tentativa)) return;
|
|
||||||
|
|
||||||
const num = parseInt(tentativa, 10);
|
|
||||||
|
|
||||||
if (num < RANGE.min || num > RANGE.max) {
|
|
||||||
await msg.reply(`⚠️ Digite um número entre ${RANGE.min} e ${RANGE.max}.`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (num === numero) {
|
|
||||||
await msg.reply(
|
|
||||||
`🎉 *Acertou!* O número era ${numero}!\n\n` +
|
|
||||||
`Use ${CMD_PREFIX} + \`adivinhação começar\` para jogar de novo.`
|
|
||||||
);
|
|
||||||
jogosAtivos.delete(chatId);
|
|
||||||
} else {
|
|
||||||
await api.send(num > numero ? "📉 Tente um número *menor*!" : "📈 Tente um número *maior*!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/audio/index.js
|
|
||||||
*
|
|
||||||
* Baixa vídeo via yt-dlp, converte para mp3 via ffmpeg e envia no chat.
|
|
||||||
* Todo o processo (download + conversão + envio + limpeza) fica aqui.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { spawn } from "child_process";
|
|
||||||
import { execFile } from "child_process";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import { enqueue } from "../../download/queue.js";
|
|
||||||
import { emptyFolder } from "../../utils/file.js";
|
|
||||||
import { CMD_PREFIX } from "../../config.js";
|
|
||||||
|
|
||||||
const logStream = fs.createWriteStream("logs/audio-error.log", { flags: "a" });
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
|
||||||
|
|
||||||
const DOWNLOADS_DIR = path.resolve("downloads");
|
|
||||||
const YT_DLP = os.platform() === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp";
|
|
||||||
const FFMPEG = os.platform() === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg";
|
|
||||||
|
|
||||||
const ARGS_BASE = [
|
|
||||||
"--extractor-args", "youtube:player_client=android",
|
|
||||||
"--print", "after_move:filepath",
|
|
||||||
"--cookies", "cookies.txt",
|
|
||||||
"--add-header", "User-Agent:Mozilla/5.0",
|
|
||||||
"--add-header", "Referer:https://www.youtube.com/",
|
|
||||||
"--retries", "4",
|
|
||||||
"--fragment-retries", "5",
|
|
||||||
"--socket-timeout", "15",
|
|
||||||
"--sleep-interval", "1",
|
|
||||||
"--max-sleep-interval", "4",
|
|
||||||
"--no-playlist",
|
|
||||||
"-f", "bv+ba/best",
|
|
||||||
];
|
|
||||||
|
|
||||||
function downloadRaw(url, id) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
|
|
||||||
|
|
||||||
const output = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`);
|
|
||||||
const proc = spawn(YT_DLP, [...ARGS_BASE, "--output", output, url]);
|
|
||||||
let stdout = "";
|
|
||||||
|
|
||||||
proc.on("error", err => reject(new Error(
|
|
||||||
err.code === "EACCES"
|
|
||||||
? "Sem permissão para executar o yt-dlp. Rode: chmod +x ./bin/yt-dlp"
|
|
||||||
: err.code === "ENOENT"
|
|
||||||
? "yt-dlp não encontrado em ./bin/yt-dlp"
|
|
||||||
: `Erro ao iniciar o yt-dlp: ${err.message}`
|
|
||||||
)));
|
|
||||||
|
|
||||||
proc.stdout.on("data", d => { stdout += d.toString(); });
|
|
||||||
proc.stderr.on("data", d => logStream.write(d));
|
|
||||||
|
|
||||||
proc.on("close", code => {
|
|
||||||
if (code !== 0) return reject(new Error(
|
|
||||||
"Não foi possível baixar o áudio. Verifique se o link é válido e tente novamente."
|
|
||||||
));
|
|
||||||
|
|
||||||
const filePath = stdout.trim().split("\n").filter(Boolean).at(-1);
|
|
||||||
if (!filePath || !fs.existsSync(filePath)) return reject(new Error(
|
|
||||||
"Download concluído mas arquivo não encontrado. Tente novamente."
|
|
||||||
));
|
|
||||||
|
|
||||||
resolve(filePath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function convertToMp3(videoPath, id) {
|
|
||||||
const mp3Path = path.join(DOWNLOADS_DIR, `${id}.mp3`);
|
|
||||||
|
|
||||||
await execFileAsync(FFMPEG, [
|
|
||||||
"-i", videoPath,
|
|
||||||
"-vn", // sem vídeo
|
|
||||||
"-ar", "44100", // sample rate
|
|
||||||
"-ac", "2", // stereo
|
|
||||||
"-b:a", "192k", // bitrate
|
|
||||||
"-y", // sobrescreve se existir
|
|
||||||
mp3Path,
|
|
||||||
]);
|
|
||||||
|
|
||||||
fs.unlinkSync(videoPath); // remove o vídeo intermediário
|
|
||||||
return mp3Path;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ({ msg, api }) {
|
|
||||||
if (!msg.is(CMD_PREFIX + "audio")) return;
|
|
||||||
|
|
||||||
const url = msg.args[1];
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
await msg.reply(`❌ Você precisa informar um link.\n\nExemplo: \`${CMD_PREFIX}audio https://youtube.com/...\``);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply("⏳ Baixando o áudio, aguarde...");
|
|
||||||
|
|
||||||
const id = `audio-${Date.now()}`;
|
|
||||||
|
|
||||||
enqueue(
|
|
||||||
async () => {
|
|
||||||
const videoPath = await downloadRaw(url, id);
|
|
||||||
const mp3Path = await convertToMp3(videoPath, id);
|
|
||||||
await api.sendAudio(mp3Path);
|
|
||||||
fs.unlinkSync(mp3Path);
|
|
||||||
emptyFolder(DOWNLOADS_DIR);
|
|
||||||
api.log.info(`${CMD_PREFIX}audio concluído → ${url}`);
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
await msg.reply(
|
|
||||||
"❌ Não consegui baixar o áudio.\n\n" +
|
|
||||||
"Verifique se o link é válido e tente novamente.\n" +
|
|
||||||
"Se o problema persistir, o conteúdo pode estar indisponível ou protegido."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,247 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/figurinha/index.js
|
|
||||||
*
|
|
||||||
* Modos de uso:
|
|
||||||
* comando + mídia anexa → cria 1 sticker direto
|
|
||||||
* comando + respondendo mídia → cria 1 sticker direto
|
|
||||||
* comando + mídia anexa + respondendo mídia → cria 2 stickers direto
|
|
||||||
* comando (sem mídia nenhuma) → abre sessão
|
|
||||||
* comando criar (com sessão aberta) → processa as mídias da sessão
|
|
||||||
*/
|
|
||||||
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import { execFile } from "child_process";
|
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
import { createSticker } from "wa-sticker-formatter";
|
|
||||||
import { emptyFolder } from "../../utils/file.js";
|
|
||||||
import { CMD_PREFIX } from "../../config.js";
|
|
||||||
|
|
||||||
const execFileAsync = promisify(execFile);
|
|
||||||
|
|
||||||
// ── Constantes ────────────────────────────────────────────────
|
|
||||||
const DOWNLOADS_DIR = path.resolve("downloads");
|
|
||||||
const FFMPEG = os.platform() === "win32" ? ".\\bin\\ffmpeg.exe" : "./bin/ffmpeg";
|
|
||||||
const MAX_STICKER_SIZE = 900 * 1024;
|
|
||||||
const SESSION_TIMEOUT = 2 * 60 * 1000;
|
|
||||||
const MAX_MEDIA = 30;
|
|
||||||
|
|
||||||
const HELP =
|
|
||||||
"📌 *Como criar figurinhas:*\n\n" +
|
|
||||||
`1️⃣ Envie \`${CMD_PREFIX}figurinha\` junto com uma mídia, ou respondendo uma mídia\n` +
|
|
||||||
" — o sticker é criado na hora\n\n" +
|
|
||||||
"2️⃣ Ou use o modo sessão para várias mídias de uma vez:\n" +
|
|
||||||
` — \`${CMD_PREFIX}figurinha\` sem mídia para iniciar\n` +
|
|
||||||
" — envie as imagens, GIFs ou vídeos\n" +
|
|
||||||
` — \`${CMD_PREFIX}figurinha criar\` para gerar todas\n\n` +
|
|
||||||
"⏳ A sessão expira em 2 minutos se nenhuma mídia for enviada.";
|
|
||||||
|
|
||||||
// ── Estado interno ────────────────────────────────────────────
|
|
||||||
// { chatId → { author, medias[], timeout } }
|
|
||||||
const sessions = new Map();
|
|
||||||
|
|
||||||
// ── Conversão ─────────────────────────────────────────────────
|
|
||||||
function ensureDir() {
|
|
||||||
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup(...files) {
|
|
||||||
for (const f of files) {
|
|
||||||
try { if (f && fs.existsSync(f)) fs.unlinkSync(f); } catch { }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function convertToGif(input, output, fps = 12) {
|
|
||||||
const filter = [
|
|
||||||
`fps=${Math.min(fps, 12)},scale=512:512:flags=lanczos,split[s0][s1]`,
|
|
||||||
`[s0]palettegen=max_colors=256:reserve_transparent=1[p]`,
|
|
||||||
`[s1][p]paletteuse=dither=bayer`,
|
|
||||||
].join(";");
|
|
||||||
await execFileAsync(FFMPEG, ["-i", input, "-filter_complex", filter, "-loop", "0", "-y", output]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resizeImage(input, output) {
|
|
||||||
await execFileAsync(FFMPEG, ["-i", input, "-vf", "scale=512:512:flags=lanczos", "-y", output]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function buildSticker(inputPath, isAnimated) {
|
|
||||||
for (const quality of [80, 60, 40, 20]) {
|
|
||||||
const buf = await createSticker(fs.readFileSync(inputPath), {
|
|
||||||
pack: "Criada por ManyBot\n",
|
|
||||||
author: "\ngithub.com/synt-xerror/manybot",
|
|
||||||
type: isAnimated ? "FULL" : "STATIC",
|
|
||||||
categories: ["🤖"],
|
|
||||||
quality,
|
|
||||||
});
|
|
||||||
if (buf.length <= MAX_STICKER_SIZE) return buf;
|
|
||||||
}
|
|
||||||
throw new Error("Não foi possível reduzir o sticker para menos de 900 KB.");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converte um objeto { mimetype, data } em sticker e envia.
|
|
||||||
* Retorna true se ok, false se falhou.
|
|
||||||
*/
|
|
||||||
async function processarUmaMedia(media, isGif, api, msg) {
|
|
||||||
ensureDir();
|
|
||||||
|
|
||||||
const ext = media.mimetype.split("/")[1];
|
|
||||||
const isVideo = media.mimetype.startsWith("video/");
|
|
||||||
const isAnimated = isVideo || isGif;
|
|
||||||
|
|
||||||
const id = `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
||||||
const inputPath = path.join(DOWNLOADS_DIR, `${id}.${ext}`);
|
|
||||||
const gifPath = path.join(DOWNLOADS_DIR, `${id}.gif`);
|
|
||||||
const resizedPath = path.join(DOWNLOADS_DIR, `${id}-scaled.${ext}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.writeFileSync(inputPath, Buffer.from(media.data, "base64"));
|
|
||||||
|
|
||||||
let stickerInput;
|
|
||||||
if (isAnimated) {
|
|
||||||
await convertToGif(inputPath, gifPath, isVideo ? 12 : 24);
|
|
||||||
stickerInput = gifPath;
|
|
||||||
} else {
|
|
||||||
await resizeImage(inputPath, resizedPath);
|
|
||||||
stickerInput = resizedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buf = await buildSticker(stickerInput, isAnimated);
|
|
||||||
await api.sendSticker(buf);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
api.log.error(`Erro ao gerar sticker: ${err.message}`);
|
|
||||||
await msg.reply(
|
|
||||||
"⚠️ Não consegui criar uma das figurinhas.\n" +
|
|
||||||
"Tente reenviar essa mídia ou use outro formato (JPG, PNG, GIF, MP4)."
|
|
||||||
);
|
|
||||||
return false;
|
|
||||||
} finally {
|
|
||||||
cleanup(inputPath, gifPath, resizedPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifica se uma mídia é suportada para sticker.
|
|
||||||
*/
|
|
||||||
function isSupported(media, isGif) {
|
|
||||||
return (
|
|
||||||
media.mimetype?.startsWith("image/") ||
|
|
||||||
media.mimetype?.startsWith("video/") ||
|
|
||||||
isGif
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Plugin ────────────────────────────────────────────────────
|
|
||||||
export default async function ({ msg, api }) {
|
|
||||||
const chatId = api.chat.id;
|
|
||||||
|
|
||||||
if (!msg.is(CMD_PREFIX + "figurinha")) {
|
|
||||||
// ── Coleta de mídia durante sessão ──────────────────────
|
|
||||||
const session = sessions.get(chatId);
|
|
||||||
if (!session) return;
|
|
||||||
if (!msg.hasMedia) return;
|
|
||||||
if (msg.sender !== session.author) return;
|
|
||||||
|
|
||||||
const media = await msg.downloadMedia();
|
|
||||||
if (!media) return;
|
|
||||||
|
|
||||||
const gif = media.mimetype === "image/gif" ||
|
|
||||||
(media.mimetype === "video/mp4" && msg.isGif);
|
|
||||||
|
|
||||||
if (isSupported(media, gif) && session.medias.length < MAX_MEDIA) {
|
|
||||||
session.medias.push({ media, isGif: gif });
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── figurinha criar ──────────────────────────────────────
|
|
||||||
const sub = msg.args[1];
|
|
||||||
|
|
||||||
if (sub === "criar") {
|
|
||||||
const session = sessions.get(chatId);
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
await msg.reply(`❌ *Nenhuma sessão ativa.*\n\n${HELP}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!session.medias.length) {
|
|
||||||
await msg.reply(`📭 *Você ainda não enviou nenhuma mídia!*\n\n${HELP}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(session.timeout);
|
|
||||||
await msg.reply("⏳ Gerando suas figurinhas, aguarde um momento...");
|
|
||||||
|
|
||||||
for (const { media, isGif } of session.medias) {
|
|
||||||
await processarUmaMedia(media, isGif, api, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply("✅ *Figurinhas criadas com sucesso!*\nSalve as que quiser no seu WhatsApp. 😄");
|
|
||||||
sessions.delete(chatId);
|
|
||||||
emptyFolder(DOWNLOADS_DIR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── figurinha com mídia direta ───────────────────────────
|
|
||||||
const mediasParaCriar = [];
|
|
||||||
|
|
||||||
// Mídia anexa à própria mensagem
|
|
||||||
if (msg.hasMedia) {
|
|
||||||
const media = await msg.downloadMedia();
|
|
||||||
if (media) {
|
|
||||||
const gif = media.mimetype === "image/gif" ||
|
|
||||||
(media.mimetype === "video/mp4" && msg.isGif);
|
|
||||||
if (isSupported(media, gif)) mediasParaCriar.push({ media, isGif: gif });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mídia da mensagem citada
|
|
||||||
if (msg.hasReply) {
|
|
||||||
const quoted = await msg.getReply();
|
|
||||||
if (quoted?.hasMedia) {
|
|
||||||
const media = await quoted.downloadMedia();
|
|
||||||
if (media) {
|
|
||||||
const gif = media.mimetype === "image/gif" ||
|
|
||||||
(media.mimetype === "video/mp4" && quoted.isGif);
|
|
||||||
if (isSupported(media, gif)) mediasParaCriar.push({ media, isGif: gif });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tem mídia para criar direto
|
|
||||||
if (mediasParaCriar.length > 0) {
|
|
||||||
await msg.reply("⏳ Gerando figurinha, aguarde...");
|
|
||||||
for (const { media, isGif } of mediasParaCriar) {
|
|
||||||
await processarUmaMedia(media, isGif, api, msg);
|
|
||||||
}
|
|
||||||
emptyFolder(DOWNLOADS_DIR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── figurinha sem mídia → abre sessão ───────────────────
|
|
||||||
if (sessions.has(chatId)) {
|
|
||||||
await msg.reply(
|
|
||||||
"⚠️ Já existe uma sessão aberta.\n\n" +
|
|
||||||
`Envie as mídias e depois use \`${CMD_PREFIX}figurinha criar\`.\n` +
|
|
||||||
"Ou aguarde 2 minutos para a sessão expirar."
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const timeout = setTimeout(async () => {
|
|
||||||
sessions.delete(chatId);
|
|
||||||
try {
|
|
||||||
await msg.reply(
|
|
||||||
"⏰ *Sessão expirada!*\n\n" +
|
|
||||||
"Você demorou mais de 2 minutos para enviar as mídias.\n" +
|
|
||||||
`Digite \`${CMD_PREFIX}figurinha\` para começar de novo.`
|
|
||||||
);
|
|
||||||
} catch { }
|
|
||||||
}, SESSION_TIMEOUT);
|
|
||||||
|
|
||||||
sessions.set(chatId, { author: msg.sender, medias: [], timeout });
|
|
||||||
await msg.reply(`✅ Sessão iniciada por *${msg.senderName}*!\n\n${HELP}`);
|
|
||||||
}
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/forca/index.js
|
|
||||||
*
|
|
||||||
* Estado dos jogos de forca fica aqui dentro — isolado no plugin.
|
|
||||||
* Múltiplos grupos jogam simultaneamente sem conflito.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { CMD_PREFIX } from "../../config.js";
|
|
||||||
|
|
||||||
// Estados dos jogos
|
|
||||||
const jogosAtivos = new Map(); // chatId -> { palavra, tema, vidas, progresso }
|
|
||||||
const participantesAtivos = new Map(); // chatId -> Set de usuários que reagiram
|
|
||||||
export let forcaAtiva = false;
|
|
||||||
|
|
||||||
|
|
||||||
// Palavras de exemplo
|
|
||||||
const PALAVRAS = [
|
|
||||||
{ palavra: "python", tema: "Linguagem de programação" },
|
|
||||||
{ palavra: "javascript", tema: "Linguagem de programação" },
|
|
||||||
{ palavra: "java", tema: "Linguagem de programação" },
|
|
||||||
{ palavra: "cachorro", tema: "Animal" },
|
|
||||||
{ palavra: "gato", tema: "Animal" },
|
|
||||||
{ palavra: "elefante", tema: "Animal" },
|
|
||||||
{ palavra: "girafa", tema: "Animal" },
|
|
||||||
{ palavra: "guitarra", tema: "Instrumento musical" },
|
|
||||||
{ palavra: "piano", tema: "Instrumento musical" },
|
|
||||||
{ palavra: "bateria", tema: "Instrumento musical" },
|
|
||||||
{ palavra: "violino", tema: "Instrumento musical" },
|
|
||||||
{ palavra: "futebol", tema: "Esporte" },
|
|
||||||
{ palavra: "basquete", tema: "Esporte" },
|
|
||||||
{ palavra: "natação", tema: "Esporte" },
|
|
||||||
{ palavra: "tênis", tema: "Esporte" },
|
|
||||||
{ palavra: "brasil", tema: "País" },
|
|
||||||
{ palavra: "japão", tema: "País" },
|
|
||||||
{ palavra: "canadá", tema: "País" },
|
|
||||||
{ palavra: "frança", tema: "País" },
|
|
||||||
{ palavra: "marte", tema: "Planeta" },
|
|
||||||
{ palavra: "vênus", tema: "Planeta" },
|
|
||||||
{ palavra: "júpiter", tema: "Planeta" },
|
|
||||||
{ palavra: "saturno", tema: "Planeta" },
|
|
||||||
{ palavra: "minecraft", tema: "Jogo" },
|
|
||||||
{ palavra: "fortnite", tema: "Jogo" },
|
|
||||||
{ palavra: "roblox", tema: "Jogo" },
|
|
||||||
{ palavra: "amongus", tema: "Jogo" },
|
|
||||||
{ palavra: "rosa", tema: "Flor" },
|
|
||||||
{ palavra: "girassol", tema: "Flor" },
|
|
||||||
{ palavra: "tulipa", tema: "Flor" },
|
|
||||||
{ palavra: "orquídea", tema: "Flor" },
|
|
||||||
{ palavra: "tesoura", tema: "Objeto" },
|
|
||||||
{ palavra: "caderno", tema: "Objeto" },
|
|
||||||
{ palavra: "computador", tema: "Objeto" },
|
|
||||||
{ palavra: "telefone", tema: "Objeto" },
|
|
||||||
{ palavra: "lua", tema: "Corpo celeste" },
|
|
||||||
{ palavra: "sol", tema: "Corpo celeste" },
|
|
||||||
{ palavra: "estrela", tema: "Corpo celeste" },
|
|
||||||
{ palavra: "cometa", tema: "Corpo celeste" },
|
|
||||||
{ palavra: "oceano", tema: "Natureza" },
|
|
||||||
{ palavra: "montanha", tema: "Natureza" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Função para gerar a palavra com underscores
|
|
||||||
const gerarProgresso = palavra =>
|
|
||||||
palavra.replace(/[a-zA-Z]/g, "_");
|
|
||||||
|
|
||||||
export default async function ({ msg, api }) {
|
|
||||||
const chatId = api.chat.id;
|
|
||||||
const sub = msg.args[1];
|
|
||||||
|
|
||||||
// ── Comando principal do jogo
|
|
||||||
if (msg.is(CMD_PREFIX + "forca")) {
|
|
||||||
if (!sub) {
|
|
||||||
await api.send(
|
|
||||||
`🎮 *Jogo da Forca*\n\n` +
|
|
||||||
`\`${CMD_PREFIX}forca começar\` — inicia o jogo\n` +
|
|
||||||
`\`${CMD_PREFIX}forca parar\` — encerra o jogo`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sub === "começar") {
|
|
||||||
forcaAtiva = true;
|
|
||||||
// Pega uma palavra aleatória
|
|
||||||
const sorteio = PALAVRAS[Math.floor(Math.random() * PALAVRAS.length)];
|
|
||||||
|
|
||||||
// Inicializa o jogo
|
|
||||||
jogosAtivos.set(chatId, {
|
|
||||||
palavra: sorteio.palavra.toLowerCase(),
|
|
||||||
tema: sorteio.tema,
|
|
||||||
vidas: 6,
|
|
||||||
progresso: gerarProgresso(sorteio.palavra)
|
|
||||||
});
|
|
||||||
|
|
||||||
participantesAtivos.set(chatId, new Set()); // reset participantes
|
|
||||||
|
|
||||||
await api.send(
|
|
||||||
`🎮 *Jogo da Forca iniciado!*\n\n` +
|
|
||||||
`Tema: *${sorteio.tema}*\n` +
|
|
||||||
`Palavra: \`${gerarProgresso(sorteio.palavra)}\`\n` +
|
|
||||||
`Vidas: 6\n\n` +
|
|
||||||
`Digite uma letra para adivinhar!`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sub === "parar") {
|
|
||||||
jogosAtivos.delete(chatId);
|
|
||||||
participantesAtivos.delete(chatId);
|
|
||||||
await api.send("🛑 Jogo da Forca encerrado.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await api.send(
|
|
||||||
`❌ Subcomando *${sub}* não existe.\n` +
|
|
||||||
`Use ${CMD_PREFIX} + \`forca começar\` ou ${CMD_PREFIX} + \`forca parar\`.`
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Tentativas durante o jogo
|
|
||||||
const jogo = jogosAtivos.get(chatId);
|
|
||||||
if (!jogo) return; // Nenhum jogo ativo
|
|
||||||
|
|
||||||
const tentativa = msg.body.trim().toLowerCase();
|
|
||||||
if (!/^[a-z]$/.test(tentativa)) return; // apenas letras simples
|
|
||||||
|
|
||||||
// Se a letra está na palavra
|
|
||||||
let acerto = false;
|
|
||||||
let novoProgresso = jogo.progresso.split("");
|
|
||||||
for (let i = 0; i < jogo.palavra.length; i++) {
|
|
||||||
if (jogo.palavra[i] === tentativa) {
|
|
||||||
novoProgresso[i] = tentativa;
|
|
||||||
acerto = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
jogo.progresso = novoProgresso.join("");
|
|
||||||
|
|
||||||
if (!acerto) jogo.vidas--;
|
|
||||||
|
|
||||||
// Feedback para o grupo
|
|
||||||
if (jogo.progresso === jogo.palavra) {
|
|
||||||
await msg.reply(`🎉 Parabéns! Palavra completa: \`${jogo.palavra}\``);
|
|
||||||
jogosAtivos.delete(chatId);
|
|
||||||
participantesAtivos.delete(chatId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jogo.vidas <= 0) {
|
|
||||||
await msg.reply(`💀 Fim de jogo! Palavra era: \`${jogo.palavra}\``);
|
|
||||||
jogosAtivos.delete(chatId);
|
|
||||||
participantesAtivos.delete(chatId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply(
|
|
||||||
`Palavra: \`${jogo.progresso}\`\n` +
|
|
||||||
`Vidas: ${jogo.vidas}\n` +
|
|
||||||
(acerto ? "✅ Acertou a letra!" : "❌ Errou a letra!")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { CMD_PREFIX } from "../../config.js"
|
|
||||||
|
|
||||||
export default async function ({ msg, api }) {
|
|
||||||
if (!msg.is(CMD_PREFIX + "many")) return;
|
|
||||||
|
|
||||||
await api.send(
|
|
||||||
`🤖 *ManyBot — Comandos disponíveis:*\n\n` +
|
|
||||||
`🎬 \`${CMD_PREFIX}video <link>\` — baixa um vídeo\n` +
|
|
||||||
`🎵 \`${CMD_PREFIX}audio <link>\` — baixa um áudio\n` +
|
|
||||||
`🖼️ \`${CMD_PREFIX}figurinha\` — cria figurinhas\n` +
|
|
||||||
`🎮 \`${CMD_PREFIX}adivinhação começar|parar\` — jogo de adivinhar número\n` +
|
|
||||||
`🎮 \`${CMD_PREFIX}forca começar|parar\` — jogo da forca\n`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { CMD_PREFIX } from "../../config.js";
|
|
||||||
const gatilhos = ["obrigado", "valeu", "brigado"];
|
|
||||||
|
|
||||||
export default async function ({ msg }) {
|
|
||||||
if (!gatilhos.some(g => msg.is(CMD_PREFIX + g))) return;
|
|
||||||
|
|
||||||
await msg.reply("😊 Por nada!");
|
|
||||||
}
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/**
|
|
||||||
* plugins/video/index.js
|
|
||||||
*
|
|
||||||
* Baixa vídeo via yt-dlp e envia no chat.
|
|
||||||
* Todo o processo (download + envio + limpeza) fica aqui.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { spawn } from "child_process";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import { enqueue } from "../../download/queue.js";
|
|
||||||
import { emptyFolder } from "../../utils/file.js";
|
|
||||||
import { CMD_PREFIX } from "../../config.js";
|
|
||||||
|
|
||||||
const logStream = fs.createWriteStream("logs/video-error.log", { flags: "a" });
|
|
||||||
|
|
||||||
const DOWNLOADS_DIR = path.resolve("downloads");
|
|
||||||
const YT_DLP = os.platform() === "win32" ? ".\\bin\\yt-dlp.exe" : "./bin/yt-dlp";
|
|
||||||
|
|
||||||
const ARGS_BASE = [
|
|
||||||
"--extractor-args", "youtube:player_client=android",
|
|
||||||
"--print", "after_move:filepath",
|
|
||||||
"--cookies", "cookies.txt",
|
|
||||||
"--add-header", "User-Agent:Mozilla/5.0",
|
|
||||||
"--add-header", "Referer:https://www.youtube.com/",
|
|
||||||
"--retries", "4",
|
|
||||||
"--fragment-retries", "5",
|
|
||||||
"--socket-timeout", "15",
|
|
||||||
"--sleep-interval", "1",
|
|
||||||
"--max-sleep-interval", "4",
|
|
||||||
"--no-playlist",
|
|
||||||
"-f", "bv+ba/best",
|
|
||||||
];
|
|
||||||
|
|
||||||
function downloadVideo(url, id) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
|
|
||||||
|
|
||||||
const output = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`);
|
|
||||||
const proc = spawn(YT_DLP, [...ARGS_BASE, "--output", output, url]);
|
|
||||||
let stdout = "";
|
|
||||||
|
|
||||||
proc.on("error", err => reject(new Error(
|
|
||||||
err.code === "EACCES"
|
|
||||||
? "Sem permissão para executar o yt-dlp. Rode: chmod +x ./bin/yt-dlp"
|
|
||||||
: err.code === "ENOENT"
|
|
||||||
? "yt-dlp não encontrado em ./bin/yt-dlp"
|
|
||||||
: `Erro ao iniciar o yt-dlp: ${err.message}`
|
|
||||||
)));
|
|
||||||
|
|
||||||
proc.stdout.on("data", d => { stdout += d.toString(); });
|
|
||||||
proc.stderr.on("data", d => logStream.write(d));
|
|
||||||
|
|
||||||
proc.on("close", code => {
|
|
||||||
if (code !== 0) return reject(new Error(
|
|
||||||
"Não foi possível baixar o vídeo. Verifique se o link é válido e tente novamente."
|
|
||||||
));
|
|
||||||
|
|
||||||
const filePath = stdout.trim().split("\n").filter(Boolean).at(-1);
|
|
||||||
if (!filePath || !fs.existsSync(filePath)) return reject(new Error(
|
|
||||||
"Download concluído mas arquivo não encontrado. Tente novamente."
|
|
||||||
));
|
|
||||||
|
|
||||||
resolve(filePath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function ({ msg, api }) {
|
|
||||||
if (!msg.is(CMD_PREFIX + "video")) return;
|
|
||||||
|
|
||||||
const url = msg.args[1];
|
|
||||||
|
|
||||||
if (!url) {
|
|
||||||
await msg.reply(`❌ Você precisa informar um link.\n\nExemplo: \`${CMD_PREFIX}video https://youtube.com/...\``);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply("⏳ Baixando o vídeo, aguarde...");
|
|
||||||
|
|
||||||
enqueue(
|
|
||||||
async () => {
|
|
||||||
const filePath = await downloadVideo(url, `video-${Date.now()}`);
|
|
||||||
await api.sendVideo(filePath);
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
emptyFolder(DOWNLOADS_DIR);
|
|
||||||
api.log.info(`${CMD_PREFIX}video concluído → ${url}`);
|
|
||||||
},
|
|
||||||
async () => {
|
|
||||||
await msg.reply(
|
|
||||||
"❌ Não consegui baixar o vídeo.\n\n" +
|
|
||||||
"Verifique se o link é válido e tente novamente.\n" +
|
|
||||||
"Se o problema persistir, o conteúdo pode estar indisponível ou protegido."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BOT_PREFIX } from "../config.js";
|
import { BOT_PREFIX } from "../config.js";
|
||||||
|
|
||||||
export function botMsg(texto) {
|
export function botMsg(text) {
|
||||||
return `${BOT_PREFIX}\n${texto}`;
|
return `${BOT_PREFIX}\n${text}`;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* Utilitário CLI para descobrir IDs de chats/grupos.
|
* CLI utility to discover chat/group IDs.
|
||||||
* Uso: node src/utils/get_id.js grupos|contatos|<nome>
|
* Usage: node src/utils/get_id.js groups|contacts|<name>
|
||||||
*/
|
*/
|
||||||
import pkg from "whatsapp-web.js";
|
import pkg from "whatsapp-web.js";
|
||||||
import qrcode from "qrcode-terminal";
|
import qrcode from "qrcode-terminal";
|
||||||
@@ -12,7 +12,7 @@ const { Client, LocalAuth } = pkg;
|
|||||||
const arg = process.argv[2];
|
const arg = process.argv[2];
|
||||||
|
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
console.log("Uso: node get_id.js grupos|contatos|<nome>");
|
console.log("Usage: node get_id.js groups|contacts|<name>");
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,29 +30,29 @@ const client = new Client({
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.on("qr", (qr) => {
|
client.on("qr", (qr) => {
|
||||||
console.log("[QR] Escaneie para autenticar:");
|
console.log("[QR] Scan to authenticate:");
|
||||||
qrcode.generate(qr, { small: true });
|
qrcode.generate(qr, { small: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
client.on("ready", async () => {
|
client.on("ready", async () => {
|
||||||
console.log("[OK] Conectado. Buscando chats...\n");
|
console.log("[OK] Connected. Searching chats...\n");
|
||||||
|
|
||||||
const chats = await client.getChats();
|
const chats = await client.getChats();
|
||||||
const search = arg.toLowerCase();
|
const search = arg.toLowerCase();
|
||||||
|
|
||||||
const filtered =
|
const filtered =
|
||||||
search === "grupos" ? chats.filter(c => c.isGroup) :
|
search === "groups" ? chats.filter(c => c.isGroup) :
|
||||||
search === "contatos" ? chats.filter(c => !c.isGroup) :
|
search === "contacts" ? chats.filter(c => !c.isGroup) :
|
||||||
chats.filter(c => (c.name || c.id.user).toLowerCase().includes(search));
|
chats.filter(c => (c.name || c.id.user).toLowerCase().includes(search));
|
||||||
|
|
||||||
if (!filtered.length) {
|
if (!filtered.length) {
|
||||||
console.log("Nenhum resultado encontrado.");
|
console.log("No results found.");
|
||||||
} else {
|
} else {
|
||||||
filtered.forEach(c => {
|
filtered.forEach(c => {
|
||||||
console.log("─".repeat(40));
|
console.log("─".repeat(40));
|
||||||
console.log("Nome: ", c.name || c.id.user);
|
console.log("Name: ", c.name || c.id.user);
|
||||||
console.log("ID: ", c.id._serialized);
|
console.log("ID: ", c.id._serialized);
|
||||||
console.log("Grupo: ", c.isGroup);
|
console.log("Group: ", c.isGroup);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
129
src/utils/pluginI18n.js
Normal file
129
src/utils/pluginI18n.js
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/**
|
||||||
|
* src/utils/pluginI18n.js
|
||||||
|
*
|
||||||
|
* Independent i18n system for plugins.
|
||||||
|
* Plugins load their own translations from locale/ folder.
|
||||||
|
* Completely separate from bot core i18n.
|
||||||
|
*
|
||||||
|
* Usage in plugin:
|
||||||
|
* import { createPluginI18n } from "../utils/pluginI18n.js";
|
||||||
|
* const { t } = createPluginI18n(import.meta.url);
|
||||||
|
*
|
||||||
|
* Folder structure:
|
||||||
|
* myPlugin/
|
||||||
|
* index.js
|
||||||
|
* locale/
|
||||||
|
* en.json (required - fallback)
|
||||||
|
* pt.json
|
||||||
|
* es.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
import { LANGUAGE } from "../config.js";
|
||||||
|
|
||||||
|
// Default/fallback language
|
||||||
|
const DEFAULT_LANG = "en";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a nested value from an object using dot path
|
||||||
|
* @param {object} obj
|
||||||
|
* @param {string} key - path like "error.notFound"
|
||||||
|
* @returns {string|undefined}
|
||||||
|
*/
|
||||||
|
function getNestedValue(obj, key) {
|
||||||
|
const parts = key.split(".");
|
||||||
|
let current = obj;
|
||||||
|
|
||||||
|
for (const part of parts) {
|
||||||
|
if (current === null || current === undefined || typeof current !== "object") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
current = current[part];
|
||||||
|
}
|
||||||
|
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces placeholders {{key}} with values from context
|
||||||
|
* @param {string} str
|
||||||
|
* @param {object} context
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function interpolate(str, context = {}) {
|
||||||
|
return str.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
||||||
|
return context[key] !== undefined ? String(context[key]) : match;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load translations for a plugin
|
||||||
|
* @param {string} localeDir - path to plugin's locale folder
|
||||||
|
* @param {string} lang - target language
|
||||||
|
* @returns {{ translations: object, fallback: object }}
|
||||||
|
*/
|
||||||
|
function loadTranslations(localeDir, lang) {
|
||||||
|
let translations = {};
|
||||||
|
let fallback = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const targetPath = path.join(localeDir, `${lang}.json`);
|
||||||
|
if (fs.existsSync(targetPath)) {
|
||||||
|
translations = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const fallbackPath = path.join(localeDir, `${DEFAULT_LANG}.json`);
|
||||||
|
if (fs.existsSync(fallbackPath)) {
|
||||||
|
fallback = JSON.parse(fs.readFileSync(fallbackPath, "utf8"));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Silent fail - plugin may not have translations
|
||||||
|
}
|
||||||
|
|
||||||
|
return { translations, fallback };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an isolated translation function for a plugin.
|
||||||
|
* Language priority: PLUGIN_LANG env var > manybot.conf LANGUAGE > en
|
||||||
|
*
|
||||||
|
* @param {string} pluginMetaUrl - import.meta.url from the plugin
|
||||||
|
* @returns {{ t: Function, lang: string }}
|
||||||
|
*/
|
||||||
|
export function createPluginI18n(pluginMetaUrl) {
|
||||||
|
const pluginDir = path.dirname(fileURLToPath(pluginMetaUrl));
|
||||||
|
const localeDir = path.join(pluginDir, "locale");
|
||||||
|
|
||||||
|
const targetLang =
|
||||||
|
process.env.PLUGIN_LANG?.trim().toLowerCase() ||
|
||||||
|
LANGUAGE?.trim().toLowerCase() ||
|
||||||
|
DEFAULT_LANG;
|
||||||
|
|
||||||
|
const { translations, fallback } = loadTranslations(localeDir, targetLang);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Translation function
|
||||||
|
* @param {string} key - translation key (e.g., "error.notFound")
|
||||||
|
* @param {object} context - values to interpolate {{key}}
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function t(key, context = {}) {
|
||||||
|
let value = getNestedValue(translations, key);
|
||||||
|
|
||||||
|
if (value === undefined) {
|
||||||
|
value = getNestedValue(fallback, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === undefined) return key;
|
||||||
|
|
||||||
|
if (typeof value !== "string") return String(value);
|
||||||
|
|
||||||
|
return interpolate(value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { t, lang: targetLang };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { createPluginI18n };
|
||||||
68
update
68
update
@@ -11,7 +11,7 @@ set -euo pipefail
|
|||||||
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
tmp_dir="$dir/tmp"
|
tmp_dir="$dir/tmp"
|
||||||
log_file="$dir/update.log"
|
log_file="$dir/update.log"
|
||||||
config_items=(".wwebjs_auth" ".wwebjs_cache" "node_modules")
|
config_items=(".wwebjs_auth" ".wwebjs_cache")
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Logging
|
# Logging
|
||||||
@@ -32,7 +32,7 @@ cleanup_on_error() {
|
|||||||
log "ERROR" "Falha durante a atualização (código: $exit_code)."
|
log "ERROR" "Falha durante a atualização (código: $exit_code)."
|
||||||
if [ -d "$tmp_dir" ] && [ "$(ls -A "$tmp_dir" 2>/dev/null)" ]; then
|
if [ -d "$tmp_dir" ] && [ "$(ls -A "$tmp_dir" 2>/dev/null)" ]; then
|
||||||
log "WARN" "Arquivos de configuração preservados em: $tmp_dir"
|
log "WARN" "Arquivos de configuração preservados em: $tmp_dir"
|
||||||
log "WARN" "Restaure manualmente com: mv $tmp_dir/* $dir/"
|
log "WARN" "Restaure manualmente com: mv \"$tmp_dir\"/* \"$dir\"/"
|
||||||
fi
|
fi
|
||||||
exit $exit_code
|
exit $exit_code
|
||||||
}
|
}
|
||||||
@@ -109,69 +109,11 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Instalação de dependências
|
# Instalação de dependências Node
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
log "INFO" "Instalando dependências..."
|
log "INFO" "Instalando dependências Node..."
|
||||||
npm ci --omit=dev 2>&1 | tee -a "$log_file"
|
npm ci --omit=dev 2>&1 | tee -a "$log_file"
|
||||||
|
|
||||||
# ------------------------
|
|
||||||
# Download
|
|
||||||
# ------------------------
|
|
||||||
download_file() {
|
|
||||||
local url="$1"
|
|
||||||
local dest="$2"
|
|
||||||
|
|
||||||
log "download_file(url=$url, dest=$dest)"
|
|
||||||
|
|
||||||
log "Baixando $url"
|
|
||||||
log "Destino: $dest"
|
|
||||||
|
|
||||||
if command -v curl >/dev/null 2>&1; then
|
|
||||||
log "Downloader: curl"
|
|
||||||
curl -L "$url" -o "$dest"
|
|
||||||
elif command -v wget >/dev/null 2>&1; then
|
|
||||||
log "Downloader: wget"
|
|
||||||
wget "$url" -O "$dest"
|
|
||||||
else
|
|
||||||
log "curl ou wget não encontrados"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
chmod +x "$dest" 2>/dev/null || true
|
|
||||||
log "Arquivo pronto: $dest"
|
|
||||||
}
|
|
||||||
|
|
||||||
# ------------------------
|
|
||||||
# Arquivos por plataforma
|
|
||||||
# ------------------------
|
|
||||||
log "Selecionando dependências binárias"
|
|
||||||
|
|
||||||
files=()
|
|
||||||
if [[ "$PLATFORM" == "win" ]]; then
|
|
||||||
log "Usando binários Windows"
|
|
||||||
files=(
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/yt-dlp.exe bin/yt-dlp.exe"
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/ffmpeg.exe bin/ffmpeg.exe"
|
|
||||||
)
|
|
||||||
else
|
|
||||||
log "Usando binários Unix"
|
|
||||||
files=(
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/yt-dlp bin/yt-dlp"
|
|
||||||
"https://github.com/synt-xerror/manybot/releases/download/dependencies/ffmpeg bin/ffmpeg"
|
|
||||||
)
|
|
||||||
log "Total de arquivos para baixar: ${#files[@]}"
|
|
||||||
|
|
||||||
# ------------------------
|
|
||||||
# Download
|
|
||||||
# ------------------------
|
|
||||||
for file in "${files[@]}"; do
|
|
||||||
url="${file%% *}"
|
|
||||||
dest="${file##* }"
|
|
||||||
|
|
||||||
log "Processando dependência"
|
|
||||||
download_file "$url" "$dest"
|
|
||||||
done
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Restauração dos arquivos de configuração
|
# Restauração dos arquivos de configuração
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
@@ -183,7 +125,7 @@ if [ ${#backed_up[@]} -gt 0 ]; then
|
|||||||
# Remove o que npm possa ter criado (ex: node_modules)
|
# Remove o que npm possa ter criado (ex: node_modules)
|
||||||
rm -rf "$dst"
|
rm -rf "$dst"
|
||||||
mv "$src" "$dst"
|
mv "$src" "$dst"
|
||||||
l"INFO" "Restaurado: $item"
|
log "INFO" "Restaurado: $item"
|
||||||
else
|
else
|
||||||
log "WARN" "Item esperado no backup não encontrado: $item"
|
log "WARN" "Item esperado no backup não encontrado: $item"
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user