Compare commits
32 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 | ||
|
|
a6fda095d8 | ||
|
|
372f644995 | ||
|
|
c75b6249c1 | ||
|
|
f9911f6cf3 | ||
|
|
92e2ea2337 | ||
|
|
438e674eff | ||
|
|
5b74cf2dc5 | ||
|
|
4f5d937265 | ||
|
|
544dc770cd | ||
|
|
5fbe257625 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -1,9 +1,17 @@
|
|||||||
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/
|
||||||
cookies.txt
|
cookies.txt
|
||||||
bin/
|
bin/
|
||||||
mychats.txt
|
mychats.txt
|
||||||
|
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"]
|
||||||
197
README.md
197
README.md
@@ -1,15 +1,194 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Criei esse bot para servir um grupo de amigos. Meu foco não é fazer ele funcionar para todo mundo.
|
<p>
|
||||||
|
<strong>Bot para WhatsApp 100% local, sem API oficial</strong>
|
||||||
|
</p>
|
||||||
|
|
||||||
Ele é 100% local e gratuito, sem necessidade de APIs burocraticas. Usufrui da biblioteca `whatsapp-web.js`, que permite bastante coisa mesmo sem a API oficial.
|
<p>
|
||||||
|
<a href="#-recursos">Recursos</a> .
|
||||||
|
<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>
|
||||||
|
|
||||||
Você consegue totalmente clonar esse repoistório e rodar seu próprio ManyBot. A licenca GPLv3 permite que você modifique o que quiser e faça seu próprio bot, mas se for publicar, deve ser open source assim como o ManyBot original.
|
<p>
|
||||||
|
🇧🇷 Português · <a href="README_EN.md">🇺🇸 English</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
Algumas funcionalidades desse bot inclui:
|
<p>
|
||||||
- Funciona em multiplos chats em apenas uma única sessão
|
<img src="https://img.shields.io/badge/Node.js-18+-339933?logo=node.js&logoColor=white" alt="Node.js 18+">
|
||||||
- Comandos de jogos e download com yt-dlp
|
<img src="https://img.shields.io/badge/npm-9+-CB3837?logo=npm&logoColor=white" alt="npm 9+">
|
||||||
- Gerador de figurinhas
|
<img src="https://img.shields.io/badge/License-GPL--v3-blue.svg" alt="License: GPL v3">
|
||||||
- Ferramenta para pegar IDs dos chats
|
<img src="https://img.shields.io/badge/Platform-Linux%20%7C%20Windows-lightgrey" alt="Platform">
|
||||||
- Entre outros
|
</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)
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recursos
|
||||||
|
|
||||||
|
- **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 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
|
||||||
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
# 3. Configure conforme sua necessidade (veja a documentação)
|
||||||
|
nano manybot.conf
|
||||||
|
|
||||||
|
# 4. Execute o script de instalação
|
||||||
|
bash ./setup
|
||||||
|
|
||||||
|
# 5. Rode o bot
|
||||||
|
node ./src/main.js
|
||||||
|
```
|
||||||
|
|
||||||
|
📱 **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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Iniciar o bot
|
||||||
|
node ./src/main.js
|
||||||
|
|
||||||
|
# Atualizar para a versão mais recente
|
||||||
|
bash ./update
|
||||||
|
|
||||||
|
# Descobrir IDs de chats
|
||||||
|
node src/utils/get_id.js
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔌 Plugins
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Gerenciando Plugins com ManyPlug
|
||||||
|
|
||||||
|
Instale e gerencie plugins usando o **ManyPlug CLI**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Criar um Plugin
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// plugins/meu-plugin/index.js
|
||||||
|
import { CMD_PREFIX } from "../../config.js";
|
||||||
|
|
||||||
|
export default async function ({ msg, api }) {
|
||||||
|
if (!msg.is(CMD_PREFIX + "oi")) return;
|
||||||
|
await msg.reply("Olá! 👋");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Veja mais na [documentação de plugins](docs/PLUGINS.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Documentação
|
||||||
|
|
||||||
|
- [📥 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`
|
||||||
|
|
||||||
|
## 🌍 Internacionalização
|
||||||
|
|
||||||
|
O ManyBot suporta múltiplos idiomas. Configure no `manybot.conf`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
LANGUAGE=pt # Português
|
||||||
|
LANGUAGE=en # English
|
||||||
|
LANGUAGE=es # Español
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Padrão:** Inglês (`en`)
|
||||||
|
- **Fallback:** Se o idioma selecionado não existir, o bot usa inglês
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Requisitos
|
||||||
|
|
||||||
|
- **Node.js** 18+
|
||||||
|
- **NPM** 9+
|
||||||
|
- **Linux** ou **Windows** (via Git Bash)
|
||||||
|
|
||||||
|
> ⚠️ Android/iOS e Termux têm suporte experimental sem garantias.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 Licença
|
||||||
|
|
||||||
|
Distribuído sob a licença **GPLv3**. Veja [LICENSE](LICENSE) para mais detalhes.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**[⬆ Voltar ao topo](#)**
|
||||||
|
|
||||||
|
</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.
|
||||||
368
deploy.sh
368
deploy.sh
@@ -1,44 +1,348 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
# development tool
|
# =============================================================================
|
||||||
# ./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
|
||||||
|
|
||||||
echo "Rewriting config.js"
|
success "Branch: ${BOLD}$BRANCH${RESET}"
|
||||||
cat > "src/config.js" << 'EOF'
|
|
||||||
export const CLIENT_ID = "bot_permanente";
|
|
||||||
export const BOT_PREFIX = "🤖 *ManyBot:* ";
|
|
||||||
export const CHATS = [
|
|
||||||
// coloque os chats que quer aqui
|
|
||||||
];
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# mudar para a branch
|
# ──────────────────────────────────────────
|
||||||
git checkout $BRANCH || { echo "Error ao change to $BRANCH"; exit 1; }
|
# GATE 2 — Working tree limpa
|
||||||
|
# ──────────────────────────────────────────
|
||||||
|
function worktree() {
|
||||||
|
# Em modo release, working tree tem de estar absolutamente limpa
|
||||||
|
if [[ -n "$RELEASE_TYPE" ]]; then
|
||||||
|
warn "Arquivos não commitados:"
|
||||||
|
[[ -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
|
||||||
|
|
||||||
# se for master, atualizar versão
|
# ── Seletor interativo de arquivos ──────────────────────────────────────
|
||||||
if [ "$BRANCH" == "master" ] && [ -n "$VERSION" ]; then
|
# Monta lista indexada: staged (S), modificados (M), não rastreados (?)
|
||||||
echo "Updating version to $VERSION"
|
declare -a ALL_FILES
|
||||||
git tag $VERSION
|
declare -a ALL_LABELS
|
||||||
npm version $VERSION --no-git-tag-version
|
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${GREEN}staged${RESET}"); done <<< "$STAGED"
|
||||||
git add package.json
|
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${YELLOW}modificado${RESET}"); done <<< "$MODIFIED"
|
||||||
git commit -m "Bump version to $VERSION"
|
while IFS= read -r f; do [[ -n "$f" ]] && ALL_FILES+=("$f") && ALL_LABELS+=("${RED}novo${RESET}"); done <<< "$UNTRACKED"
|
||||||
git push origin $VERSION
|
|
||||||
|
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/)
|
||||||
BIN
examples/figurinha.gif
Normal file
BIN
examples/figurinha.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 620 KiB |
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
|
||||||
|
]
|
||||||
668
package-lock.json
generated
668
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "whatsapp-bot",
|
"name": "manybot",
|
||||||
"version": "2.3.1",
|
"version": "2.4.3",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "whatsapp-bot",
|
"name": "manybot",
|
||||||
"version": "2.3.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.3.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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
61
setup
61
setup
@@ -1,6 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
|
# Salvando diretório para evitar problemas
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
# ------------------------
|
# ------------------------
|
||||||
# Cores
|
# Cores
|
||||||
# ------------------------
|
# ------------------------
|
||||||
@@ -46,9 +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
|
||||||
A Amizade é Mágica!
|
|
||||||
|
|
||||||
EOF
|
EOF
|
||||||
echo -e "${RESET}"
|
echo -e "${RESET}"
|
||||||
@@ -162,43 +164,40 @@ export PUPPETEER_SKIP_DOWNLOAD=1
|
|||||||
run_cmd npm install
|
run_cmd npm install
|
||||||
|
|
||||||
# ------------------------
|
# ------------------------
|
||||||
# Diretórios
|
# Chrome Puppeeter
|
||||||
# ------------------------
|
# ------------------------
|
||||||
log_info "Preparando diretórios"
|
log_info "Instalando Chrome"
|
||||||
mkdir -p bin
|
|
||||||
log_debug "Diretório bin garantido"
|
npx puppeteer browsers install chrome
|
||||||
|
|
||||||
# ------------------------
|
# ------------------------
|
||||||
# Arquivos por plataforma
|
# ManyPlug CLI
|
||||||
# ------------------------
|
# ------------------------
|
||||||
log_info "Selecionando dependências binárias"
|
log_info "Instalando ManyPlug CLI"
|
||||||
|
|
||||||
files=()
|
if ! command -v manyplug &>/dev/null; then
|
||||||
if [[ "$PLATFORM" == "win" ]]; then
|
log_info "ManyPlug não encontrado, instalando globalmente..."
|
||||||
log_debug "Usando binários Windows"
|
npm install -g @freakk.dev/manyplug
|
||||||
files=(
|
log_ok "ManyPlug instalado com sucesso"
|
||||||
"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
|
||||||
|
|
||||||
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"
|
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,39 +1,47 @@
|
|||||||
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: { headless: true, ...resolvePuppeteerConfig() },
|
puppeteer: {
|
||||||
|
headless: true,
|
||||||
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-setuid-sandbox',
|
||||||
|
...(resolvePuppeteerConfig().args || [])
|
||||||
|
],
|
||||||
|
...resolvePuppeteerConfig()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Eventos ───────────────────────────────────────────────────
|
// ── Events ────────────────────────────────────────────────────
|
||||||
client.on("qr", handleQR);
|
client.on("qr", handleQR);
|
||||||
|
|
||||||
client.on("ready", () => {
|
client.on("ready", () => {
|
||||||
console.log("READY DISPAROU"); // temporário
|
|
||||||
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,5 +0,0 @@
|
|||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
|
|
||||||
export async function cmdA(msg, _chat, _chatId, args) {
|
|
||||||
if (!args[0]) await msg.reply(botMsg("B!"));
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { iniciarJogo, pararJogo } from "../logic/games/adivinhacao.js";
|
|
||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
import { logger } from "../../logger/logger.js";
|
|
||||||
|
|
||||||
const SUBCOMANDOS = new Map([
|
|
||||||
["começar", async (chat) => {
|
|
||||||
iniciarJogo(chat.id._serialized);
|
|
||||||
await chat.sendMessage(botMsg(
|
|
||||||
"🎮 *Jogo iniciado!*\n\n" +
|
|
||||||
"Estou pensando em um número de 1 a 100.\n" +
|
|
||||||
"Tente adivinhar! 🤔"
|
|
||||||
));
|
|
||||||
logger.done("!adivinhação", "jogo iniciado");
|
|
||||||
}],
|
|
||||||
["parar", async (chat) => {
|
|
||||||
pararJogo(chat.id._serialized);
|
|
||||||
await chat.sendMessage(botMsg("🛑 Jogo encerrado."));
|
|
||||||
logger.done("!adivinhação", "jogo parado");
|
|
||||||
}],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export async function cmdAdivinhacao(msg, chat, _chatId, args) {
|
|
||||||
if (!args[0]) {
|
|
||||||
await chat.sendMessage(botMsg(
|
|
||||||
"🎮 *Jogo de adivinhação:*\n\n" +
|
|
||||||
"`!adivinhação começar` — inicia o jogo\n" +
|
|
||||||
"`!adivinhação parar` — encerra o jogo"
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const subcomando = SUBCOMANDOS.get(args[0]);
|
|
||||||
|
|
||||||
if (!subcomando) {
|
|
||||||
await chat.sendMessage(botMsg(
|
|
||||||
`❌ Subcomando *${args[0]}* não existe.\n\n` +
|
|
||||||
"Use `!adivinhação começar` ou `!adivinhação parar`."
|
|
||||||
));
|
|
||||||
logger.warn(`!adivinhação — subcomando desconhecido: ${args[0]}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await subcomando(chat);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { enqueueDownload } from "../../download/queue.js";
|
|
||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
import { logger } from "../../logger/logger.js";
|
|
||||||
|
|
||||||
export async function cmdAudio(msg, chat, chatId, args) {
|
|
||||||
if (!args[0]) {
|
|
||||||
await msg.reply(botMsg("❌ Você precisa informar um link.\n\nExemplo: `!audio https://youtube.com/...`"));
|
|
||||||
logger.warn("!audio sem link");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply(botMsg("⏳ Baixando o áudio, aguarde..."));
|
|
||||||
enqueueDownload("audio", args[0], msg, chatId);
|
|
||||||
logger.done("!audio", `enfileirado → ${args[0]}`);
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import { iniciarSessao, gerarSticker, help } from "../logic/figurinha.js";
|
|
||||||
import { stickerSessions } from "../logic/stickerSessions.js";
|
|
||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
|
|
||||||
export async function cmdFigurinha(msg, chat, _chatId, args) {
|
|
||||||
const author = msg.author || msg.from;
|
|
||||||
const name = msg._data?.notifyName || author.replace(/(:\d+)?@.*$/, "");
|
|
||||||
const groupId = chat.id._serialized;
|
|
||||||
|
|
||||||
if (args[0] === "criar") {
|
|
||||||
await gerarSticker(msg, groupId);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stickerSessions.has(groupId)) {
|
|
||||||
await msg.reply(botMsg(
|
|
||||||
"⚠️ Já existe uma sessão aberta.\n\n" +
|
|
||||||
"Envie as mídias e depois use `!figurinha criar`.\n" +
|
|
||||||
"Ou aguarde 2 minutos para a sessão expirar."
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
iniciarSessao(groupId, author, msg);
|
|
||||||
await msg.reply(botMsg(`✅ Sessão iniciada por *${name}*!\n\n${help}`));
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
|
|
||||||
const HELP = new Map([
|
|
||||||
["ping", "> `!ping`\nResponde pong."],
|
|
||||||
["video", "> `!video <link>`\nBaixa vídeo da internet."],
|
|
||||||
["audio", "> `!audio <link>`\nBaixa áudio da internet."],
|
|
||||||
["figurinha", "> `!figurinha`\nTransforma imagem/GIF em sticker."],
|
|
||||||
]);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Envia a descrição de um comando específico.
|
|
||||||
* @param {string} cmd — nome do comando (sem prefixo)
|
|
||||||
* @param {object} chat
|
|
||||||
*/
|
|
||||||
export async function processarInfo(cmd, chat) {
|
|
||||||
const texto = HELP.get(cmd);
|
|
||||||
|
|
||||||
if (!texto) {
|
|
||||||
await chat.sendMessage(botMsg(`❌ Comando '${cmd}' não encontrado.`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await chat.sendMessage(botMsg(texto));
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
|
|
||||||
export async function cmdMany(msg, chat) {
|
|
||||||
await chat.sendMessage(botMsg(
|
|
||||||
"*Comandos disponíveis:*\n\n" +
|
|
||||||
"🎬 `!video <link>` — baixa um vídeo\n" +
|
|
||||||
"🎵 `!audio <link>` — baixa um áudio\n" +
|
|
||||||
"🖼️ `!figurinha` — cria figurinhas\n" +
|
|
||||||
"🎮 `!adivinhação começar|parar` — jogo de adivinhar número\n"
|
|
||||||
));
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
|
|
||||||
export async function cmdObrigado(msg) {
|
|
||||||
await msg.reply(botMsg("😊 Por nada!"));
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { enqueueDownload } from "../../download/queue.js";
|
|
||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
import { logger } from "../../logger/logger.js";
|
|
||||||
|
|
||||||
export async function cmdVideo(msg, chat, chatId, args) {
|
|
||||||
if (!args[0]) {
|
|
||||||
await msg.reply(botMsg("❌ Você precisa informar um link.\n\nExemplo: `!video https://youtube.com/...`"));
|
|
||||||
logger.warn("!video sem link");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply(botMsg("⏳ Baixando o vídeo, aguarde..."));
|
|
||||||
enqueueDownload("video", args[0], msg, chatId);
|
|
||||||
logger.done("!video", `enfileirado → ${args[0]}`);
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { parseCommand } from "./parser.js";
|
|
||||||
import { commandRegistry } from "./registry.js";
|
|
||||||
import { logger } from "../logger/logger.js";
|
|
||||||
import { botMsg } from "../utils/botMsg.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Roteia a mensagem para o handler correto.
|
|
||||||
* Não conhece nenhum comando — apenas delega.
|
|
||||||
*/
|
|
||||||
export async function processarComando(msg, chat, chatId) {
|
|
||||||
const { cmd, args, valid } = parseCommand(msg.body);
|
|
||||||
|
|
||||||
if (!valid) return;
|
|
||||||
|
|
||||||
const handler = commandRegistry.get(cmd);
|
|
||||||
|
|
||||||
if (!handler) {
|
|
||||||
logger.warn(`Comando desconhecido: ${cmd}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.cmd(cmd);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await handler(msg, chat, chatId, args);
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Falha em ${cmd} — ${err.message}`);
|
|
||||||
await chat.sendMessage(botMsg(
|
|
||||||
"❌ Algo deu errado ao executar esse comando.\n" +
|
|
||||||
"Tente novamente em instantes."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,192 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import os from "os";
|
|
||||||
import { execFile } from "child_process";
|
|
||||||
import { promisify } from "util";
|
|
||||||
|
|
||||||
import pkg from "whatsapp-web.js";
|
|
||||||
import { createSticker } from "wa-sticker-formatter";
|
|
||||||
|
|
||||||
import { client } from "../../client/whatsappClient.js";
|
|
||||||
import { botMsg } from "../../utils/botMsg.js";
|
|
||||||
import { emptyFolder } from "../../utils/file.js";
|
|
||||||
import { stickerSessions } from "./stickerSessions.js"; // ← sem circular
|
|
||||||
import { logger } from "../../logger/logger.js";
|
|
||||||
|
|
||||||
const { MessageMedia } = pkg;
|
|
||||||
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 = 10;
|
|
||||||
|
|
||||||
// ── Helpers ───────────────────────────────────────────────────
|
|
||||||
function ensureDownloadsDir() {
|
|
||||||
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanupFiles(...files) {
|
|
||||||
for (const f of files) {
|
|
||||||
try { if (f && fs.existsSync(f)) fs.unlinkSync(f); } catch { /* ignora */ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function convertVideoToGif(inputPath, outputPath, fps = 12) {
|
|
||||||
const clampedFps = Math.min(fps, 12);
|
|
||||||
const filter = [
|
|
||||||
`fps=${clampedFps},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", inputPath, "-filter_complex", filter, "-loop", "0", "-y", outputPath]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function resizeToSticker(inputPath, outputPath) {
|
|
||||||
await execFileAsync(FFMPEG, ["-i", inputPath, "-vf", "scale=512:512:flags=lanczos", "-y", outputPath]);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createStickerWithFallback(stickerInputPath, isAnimated) {
|
|
||||||
for (const quality of [80, 60, 40, 20]) {
|
|
||||||
const buffer = await createSticker(fs.readFileSync(stickerInputPath), {
|
|
||||||
pack: "Criada por ManyBot\n",
|
|
||||||
author: "\ngithub.com/synt-xerror/manybot",
|
|
||||||
type: isAnimated ? "FULL" : "STATIC",
|
|
||||||
categories: ["🤖"],
|
|
||||||
quality,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buffer.length <= MAX_STICKER_SIZE) return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error("Não foi possível reduzir o sticker para menos de 900 KB.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Textos ────────────────────────────────────────────────────
|
|
||||||
export const help =
|
|
||||||
"📌 *Como criar figurinhas:*\n\n" +
|
|
||||||
"1️⃣ Digite `!figurinha` para iniciar\n" +
|
|
||||||
"2️⃣ Envie as imagens, GIFs ou vídeos que quer transformar\n" +
|
|
||||||
"3️⃣ Digite `!figurinha criar` para gerar as figurinhas\n\n" +
|
|
||||||
"⏳ A sessão expira em 2 minutos se nenhuma mídia for enviada.";
|
|
||||||
|
|
||||||
// ── Sessão ────────────────────────────────────────────────────
|
|
||||||
export function iniciarSessao(chatId, author, msg) {
|
|
||||||
if (stickerSessions.has(chatId)) return false;
|
|
||||||
|
|
||||||
const timeout = setTimeout(async () => {
|
|
||||||
stickerSessions.delete(chatId);
|
|
||||||
try {
|
|
||||||
await msg.reply(botMsg(
|
|
||||||
"⏰ *Sessão expirada!*\n\n" +
|
|
||||||
"Você demorou mais de 2 minutos para enviar as mídias.\n" +
|
|
||||||
"Digite `!figurinha` para começar de novo."
|
|
||||||
));
|
|
||||||
} catch (err) {
|
|
||||||
logger.warn(`Erro ao notificar expiração da sessão: ${err.message}`);
|
|
||||||
}
|
|
||||||
}, SESSION_TIMEOUT);
|
|
||||||
|
|
||||||
stickerSessions.set(chatId, { author, medias: [], timeout });
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Coleta de mídia ───────────────────────────────────────────
|
|
||||||
export async function coletarMidia(msg) {
|
|
||||||
const chat = await msg.getChat();
|
|
||||||
const chatId = chat.id._serialized;
|
|
||||||
const session = stickerSessions.get(chatId);
|
|
||||||
|
|
||||||
if (!session) return;
|
|
||||||
|
|
||||||
const sender = msg.author || msg.from;
|
|
||||||
if (sender !== session.author) return;
|
|
||||||
if (!msg.hasMedia) return;
|
|
||||||
|
|
||||||
const media = await msg.downloadMedia();
|
|
||||||
if (!media) return;
|
|
||||||
|
|
||||||
const isGif =
|
|
||||||
media.mimetype === "image/gif" ||
|
|
||||||
(media.mimetype === "video/mp4" && msg._data?.isGif);
|
|
||||||
|
|
||||||
const isSupported =
|
|
||||||
media.mimetype?.startsWith("image/") ||
|
|
||||||
media.mimetype?.startsWith("video/") ||
|
|
||||||
isGif;
|
|
||||||
|
|
||||||
if (!isSupported) return;
|
|
||||||
|
|
||||||
if (session.medias.length < MAX_MEDIA) {
|
|
||||||
session.medias.push(media);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Geração de stickers ───────────────────────────────────────
|
|
||||||
export async function gerarSticker(msg, chatId) {
|
|
||||||
const sender = msg.author || msg.from;
|
|
||||||
const session = stickerSessions.get(chatId);
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
return msg.reply(botMsg("❌ *Nenhuma sessão ativa.*\n\n" + help));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.author !== sender) {
|
|
||||||
return msg.reply(botMsg("🚫 Só quem digitou `!figurinha` pode usar `!figurinha criar`."));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session.medias.length) {
|
|
||||||
return msg.reply(botMsg("📭 *Você ainda não enviou nenhuma mídia!*\n\n" + help));
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(session.timeout);
|
|
||||||
await msg.reply(botMsg("⏳ Gerando suas figurinhas, aguarde um momento..."));
|
|
||||||
ensureDownloadsDir();
|
|
||||||
|
|
||||||
for (const media of session.medias) {
|
|
||||||
try {
|
|
||||||
const ext = media.mimetype.split("/")[1];
|
|
||||||
const isVideo = media.mimetype.startsWith("video/");
|
|
||||||
const isGif = media.mimetype === "image/gif";
|
|
||||||
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}`);
|
|
||||||
|
|
||||||
fs.writeFileSync(inputPath, Buffer.from(media.data, "base64"));
|
|
||||||
|
|
||||||
let stickerInputPath;
|
|
||||||
|
|
||||||
if (isAnimated) {
|
|
||||||
await convertVideoToGif(inputPath, gifPath, isVideo ? 12 : 24);
|
|
||||||
stickerInputPath = gifPath;
|
|
||||||
} else {
|
|
||||||
await resizeToSticker(inputPath, resizedPath);
|
|
||||||
stickerInputPath = resizedPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
const stickerBuffer = await createStickerWithFallback(stickerInputPath, isAnimated);
|
|
||||||
const stickerMedia = new MessageMedia("image/webp", stickerBuffer.toString("base64"));
|
|
||||||
|
|
||||||
await client.sendMessage(chatId, stickerMedia, { sendMediaAsSticker: true });
|
|
||||||
cleanupFiles(inputPath, gifPath, resizedPath);
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
logger.error(`Erro ao gerar sticker: ${err.message}`);
|
|
||||||
await msg.reply(botMsg(
|
|
||||||
"⚠️ Não consegui criar uma das figurinhas.\n" +
|
|
||||||
"Tente reenviar essa mídia ou use outro formato (JPG, PNG, GIF, MP4)."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await msg.reply(botMsg("✅ *Figurinhas criadas com sucesso!*\nSalve as que quiser no seu WhatsApp. 😄"));
|
|
||||||
|
|
||||||
stickerSessions.delete(chatId);
|
|
||||||
emptyFolder("downloads");
|
|
||||||
}
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import { botMsg } from "../../../utils/botMsg.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Estado dos jogos ativos, keyed por chatId.
|
|
||||||
* Permite múltiplos grupos jogando simultaneamente sem conflito.
|
|
||||||
* @type {Map<string, number>}
|
|
||||||
*/
|
|
||||||
const jogosAtivos = new Map();
|
|
||||||
|
|
||||||
const RANGE = { min: 1, max: 100 };
|
|
||||||
|
|
||||||
const sorteio = () =>
|
|
||||||
Math.floor(Math.random() * (RANGE.max - RANGE.min + 1)) + RANGE.min;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} chatId
|
|
||||||
*/
|
|
||||||
export function iniciarJogo(chatId) {
|
|
||||||
const numero = sorteio();
|
|
||||||
jogosAtivos.set(chatId, numero);
|
|
||||||
return numero;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {string} chatId
|
|
||||||
*/
|
|
||||||
export function pararJogo(chatId) {
|
|
||||||
jogosAtivos.delete(chatId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processa uma tentativa de adivinhação.
|
|
||||||
* @param {import("whatsapp-web.js").Message} msg
|
|
||||||
* @param {import("whatsapp-web.js").Chat} chat
|
|
||||||
*/
|
|
||||||
export async function processarJogo(msg, chat) {
|
|
||||||
const chatId = chat.id._serialized;
|
|
||||||
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(botMsg(`⚠️ Digite um número entre ${RANGE.min} e ${RANGE.max}.`));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (num === numero) {
|
|
||||||
await msg.reply(botMsg(
|
|
||||||
`🎉 *Acertou!* O número era ${numero}!\n\n` +
|
|
||||||
"Use \`!adivinhação começar\` para jogar de novo."
|
|
||||||
));
|
|
||||||
pararJogo(chatId);
|
|
||||||
} else {
|
|
||||||
await chat.sendMessage(botMsg(num > numero ? "📉 Tente um número *menor*!" : "📈 Tente um número *maior*!"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
/**
|
|
||||||
* Armazena sessões ativas de criação de figurinha.
|
|
||||||
* Módulo neutro — não importa nada do projeto, pode ser importado por qualquer um.
|
|
||||||
*
|
|
||||||
* @type {Map<string, { author: string, medias: object[], timeout: NodeJS.Timeout }>}
|
|
||||||
*/
|
|
||||||
export const stickerSessions = new Map();
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { CMD_PREFIX } from "../config.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef {Object} ParsedCommand
|
|
||||||
* @property {string} cmd — ex: "!video", "a"
|
|
||||||
* @property {string[]} args — tokens restantes
|
|
||||||
* @property {boolean} valid — false se não for um comando reconhecível
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extrai comando e argumentos de uma mensagem.
|
|
||||||
* Retorna `valid: false` para mensagens que não são comandos.
|
|
||||||
*
|
|
||||||
* @param {string} body
|
|
||||||
* @returns {ParsedCommand}
|
|
||||||
*/
|
|
||||||
export function parseCommand(body) {
|
|
||||||
const tokens = body?.trim().split(/\s+/) ?? [];
|
|
||||||
const cmd = tokens[0]?.toLowerCase() ?? "";
|
|
||||||
const args = tokens.slice(1);
|
|
||||||
const valid = cmd.startsWith(CMD_PREFIX) || cmd === "a";
|
|
||||||
|
|
||||||
return { cmd, args, valid };
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { cmdMany } from "./handlers/many.js";
|
|
||||||
import { cmdVideo } from "./handlers/video.js";
|
|
||||||
import { cmdAudio } from "./handlers/audio.js";
|
|
||||||
import { cmdFigurinha } from "./handlers/figurinha.js";
|
|
||||||
import { cmdAdivinhacao } from "./handlers/adivinhacao.js";
|
|
||||||
import { cmdObrigado } from "./handlers/obrigado.js";
|
|
||||||
import { cmdA } from "./handlers/a.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mapa de comando → handler.
|
|
||||||
* Cada handler tem a assinatura: (msg, chat, chatId, args) => Promise<void>
|
|
||||||
*
|
|
||||||
* @type {Map<string, Function>}
|
|
||||||
*/
|
|
||||||
export const commandRegistry = new Map([
|
|
||||||
["!many", cmdMany],
|
|
||||||
["!video", cmdVideo],
|
|
||||||
["!audio", cmdAudio],
|
|
||||||
["!figurinha", cmdFigurinha],
|
|
||||||
["!adivinhação", cmdAdivinhacao],
|
|
||||||
["!obrigado", cmdObrigado],
|
|
||||||
["!valeu", cmdObrigado],
|
|
||||||
["!brigado", cmdObrigado],
|
|
||||||
["a", cmdA],
|
|
||||||
]);
|
|
||||||
@@ -1,5 +1,81 @@
|
|||||||
export const CLIENT_ID = "bot_permanente";
|
/**
|
||||||
export const BOT_PREFIX = "🤖 *ManyBot:* ";
|
* config.js
|
||||||
export const CHATS = [
|
*
|
||||||
// coloque os chats que quer aqui
|
* Reads and parses manybot.conf.
|
||||||
];
|
* Supports multiline lists and inline comments.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { fileURLToPath } from "url";
|
||||||
|
|
||||||
|
function parseConf(raw) {
|
||||||
|
const lines = raw.split("\n");
|
||||||
|
|
||||||
|
const cleaned = [];
|
||||||
|
let insideList = false;
|
||||||
|
let buffer = "";
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
line = line.replace(/#.*$/, "").trim();
|
||||||
|
if (!line) continue;
|
||||||
|
|
||||||
|
if (!insideList) {
|
||||||
|
if (line.includes("=[") && !line.includes("]")) {
|
||||||
|
insideList = true;
|
||||||
|
buffer = line;
|
||||||
|
} else {
|
||||||
|
cleaned.push(line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buffer += line;
|
||||||
|
if (line.includes("]")) {
|
||||||
|
insideList = false;
|
||||||
|
cleaned.push(buffer);
|
||||||
|
buffer = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = {};
|
||||||
|
for (const line of cleaned) {
|
||||||
|
const eqIdx = line.indexOf("=");
|
||||||
|
if (eqIdx === -1) continue;
|
||||||
|
|
||||||
|
const key = line.slice(0, eqIdx).trim();
|
||||||
|
const raw = line.slice(eqIdx + 1).trim();
|
||||||
|
|
||||||
|
if (raw.startsWith("[") && raw.endsWith("]")) {
|
||||||
|
result[key] = raw
|
||||||
|
.slice(1, -1)
|
||||||
|
.split(",")
|
||||||
|
.map(x => x.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
} else {
|
||||||
|
result[key] = raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
export const CLIENT_ID = config.CLIENT_ID ?? "bot_permanente";
|
||||||
|
export const CMD_PREFIX = config.CMD_PREFIX ?? "!";
|
||||||
|
export const CHATS = config.CHATS ?? [];
|
||||||
|
|
||||||
|
/** Active plugin list — e.g., PLUGINS=[video, audio, hello] */
|
||||||
|
export const PLUGINS = config.PLUGINS ?? [];
|
||||||
|
|
||||||
|
/** 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;
|
||||||
|
|||||||
@@ -1,112 +0,0 @@
|
|||||||
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 { logger } from "../logger/logger.js";
|
|
||||||
|
|
||||||
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",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Ambos baixam como vídeo — áudio é convertido depois via ffmpeg
|
|
||||||
const ARGS_BY_TYPE = {
|
|
||||||
video: ["-f", "bv+ba/best"],
|
|
||||||
audio: ["-f", "bv+ba/best"], // baixa vídeo, converte depois
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Baixa um vídeo ou áudio via yt-dlp.
|
|
||||||
* Para áudio, baixa o vídeo e converte para mp3 com ffmpeg.
|
|
||||||
* @param {"video"|"audio"} type
|
|
||||||
* @param {string} url
|
|
||||||
* @param {string} id
|
|
||||||
* @returns {Promise<string>} caminho do arquivo final
|
|
||||||
*/
|
|
||||||
export async function download(type, url, id) {
|
|
||||||
fs.mkdirSync(DOWNLOADS_DIR, { recursive: true });
|
|
||||||
|
|
||||||
const output = path.join(DOWNLOADS_DIR, `${id}.%(ext)s`);
|
|
||||||
const args = [...ARGS_BASE, ...ARGS_BY_TYPE[type], "--output", output, url];
|
|
||||||
const videoPath = await runProcess(YT_DLP, args, type);
|
|
||||||
|
|
||||||
if (type === "audio") {
|
|
||||||
return convertToMp3(videoPath, id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return videoPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converte um arquivo de vídeo para mp3 via ffmpeg.
|
|
||||||
* Remove o vídeo original após a conversão.
|
|
||||||
* @param {string} videoPath
|
|
||||||
* @param {string} id
|
|
||||||
* @returns {Promise<string>} caminho do mp3
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Compat ────────────────────────────────────────────────────
|
|
||||||
export const get_video = (url, id) => download("video", url, id);
|
|
||||||
export const get_audio = (url, id) => download("audio", url, id);
|
|
||||||
|
|
||||||
// ── Interno ───────────────────────────────────────────────────
|
|
||||||
function runProcess(cmd, args, type) {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const proc = spawn(cmd, args);
|
|
||||||
let stdout = "";
|
|
||||||
|
|
||||||
proc.stdout.on("data", (data) => { stdout += data.toString(); });
|
|
||||||
proc.stderr.on("data", (data) => { logger.warn(`yt-dlp [${type}]: ${data.toString().trim()}`); });
|
|
||||||
|
|
||||||
proc.on("close", (code) => {
|
|
||||||
if (code !== 0) {
|
|
||||||
return reject(new Error(
|
|
||||||
`Não foi possível baixar o ${type}. 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(
|
|
||||||
"O download foi concluído, mas o arquivo não foi encontrado. Tente novamente."
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(filepath);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
/**
|
|
||||||
* Retorna o MIME type e extensão para cada tipo de download suportado.
|
|
||||||
* @param {"video"|"audio"} type
|
|
||||||
* @returns {{ mime: string, label: string }}
|
|
||||||
*/
|
|
||||||
export function resolveMediaType(type) {
|
|
||||||
const types = {
|
|
||||||
video: { mime: "video/mp4", label: "vídeo" },
|
|
||||||
audio: { mime: "audio/mpeg", label: "áudio" },
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolved = types[type];
|
|
||||||
if (!resolved) throw new Error(`Tipo de mídia desconhecido: ${type}`);
|
|
||||||
return resolved;
|
|
||||||
}
|
|
||||||
@@ -1,74 +1,55 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import pkg from "whatsapp-web.js";
|
|
||||||
import { download } from "./downloader.js";
|
|
||||||
import { resolveMediaType } from "./mediaType.js";
|
|
||||||
import { botMsg } from "../utils/botMsg.js";
|
|
||||||
import { emptyFolder } from "../utils/file.js";
|
|
||||||
import { logger } from "../logger/logger.js";
|
|
||||||
import client from "../client/whatsappClient.js";
|
|
||||||
|
|
||||||
const { MessageMedia } = pkg;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{ type: "video"|"audio", url: string, msg: object, chatId: string }} DownloadJob
|
* src/download/queue.js
|
||||||
|
*
|
||||||
|
* Sequential execution queue for heavy jobs (downloads, conversions).
|
||||||
|
* Ensures only one job runs at a time — without overloading yt-dlp or ffmpeg.
|
||||||
|
*
|
||||||
|
* Plugin passes a `workFn` that does everything: download, convert, send.
|
||||||
|
* Queue only handles sequence and error handling.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* import { enqueue } from "../../src/download/queue.js";
|
||||||
|
* enqueue(async () => { ... all plugin logic ... }, onError);
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/** @type {DownloadJob[]} */
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* workFn: () => Promise<void>,
|
||||||
|
* errorFn: (err: Error) => Promise<void>,
|
||||||
|
* }} Job
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @type {Job[]} */
|
||||||
let queue = [];
|
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 {"video"|"audio"} type
|
*
|
||||||
* @param {string} url
|
* @param {Function} workFn — async () => void — all plugin logic
|
||||||
* @param {object} msg
|
* @param {Function} errorFn — async (err) => void — called if workFn throws
|
||||||
* @param {string} chatId
|
|
||||||
*/
|
*/
|
||||||
export function enqueueDownload(type, url, msg, chatId) {
|
export function enqueue(workFn, errorFn) {
|
||||||
queue.push({ type, url, msg, chatId });
|
queue.push({ workFn, errorFn });
|
||||||
if (!processing) processQueue();
|
if (!processing) processQueue();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processQueue() {
|
async function processQueue() {
|
||||||
processing = true;
|
processing = true;
|
||||||
|
|
||||||
while (queue.length) {
|
while (queue.length) {
|
||||||
const job = queue.shift();
|
await processJob(queue.shift());
|
||||||
await processJob(job);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
processing = false;
|
processing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
async function processJob({ workFn, errorFn }) {
|
||||||
* Executa um único job: baixa, envia e limpa.
|
|
||||||
* @param {DownloadJob} job
|
|
||||||
*/
|
|
||||||
async function processJob(job) {
|
|
||||||
const { mime, label } = resolveMediaType(job.type);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const filePath = await download(job.type, job.url, job.msg.id._serialized);
|
await workFn();
|
||||||
|
|
||||||
const media = new MessageMedia(
|
|
||||||
mime,
|
|
||||||
fs.readFileSync(filePath).toString("base64"),
|
|
||||||
path.basename(filePath)
|
|
||||||
);
|
|
||||||
|
|
||||||
await client.sendMessage(job.chatId, media);
|
|
||||||
|
|
||||||
fs.unlinkSync(filePath);
|
|
||||||
emptyFolder("downloads");
|
|
||||||
|
|
||||||
logger.done(`download:${job.type}`, job.url);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Falha ao baixar ${label} — ${err.message}`);
|
logger.error(t("system.downloadJobFailed", { message: err.message }));
|
||||||
await job.msg.reply(botMsg(
|
try { await errorFn(err); } catch { }
|
||||||
`❌ Não consegui baixar o ${label}.\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,29 +0,0 @@
|
|||||||
import { CHATS, BOT_PREFIX } from "../config.js";
|
|
||||||
import { getChatId } from "../utils/getChatId.js";
|
|
||||||
import { processarComando } from "../commands/index.js";
|
|
||||||
import { coletarMidia } from "../commands/logic/figurinha.js";
|
|
||||||
import { processarJogo } from "../commands/logic/games/adivinhacao.js";
|
|
||||||
import { buildMessageContext } from "../logger/messageContext.js";
|
|
||||||
import { logger } from "../logger/logger.js";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pipeline de processamento de uma mensagem recebida.
|
|
||||||
* Ordem: filtro de chat → log → mídia → comando → jogo.
|
|
||||||
*
|
|
||||||
* @param {import("whatsapp-web.js").Message} msg
|
|
||||||
*/
|
|
||||||
export async function handleMessage(msg) {
|
|
||||||
const chat = await msg.getChat();
|
|
||||||
const chatId = getChatId(chat);
|
|
||||||
|
|
||||||
if (!CHATS.includes(chatId)) return;
|
|
||||||
|
|
||||||
const ctx = await buildMessageContext(msg, chat, BOT_PREFIX);
|
|
||||||
logger.msg(ctx);
|
|
||||||
|
|
||||||
await coletarMidia(msg);
|
|
||||||
await processarComando(msg, chat, chatId);
|
|
||||||
await processarJogo(msg, chat);
|
|
||||||
|
|
||||||
logger.done("message_create", `de +${ctx.senderNumber}`);
|
|
||||||
}
|
|
||||||
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 };
|
||||||
43
src/kernel/messageHandler.js
Normal file
43
src/kernel/messageHandler.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* messageHandler.js
|
||||||
|
*
|
||||||
|
* Central pipeline for received messages.
|
||||||
|
*
|
||||||
|
* Order:
|
||||||
|
* 1. Filter allowed chats (CHATS from .conf)
|
||||||
|
* — if CHATS is empty, accepts all chats
|
||||||
|
* 2. Log the message
|
||||||
|
* 3. Pass context to all active plugins
|
||||||
|
*
|
||||||
|
* Kernel knows no commands — only distributes.
|
||||||
|
* Each plugin decides on its own whether to act or ignore.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CHATS } from "../config.js";
|
||||||
|
import { getChatId } from "../utils/getChatId.js";
|
||||||
|
import { buildApi } from "./pluginApi.js";
|
||||||
|
import { pluginRegistry } from "./pluginLoader.js";
|
||||||
|
import { runPlugin } from "./pluginGuard.js";
|
||||||
|
import { buildMessageContext } from "../logger/messageContext.js";
|
||||||
|
import { logger } from "../logger/logger.js";
|
||||||
|
import client from "../client/whatsappClient.js";
|
||||||
|
|
||||||
|
export async function handleMessage(msg) {
|
||||||
|
const chat = await msg.getChat();
|
||||||
|
const chatId = getChatId(chat);
|
||||||
|
|
||||||
|
// CHATS empty = accepts all chats
|
||||||
|
if (CHATS.length > 0 && !CHATS.includes(chatId)) return;
|
||||||
|
|
||||||
|
const ctx = await buildMessageContext(msg, chat);
|
||||||
|
logger.msg(ctx);
|
||||||
|
|
||||||
|
const api = buildApi({ msg, chat, client, pluginRegistry });
|
||||||
|
const context = { msg: api.msg, chat: api.chat, api };
|
||||||
|
|
||||||
|
for (const plugin of pluginRegistry.values()) {
|
||||||
|
await runPlugin(plugin, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.done("message_create", `de +${ctx.senderNumber}`);
|
||||||
|
}
|
||||||
278
src/kernel/pluginApi.js
Normal file
278
src/kernel/pluginApi.js
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
/**
|
||||||
|
* pluginApi.js
|
||||||
|
*
|
||||||
|
* Builds the `api` object each plugin receives.
|
||||||
|
* Plugins can only do what's here — never touch client directly.
|
||||||
|
*
|
||||||
|
* `chat` is already filtered by kernel (only allowed chats from .conf),
|
||||||
|
* so plugins don't need and can't choose destination.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from "../logger/logger.js";
|
||||||
|
import pkg from "whatsapp-web.js";
|
||||||
|
|
||||||
|
const { MessageMedia } = pkg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} params
|
||||||
|
* @param {import("whatsapp-web.js").Message} params.msg
|
||||||
|
* @param {import("whatsapp-web.js").Chat} params.chat
|
||||||
|
* @param {Map<string, any>} params.pluginRegistry
|
||||||
|
* @returns {object} api
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* 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 }) {
|
||||||
|
|
||||||
|
const currentChat = chat;
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
// ── Message reading ─────────────────────────────────────
|
||||||
|
|
||||||
|
msg: {
|
||||||
|
/** Message body */
|
||||||
|
body: msg.body ?? "",
|
||||||
|
|
||||||
|
/** Type: "chat", "image", "video", "audio", "ptt", "sticker", "document" */
|
||||||
|
type: msg.type,
|
||||||
|
|
||||||
|
/** true if message came from bot itself */
|
||||||
|
fromMe: msg.fromMe,
|
||||||
|
|
||||||
|
/** Sender ID (ex: "5511999999999@c.us") */
|
||||||
|
sender: msg.author || msg.from,
|
||||||
|
|
||||||
|
/** Display name of sender */
|
||||||
|
senderName: msg._data?.notifyName || String(msg.from).replace(/(:\d+)?@.*$/, ""),
|
||||||
|
|
||||||
|
/** Tokens: ["!video", "https://..."] */
|
||||||
|
args: msg.body?.trim().split(/\s+/) ?? [],
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if message is a specific command.
|
||||||
|
* @param {string} cmd — ex: "!hello"
|
||||||
|
*/
|
||||||
|
is(cmd) {
|
||||||
|
return msg.body?.trim().toLowerCase().startsWith(cmd.toLowerCase());
|
||||||
|
},
|
||||||
|
|
||||||
|
/** true if message has attached media */
|
||||||
|
hasMedia: msg.hasMedia,
|
||||||
|
|
||||||
|
/** true if media is a GIF (short looping video) */
|
||||||
|
isGif: msg._data?.isGif ?? false,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Download message media.
|
||||||
|
* Returns neutral object { mimetype, data } — without exposing MessageMedia.
|
||||||
|
* @returns {Promise<{ mimetype: string, data: string } | null>}
|
||||||
|
*/
|
||||||
|
async downloadMedia() {
|
||||||
|
const media = await msg.downloadMedia();
|
||||||
|
if (!media) return null;
|
||||||
|
return { mimetype: media.mimetype, data: media.data };
|
||||||
|
},
|
||||||
|
|
||||||
|
/** true if message is a reply to another */
|
||||||
|
hasReply: msg.hasQuotedMsg,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns quoted message if exists.
|
||||||
|
* @returns {Promise<import("whatsapp-web.js").Message|null>}
|
||||||
|
*/
|
||||||
|
async getReply() {
|
||||||
|
if (!msg.hasQuotedMsg) return null;
|
||||||
|
return msg.getQuotedMessage();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reply directly to message (with quote).
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
async reply(text) {
|
||||||
|
return msg.reply(text);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Send to current chat ─────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send plain text.
|
||||||
|
* @param {string} text
|
||||||
|
*/
|
||||||
|
async send(text) {
|
||||||
|
return currentChat.sendMessage(text);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send media (image, video, audio, document).
|
||||||
|
* @param {import("whatsapp-web.js").MessageMedia} media
|
||||||
|
* @param {string} [caption]
|
||||||
|
*/
|
||||||
|
async sendMedia(media, caption = "") {
|
||||||
|
return currentChat.sendMessage(media, { caption });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send video file from local path.
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} [caption]
|
||||||
|
*/
|
||||||
|
async sendVideo(filePath, caption = "") {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return currentChat.sendMessage(media, { caption });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send audio file from local path.
|
||||||
|
* @param {string} filePath
|
||||||
|
*/
|
||||||
|
async sendAudio(filePath) {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return currentChat.sendMessage(media, { sendAudioAsVoice: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send image from local path.
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} [caption]
|
||||||
|
*/
|
||||||
|
async sendImage(filePath, caption = "") {
|
||||||
|
const media = MessageMedia.fromFilePath(filePath);
|
||||||
|
return currentChat.sendMessage(media, { caption });
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a sticker.
|
||||||
|
* Accepts filePath (string) or buffer (Buffer) — plugin never needs
|
||||||
|
* to know MessageMedia exists.
|
||||||
|
* @param {string | Buffer} source
|
||||||
|
*/
|
||||||
|
async sendSticker(source) {
|
||||||
|
const media = typeof source === "string"
|
||||||
|
? MessageMedia.fromFilePath(source)
|
||||||
|
: new MessageMedia("image/webp", source.toString("base64"));
|
||||||
|
return currentChat.sendMessage(media, { sendMediaAsSticker: true });
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Send to specific chat ───────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send text to specific chat by ID.
|
||||||
|
* @param {string} chatId
|
||||||
|
* @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}
|
||||||
|
*/
|
||||||
|
getPlugin(name) {
|
||||||
|
return pluginRegistry.get(name)?.exports ?? null;
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Logger ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
log: {
|
||||||
|
info: (...a) => logger.info(...a),
|
||||||
|
warn: (...a) => logger.warn(...a),
|
||||||
|
error: (...a) => logger.error(...a),
|
||||||
|
success: (...a) => logger.success(...a),
|
||||||
|
},
|
||||||
|
|
||||||
|
// ── Current chat info ────────────────────────────────────
|
||||||
|
|
||||||
|
chat: {
|
||||||
|
id: currentChat.id._serialized,
|
||||||
|
name: currentChat.name || currentChat.id.user,
|
||||||
|
isGroup: /@g\.us$/.test(currentChat.id._serialized),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
37
src/kernel/pluginGuard.js
Normal file
37
src/kernel/pluginGuard.js
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/**
|
||||||
|
* pluginGuard.js
|
||||||
|
*
|
||||||
|
* Runs a plugin safely.
|
||||||
|
* If plugin throws an error:
|
||||||
|
* - Logs error with context
|
||||||
|
* - Marks plugin as "error" in registry
|
||||||
|
* - Never crashes the bot
|
||||||
|
*
|
||||||
|
* Disabled or errored plugins are silently ignored.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
import { pluginRegistry } from "./pluginLoader.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} plugin — pluginRegistry entry
|
||||||
|
* @param {object} context — { msg, chat, api }
|
||||||
|
*/
|
||||||
|
export async function runPlugin(plugin, context) {
|
||||||
|
if (plugin.status !== "active") return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await plugin.run(context);
|
||||||
|
} catch (err) {
|
||||||
|
// Disable plugin to prevent further breakage
|
||||||
|
plugin.status = "error";
|
||||||
|
plugin.error = err;
|
||||||
|
pluginRegistry.set(plugin.name, plugin);
|
||||||
|
|
||||||
|
logger.error(
|
||||||
|
t("system.pluginDisabledAfterError", { name: plugin.name, message: err.message }),
|
||||||
|
`\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
111
src/kernel/pluginLoader.js
Normal file
111
src/kernel/pluginLoader.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* pluginLoader.js
|
||||||
|
*
|
||||||
|
* Responsible for:
|
||||||
|
* 1. Reading active plugins from manybot.conf (PLUGINS=[...])
|
||||||
|
* 2. Loading each plugin from /plugins folder
|
||||||
|
* 3. Registering in pluginRegistry with status and public exports
|
||||||
|
* 4. Exposing pluginRegistry to kernel and pluginApi
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from "fs";
|
||||||
|
import path from "path";
|
||||||
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
|
const PLUGINS_DIR = path.resolve("src/plugins");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Each entry in registry:
|
||||||
|
* {
|
||||||
|
* name: string,
|
||||||
|
* status: "active" | "disabled" | "error",
|
||||||
|
* run: async function({ msg, chat, api }) — plugin default function
|
||||||
|
* exports: any — what plugin exposed via `export const api = { ... }`
|
||||||
|
* error: Error | null
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* @type {Map<string, object>}
|
||||||
|
*/
|
||||||
|
export const pluginRegistry = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load all active plugins listed in `activePlugins`.
|
||||||
|
* Called once during bot initialization.
|
||||||
|
*
|
||||||
|
* @param {string[]} activePlugins — active plugin names (from .conf)
|
||||||
|
*/
|
||||||
|
export async function loadPlugins(activePlugins) {
|
||||||
|
if (!fs.existsSync(PLUGINS_DIR)) {
|
||||||
|
logger.warn(t("system.pluginsFolderNotFound"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name of activePlugins) {
|
||||||
|
await loadPlugin(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const total = pluginRegistry.size;
|
||||||
|
const ativos = [...pluginRegistry.values()].filter(p => p.status === "active").length;
|
||||||
|
const erros = total - ativos;
|
||||||
|
|
||||||
|
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 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Carrega um único plugin pelo nome.
|
||||||
|
* @param {string} name
|
||||||
|
*/
|
||||||
|
async function loadPlugin(name) {
|
||||||
|
const pluginPath = path.join(PLUGINS_DIR, name, "index.js");
|
||||||
|
|
||||||
|
if (!fs.existsSync(pluginPath)) {
|
||||||
|
logger.warn(t("system.pluginNotFound", { name, path: pluginPath }));
|
||||||
|
pluginRegistry.set(name, { name, status: "disabled", run: null, exports: null, error: null });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mod = await import(pluginPath);
|
||||||
|
|
||||||
|
// Plugin must export a default function — this is called on every message
|
||||||
|
if (typeof mod.default !== "function") {
|
||||||
|
throw new Error(`Plugin "${name}" does not export a default function`);
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginRegistry.set(name, {
|
||||||
|
name,
|
||||||
|
status: "active",
|
||||||
|
run: mod.default,
|
||||||
|
setup: mod.setup ?? null, // opcional — chamado uma vez na inicialização
|
||||||
|
exports: mod.api ?? null,
|
||||||
|
error: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(t("system.pluginLoaded", { name }));
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(t("system.pluginLoadFailed", { name, message: err.message }));
|
||||||
|
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
|
||||||
|
};
|
||||||
|
}
|
||||||
48
src/kernel/scheduler.js
Normal file
48
src/kernel/scheduler.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* scheduler.js
|
||||||
|
*
|
||||||
|
* Allows plugins to register scheduled tasks via cron.
|
||||||
|
* Uses node-cron underneath, but plugins never import node-cron directly —
|
||||||
|
* they only call api.schedule(cron, fn).
|
||||||
|
*
|
||||||
|
* Usage in plugin:
|
||||||
|
* import { schedule } from "many";
|
||||||
|
* schedule("0 9 * * 1", async () => { await api.send("Good morning!"); });
|
||||||
|
*/
|
||||||
|
|
||||||
|
import cron from "node-cron";
|
||||||
|
import { logger } from "../logger/logger.js";
|
||||||
|
import { t } from "../i18n/index.js";
|
||||||
|
|
||||||
|
/** List of active tasks (for eventual teardown) */
|
||||||
|
const tasks = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a cron task.
|
||||||
|
* @param {string} expression — cron expression e.g., "0 9 * * 1"
|
||||||
|
* @param {Function} fn — async function to execute
|
||||||
|
* @param {string} pluginName — plugin name (for logging)
|
||||||
|
*/
|
||||||
|
export function schedule(expression, fn, pluginName = "unknown") {
|
||||||
|
if (!cron.validate(expression)) {
|
||||||
|
logger.warn(t("system.schedulerInvalidCron", { name: pluginName, expression }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = cron.schedule(expression, async () => {
|
||||||
|
try {
|
||||||
|
await fn();
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(t("system.schedulerError", { name: pluginName, message: err.message }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tasks.push({ pluginName, expression, task });
|
||||||
|
logger.info(t("system.schedulerRegistered", { name: pluginName, expression }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Stop all schedules (useful for shutdown) */
|
||||||
|
export function stopAll() {
|
||||||
|
tasks.forEach(({ task }) => task.stop());
|
||||||
|
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",
|
||||||
@@ -9,27 +9,27 @@ export const c = {
|
|||||||
export const SEP = `${c.gray}${"─".repeat(52)}${c.reset}`;
|
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, isCommand) =>
|
export const formatBody = (body, isCommand) =>
|
||||||
body?.trim()
|
body?.trim()
|
||||||
? `${isCommand ? c.yellow : 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}` +
|
||||||
|
|||||||
@@ -1,52 +1,40 @@
|
|||||||
import {
|
import {
|
||||||
c, SEP, 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(`${SEP}\n${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, isCommand, quoted } = ctx;
|
const { chatName, isGroup, senderName, senderNumber, type, body, quoted } = ctx;
|
||||||
|
const context = isGroup ? `${chatName} (${t("log.context.group")})` : chatName;
|
||||||
const typeLabel = formatType(type);
|
const reply = quoted ? ` → ${t("log.context.replyTo")} ${quoted.name} +${quoted.number}: "${quoted.preview}"` : "";
|
||||||
const context = formatContext(chatName, isGroup);
|
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}`);
|
||||||
const bodyText = formatBody(body, isCommand);
|
|
||||||
const replyLine = quoted
|
|
||||||
? formatReply(quoted.name, quoted.number, quoted.preview)
|
|
||||||
: "";
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
`${SEP}\n` +
|
|
||||||
`${c.gray}[${now()}]${c.reset} ${c.cyan}MSG ${c.reset}${context}\n` +
|
|
||||||
`${c.gray} De: ${c.reset}${c.white}${senderName}${c.reset} ${c.dim}+${senderNumber}${c.reset}\n` +
|
|
||||||
`${c.gray} Tipo: ${c.reset}${typeLabel}${isCommand ? ` ${c.yellow}(bot)${c.reset}` : ""}\n` +
|
|
||||||
`${c.gray} Text: ${c.reset}${bodyText}` +
|
|
||||||
replyLine
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
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
|
||||||
@@ -28,7 +28,6 @@ export async function getNumber(msg) {
|
|||||||
* @property {string} senderNumber
|
* @property {string} senderNumber
|
||||||
* @property {string} type
|
* @property {string} type
|
||||||
* @property {string} body
|
* @property {string} body
|
||||||
* @property {boolean} isCommand
|
|
||||||
* @property {{ name: string, number: string, preview: string } | null} quoted
|
* @property {{ name: string, number: string, preview: string } | null} quoted
|
||||||
*/
|
*/
|
||||||
export async function buildMessageContext(msg, chat, botPrefix) {
|
export async function buildMessageContext(msg, chat, botPrefix) {
|
||||||
@@ -47,14 +46,13 @@ export async function buildMessageContext(msg, chat, botPrefix) {
|
|||||||
senderNumber: number,
|
senderNumber: number,
|
||||||
type: msg?.type || "text",
|
type: msg?.type || "text",
|
||||||
body: msg.body,
|
body: msg.body,
|
||||||
isCommand: !!msg.body?.trimStart().startsWith(botPrefix),
|
|
||||||
quoted,
|
quoted,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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>}
|
||||||
@@ -70,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}"`
|
||||||
|
|||||||
42
src/main.js
42
src/main.js
@@ -1,19 +1,47 @@
|
|||||||
import client from "./client/whatsappClient.js";
|
/**
|
||||||
import { handleMessage } from "./handlers/messageHandler.js";
|
* main.js
|
||||||
import { logger } from "./logger/logger.js";
|
*
|
||||||
|
* ManyBot entry point.
|
||||||
|
* Initializes WhatsApp client and loads plugins.
|
||||||
|
*/
|
||||||
|
|
||||||
logger.info("Iniciando ManyBot...");
|
import client from "./client/whatsappClient.js";
|
||||||
|
import { handleMessage } from "./kernel/messageHandler.js";
|
||||||
|
import { loadPlugins, setupPlugins } from "./kernel/pluginLoader.js";
|
||||||
|
import { buildSetupApi } from "./kernel/pluginApi.js";
|
||||||
|
import { logger } from "./logger/logger.js";
|
||||||
|
import { PLUGINS } from "./config.js";
|
||||||
|
import { t } from "./i18n/index.js";
|
||||||
|
|
||||||
|
logger.info(t("bot.starting"));
|
||||||
|
|
||||||
|
// Global safety net — no error should crash the bot
|
||||||
|
process.on("uncaughtException", (err) => {
|
||||||
|
logger.error(`${t("bot.error.uncaught")} — ${err.message}`, `\n ${t("errors.stack")}: ${err.stack?.split("\n")[1]?.trim() ?? ""}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("unhandledRejection", (reason) => {
|
||||||
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
||||||
|
logger.error(`${t("bot.error.unhandled")} — ${msg}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load plugins before connecting
|
||||||
|
await loadPlugins(PLUGINS);
|
||||||
|
|
||||||
client.on("message_create", async (msg) => {
|
client.on("message_create", async (msg) => {
|
||||||
try {
|
try {
|
||||||
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();
|
||||||
logger.info("Cliente inicializado. Aguardando conexão com WhatsApp...");
|
console.log("\n");
|
||||||
|
logger.info(t("bot.initialized"));
|
||||||
@@ -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,49 +1,58 @@
|
|||||||
/**
|
/**
|
||||||
* 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";
|
||||||
import { CLIENT_ID } from "../config.js";
|
import { resolvePuppeteerConfig } from "../client/environment.js";
|
||||||
|
|
||||||
|
const CLIENT_ID="getId"
|
||||||
const { Client, LocalAuth } = pkg;
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
const client = new Client({
|
const client = new Client({
|
||||||
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
|
||||||
puppeteer: { headless: true },
|
puppeteer: {
|
||||||
|
headless: true,
|
||||||
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-setuid-sandbox',
|
||||||
|
...(resolvePuppeteerConfig().args || [])
|
||||||
|
],
|
||||||
|
...resolvePuppeteerConfig()
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
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 };
|
||||||
3
todo.txt
3
todo.txt
@@ -1,3 +0,0 @@
|
|||||||
Possibilidade de tirar fundo de figurinhas
|
|
||||||
|
|
||||||
Salvar mensagens num banco de dados
|
|
||||||
141
update
Normal file
141
update
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ==============================================================================
|
||||||
|
# update.sh — Script de atualização segura do ManyBot
|
||||||
|
# ==============================================================================
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Configuração
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
tmp_dir="$dir/tmp"
|
||||||
|
log_file="$dir/update.log"
|
||||||
|
config_items=(".wwebjs_auth" ".wwebjs_cache")
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Logging
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
log() {
|
||||||
|
local level="$1"; shift
|
||||||
|
local msg="$*"
|
||||||
|
local timestamp
|
||||||
|
timestamp="$(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
|
echo "[$timestamp] [$level] $msg" | tee -a "$log_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Trap: executa em caso de qualquer erro
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
cleanup_on_error() {
|
||||||
|
local 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
|
||||||
|
log "WARN" "Arquivos de configuração preservados em: $tmp_dir"
|
||||||
|
log "WARN" "Restaure manualmente com: mv \"$tmp_dir\"/* \"$dir\"/"
|
||||||
|
fi
|
||||||
|
exit $exit_code
|
||||||
|
}
|
||||||
|
trap cleanup_on_error ERR
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Validações iniciais
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
log "INFO" "Iniciando atualização do ManyBot..."
|
||||||
|
|
||||||
|
# Verifica dependências obrigatórias
|
||||||
|
for cmd in git npm; do
|
||||||
|
if ! command -v "$cmd" &>/dev/null; then
|
||||||
|
log "ERROR" "Dependência ausente: '$cmd' não encontrado no PATH."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Verifica se está em um repositório git
|
||||||
|
if ! git -C "$dir" rev-parse --is-inside-work-tree &>/dev/null; then
|
||||||
|
log "ERROR" "'$dir' não é um repositório git."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Exibe o remote para transparência
|
||||||
|
remote_url=$(git -C "$dir" remote get-url origin 2>/dev/null || echo "(não definido)")
|
||||||
|
log "INFO" "Remote: $remote_url"
|
||||||
|
|
||||||
|
# Verifica se há alterações locais não commitadas
|
||||||
|
if ! git -C "$dir" diff --quiet || ! git -C "$dir" diff --cached --quiet; then
|
||||||
|
log "WARN" "Existem alterações locais não commitadas. Elas serão descartadas pelo reset."
|
||||||
|
read -r -p "Deseja continuar mesmo assim? [s/N] " confirm
|
||||||
|
confirm="${confirm:-N}"
|
||||||
|
if [[ ! "$confirm" =~ ^[sS]$ ]]; then
|
||||||
|
log "INFO" "Atualização cancelada pelo usuário."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Backup dos arquivos de configuração
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
mkdir -p "$tmp_dir"
|
||||||
|
|
||||||
|
backed_up=()
|
||||||
|
for item in "${config_items[@]}"; do
|
||||||
|
src="$dir/$item"
|
||||||
|
if [ -e "$src" ]; then
|
||||||
|
mv "$src" "$tmp_dir/"
|
||||||
|
backed_up+=("$item")
|
||||||
|
log "INFO" "Backup: $item → tmp/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Atualização do repositório
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
log "INFO" "Buscando atualizações..."
|
||||||
|
git -C "$dir" fetch origin
|
||||||
|
|
||||||
|
branch=$(git -C "$dir" rev-parse --abbrev-ref HEAD)
|
||||||
|
log "INFO" "Branch atual: $branch"
|
||||||
|
|
||||||
|
# Registra o hash antes e depois para auditoria
|
||||||
|
hash_before=$(git -C "$dir" rev-parse HEAD)
|
||||||
|
git -C "$dir" reset --hard "origin/$branch"
|
||||||
|
hash_after=$(git -C "$dir" rev-parse HEAD)
|
||||||
|
|
||||||
|
if [ "$hash_before" = "$hash_after" ]; then
|
||||||
|
log "INFO" "Repositório já estava atualizado (sem mudanças)."
|
||||||
|
else
|
||||||
|
log "INFO" "Atualizado: $hash_before → $hash_after"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Instalação de dependências Node
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
log "INFO" "Instalando dependências Node..."
|
||||||
|
npm ci --omit=dev 2>&1 | tee -a "$log_file"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Restauração dos arquivos de configuração
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
if [ ${#backed_up[@]} -gt 0 ]; then
|
||||||
|
for item in "${backed_up[@]}"; do
|
||||||
|
src="$tmp_dir/$item"
|
||||||
|
dst="$dir/$item"
|
||||||
|
if [ -e "$src" ]; then
|
||||||
|
# Remove o que npm possa ter criado (ex: node_modules)
|
||||||
|
rm -rf "$dst"
|
||||||
|
mv "$src" "$dst"
|
||||||
|
log "INFO" "Restaurado: $item"
|
||||||
|
else
|
||||||
|
log "WARN" "Item esperado no backup não encontrado: $item"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Limpa tmp apenas após restauração bem-sucedida
|
||||||
|
rm -rf "$tmp_dir"
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Concluído
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
log "INFO" "ManyBot atualizado com sucesso."
|
||||||
Reference in New Issue
Block a user