6 Commits

Author SHA1 Message Date
synt-xerror
c481ae3519 teste pra 2.0 2026-03-13 06:59:12 -03:00
synt-xerror
b94d603572 a 2026-03-12 11:32:07 -03:00
synt-xerror
5ba68c9ee3 removing node_modules 2026-03-12 07:52:29 -03:00
synt-xerror
240c392fad yt-dlp support 2026-03-12 01:22:29 -03:00
synt-xerror
b96332e618 Merge branch 'main' 2026-03-11 20:10:28 -03:00
SyntaxError!
c95d66a763 Initial commit 2026-03-11 20:00:13 -03:00
12 changed files with 3186 additions and 137 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
env.js
.wwebjs_auth
.wwebjs_cache
downloads
node_modules/

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 SyntaxError!
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

15
README.md Normal file
View File

@@ -0,0 +1,15 @@
![ManyBot Logo](logo.png)
Criei esse bot para servir um grupo de amigos. Meu foco não é fazer ele funcionar para todo mundo.
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.
Você consegue totalmente clonar esse repoistório e rodar seu próprio ManyBot. A licenca MIT permite que você modifique o que quiser e faça seu próprio bot.
Algumas funcionalidades desse bot inclui:
- Funciona em multiplos chats em apenas uma única sessão
- Comandos de jogos e download com yt-dlp
- Gerador de figurinhas
- Ferramenta para pegar IDs dos chats
- Entre outros

4
deploy.sh Normal file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
TAG=$(git describe --tags --abbrev=0)
npm version $TAG --no-git-tag-version

58
get_id.js Normal file
View File

@@ -0,0 +1,58 @@
// get_id.js
const arg = process.argv[2]; // argumento passado no node
if (!arg) {
console.log("Use: node get_id.js grupos|contatos|<nome>");
process.exit(0);
}
console.log("[PESQUISANDO] Aguarde...");
import pkg from 'whatsapp-web.js';
const { Client, LocalAuth } = pkg;
import qrcode from 'qrcode-terminal';
const CLIENT_ID = "bot_permanente"; // sempre o mesmo
const client = new Client({
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
puppeteer: { headless: true }
});
client.on('qr', qr => {
console.log("[WPP] QR Code gerado. Escaneie apenas uma vez:");
qrcode.generate(qr, { small: true });
});
client.on('ready', async () => {
console.log("[WPP] Conectado");
const chats = await client.getChats(); // <- precisa do await
let filtered = [];
if (arg.toLowerCase() === "grupos") {
filtered = chats.filter(c => c.isGroup);
} else if (arg.toLowerCase() === "contatos") {
filtered = chats.filter(c => !c.isGroup);
} else {
const search = arg.toLowerCase();
filtered = chats.filter(c => (c.name || c.id.user).toLowerCase().includes(search));
}
if (filtered.length === 0) {
console.log("Nenhum chat encontrado com esse filtro.");
} else {
console.log(`Encontrados ${filtered.length} chats:`);
filtered.forEach(c => {
console.log("================================");
console.log("NAME:", c.name || c.id.user);
console.log("ID:", c.id._serialized);
console.log("GROUP:", c.isGroup);
});
}
process.exit(0);
});
client.initialize();

BIN
logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

384
main.js Normal file
View File

@@ -0,0 +1,384 @@
console.log("[CARREGANDO] Aguarde...");
import pkg from 'whatsapp-web.js';
const { Client, LocalAuth, MessageMedia } = pkg;
import qrcode from 'qrcode-terminal';
import fs from 'fs';
import { exec } from 'child_process';
import sharp from 'sharp';
import { CHATS } from "./env.js";
const CLIENT_ID = "bot_permanente";
const BOT_PREFIX = "🤖 *ManyBot:* ";
let jogoAtivo = null;
if (!fs.existsSync("downloads")) fs.mkdirSync("downloads");
const client = new Client({
authStrategy: new LocalAuth({ clientId: CLIENT_ID }),
puppeteer: { headless: true }
});
function botMsg(texto) {
return `${BOT_PREFIX}\n${texto}`;
}
function getChatId(chat) {
return chat.id._serialized;
}
client.on("qr", qr => {
console.log("[BOT] Escaneie o QR Code");
qrcode.generate(qr, { small: true });
});
client.on("ready", () => {
exec("clear");
console.log("[BOT] WhatsApp conectado.");
});
client.on("disconnected", reason => {
console.warn("[BOT] Reconectando:", reason);
setTimeout(() => client.initialize(), 5000);
});
client.on("message_create", async msg => {
try {
const chat = await msg.getChat();
const chatId = getChatId(chat);
if (!CHATS.includes(chatId)) return;
console.log("==================================");
console.log(`CHAT NAME : ${chat.name || chat.id.user}`);
console.log(`CHAT ID : ${chatId}`);
console.log(`FROM : ${msg.from}`);
console.log(`BODY : ${msg.body}`);
console.log("==================================\n");
await processarComando(msg, chat, chatId);
await processarJogo(msg, chat);
} catch (err) {
console.error("[ERRO]", err);
}
});
// ---------------- DOWNLOAD ----------------
let downloadQueue = [];
let processingQueue = false;
function run(cmd) {
return new Promise((resolve, reject) => {
exec(cmd, (err, stdout, stderr) => {
if (err) reject(err);
else resolve({ stdout, stderr });
});
});
}
async function get_video(url, id) {
const cmd = `./yt-dlp --extractor-args "youtube:player_client=android" -f mp4 --print after_move:filepath -o "downloads/${id}.%(ext)s" "${url}"`;
const { stdout } = await run(cmd);
const filepath = stdout.trim();
if (!filepath) throw new Error("yt-dlp não retornou caminho");
return filepath;
}
async function get_audio(url, id) {
const cmd = `./yt-dlp --extractor-args "youtube:player_client=android" -t mp3 --print after_move:filepath -o "downloads/${id}.%(ext)s" "${url}"`;
const { stdout } = await run(cmd);
const filepath = stdout.trim();
if (!filepath) throw new Error("yt-dlp não retornou caminho");
return filepath;
}
async function enviarVideo(chatId, path) {
const file = fs.readFileSync(path);
const media = new MessageMedia(
"video/mp4",
file.toString("base64"),
path.split("/").pop()
);
await client.sendMessage(chatId, media);
fs.unlinkSync(path);
}
async function enviarAudio(chatId, path) {
const file = fs.readFileSync(path);
const media = new MessageMedia(
"audio/mpeg",
file.toString("base64"),
path.split("/").pop()
);
await client.sendMessage(chatId, media);
fs.unlinkSync(path);
}
function enqueueDownload(type, url, msg, chatId) {
downloadQueue.push({ type, url, msg, chatId });
if (!processingQueue) processQueue();
}
async function processQueue() {
processingQueue = true;
while (downloadQueue.length) {
const job = downloadQueue.shift();
try {
let path;
if (job.type === "video")
path = await get_video(job.url, job.msg.id._serialized);
else
path = await get_audio(job.url, job.msg.id._serialized);
if (job.type === "video")
await enviarVideo(job.chatId, path);
else
await enviarAudio(job.chatId, path);
} catch (err) {
await client.sendMessage(
job.chatId,
botMsg(`❌ Erro ao baixar ${job.type}\n\`${err.message}\``)
);
}
}
processingQueue = false;
}
// ---------------- FIGURINHA ----------------
async function gerarSticker(msg) {
if (!msg.hasMedia) {
await msg.reply(botMsg("Envie uma imagem junto com o comando: `!figurinha`."));
return;
}
const media = await msg.downloadMedia();
const ext = media.mimetype.split("/")[1];
const input = `downloads/${msg.id._serialized}.${ext}`;
const output = `downloads/${msg.id._serialized}.webp`;
fs.writeFileSync(input, Buffer.from(media.data, "base64"));
await sharp(input)
.resize(512, 512, {
fit: "contain",
background: { r:0,g:0,b:0,alpha:0 }
})
.webp()
.toFile(output);
const data = fs.readFileSync(output);
const sticker = new MessageMedia(
"image/webp",
data.toString("base64"),
"sticker.webp"
);
const chat = await msg.getChat();
await client.sendMessage(
chat.id._serialized,
sticker,
{ sendMediaAsSticker: true }
);
fs.unlinkSync(input);
fs.unlinkSync(output);
}
// ---------------- COMANDOS ----------------
async function processarComando(msg, chat, chatId) {
const tokens = msg.body.trim().split(/\s+/);
try {
switch(tokens[0]) {
case "!many":
await chat.sendMessage(botMsg(
"Comandos:\n\n"+
"`!ping`\n"+
"`!video <link>`\n"+
"`!audio <link>`\n"+
"`!figurinha`\n"+
"`!adivinhação começar|parar`\n"+
"`!info <comando>`"
));
break;
case "!ping":
await msg.reply(botMsg("pong 🏓"));
break;
case "!video":
if (!tokens[1]) return;
await msg.reply(botMsg("⏳ Baixando vídeo..."));
enqueueDownload("video", tokens[1], msg, chatId);
break;
case "!audio":
if (!tokens[1]) return;
await msg.reply(botMsg("⏳ Baixando áudio..."));
enqueueDownload("audio", tokens[1], msg, chatId);
break;
case "!figurinha":
await gerarSticker(msg);
break;
case "!adivinhação":
if (!tokens[1]) {
await chat.sendMessage(botMsg(
"`!adivinhação começar`\n"+
"`!adivinhação parar`"
));
return;
}
if (tokens[1] === "começar") {
jogoAtivo = Math.floor(Math.random()*100)+1;
await chat.sendMessage(botMsg(
"Adivinhe o número de 1 a 100!"
));
}
if (tokens[1] === "parar") {
jogoAtivo = null;
await chat.sendMessage(botMsg("Jogo encerrado."));
}
break;
case "A":
if (!tokens[1]) {
msg.reply(botMsg("B!"));
}
break;
case "a":
if (!tokens[1]) {
msg.reply(botMsg("B!"));
}
break;
case "!info":
if (!tokens[1]) {
await chat.sendMessage(botMsg(
"Use:\n" +
"`!info <comando>`"
));
return;
}
switch(tokens[1]) {
case "ping":
await chat.sendMessage(botMsg(
"> `!ping`\nResponde pong."
));
break;
case "video":
await chat.sendMessage(botMsg(
"> `!video <link>`\nBaixa vídeo da internet.\n"
));
break;
case "audio":
await chat.sendMessage(botMsg(
"> `!audio <link>`\nBaixa áudio da internet.\n"
));
break;
case "figurinha":
await chat.sendMessage(botMsg(
"`!figurinha`\nTransforma imagem/GIF em sticker."
));
break;
default:
await chat.sendMessage(botMsg(
`❌ Comando '${tokens[1]}' não encontrado.`
));
}
break;
}
} catch(err) {
console.error(err);
await chat.sendMessage(
botMsg("Erro:\n`"+err.message+"`")
);
}
}
// ---------------- JOGO ----------------
async function processarJogo(msg, chat) {
if (!jogoAtivo) return;
const tentativa = msg.body.trim();
if (!/^\d+$/.test(tentativa)) return;
const num = parseInt(tentativa);
if (num === jogoAtivo) {
await msg.reply(botMsg(`Acertou! Número: ${jogoAtivo}`));
jogoAtivo = null;
}
else if (num > jogoAtivo) {
await chat.sendMessage(botMsg("Menor."));
}
else {
await chat.sendMessage(botMsg("Maior."));
}
}
client.initialize();

137
main.py
View File

@@ -1,137 +0,0 @@
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from webdriver_manager.chrome import ChromeDriverManager
import time
import pyperclip
import random
GRUPO = "𝑩𝒂𝒕𝒆 𝒑𝒂𝒑𝒐"
BOT_PREFIX = "🤖 *ManyBot:* "
PROFILE_DIR = "/home/syntax/whatsapp-profile"
CHECK_INTERVAL = 0.5
def iniciar_driver():
print("[DRIVER] Iniciando Chrome...")
opts = Options()
opts.add_argument(f"--user-data-dir={PROFILE_DIR}")
opts.add_argument("--profile-directory=Default")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
opts.add_argument("--disable-extensions")
opts.add_argument("--disable-gpu")
driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=opts)
driver.get("https://web.whatsapp.com")
wait = WebDriverWait(driver, 120)
print("[DRIVER] Aguardando QR Code ou login...")
wait.until(EC.presence_of_element_located((By.ID, "pane-side")))
print("[DRIVER] WhatsApp Web carregado.")
return driver, wait
def abrir_grupo(driver, wait):
print(f"[GRUPO] Procurando '{GRUPO}'...")
grupo_box = wait.until(EC.presence_of_element_located((By.XPATH, f'//span[@title="{GRUPO}"]')))
grupo_box.click()
print(f"[GRUPO] Aberto.")
def pegar_ultima_mensagem(driver):
msgs = driver.find_elements(By.CSS_SELECTOR, "[data-testid='selectable-text'] span")
if not msgs:
return None
return msgs[-1].text
def enviar_mensagem(driver, wait, texto):
print(f"[ENVIO] Enviando: '{texto}'")
caixa = wait.until(EC.element_to_be_clickable((
By.CSS_SELECTOR, "footer div[contenteditable='true'][role='textbox']"
)))
caixa.click()
pyperclip.copy(texto)
caixa.send_keys(Keys.CONTROL, 'v')
time.sleep(0.3)
caixa.send_keys(Keys.ENTER)
print("[ENVIO] Mensagem enviada.")
def bot_msg(texto):
return f"{BOT_PREFIX}\n{texto}"
# -----------------------------
driver, wait = iniciar_driver()
abrir_grupo(driver, wait)
ultima_mensagem = None
def jogo():
n = random.randint(1, 100)
print(f"[JOGO] Jogo iniciado! Número escolhido: {n}")
enviar_mensagem(driver, wait, bot_msg("Hora do jogo! Tentem adivinhar qual número de 1 a 100 eu estou pensando!"))
while True:
try:
tentativa = pegar_ultima_mensagem(driver)
if not tentativa or tentativa == ultima_mensagem:
time.sleep(CHECK_INTERVAL)
continue
print(f"[JOGO] Nova tentativa: '{tentativa}'")
time.sleep(CHECK_INTERVAL)
if tentativa.isdigit():
num = int(tentativa)
if num == n:
enviar_mensagem(driver, wait, bot_msg(f"Parabéns! Você acertou!! O número era: {n}"))
break
elif num > n:
enviar_mensagem(driver, wait, bot_msg(f"Quase! Um pouco menor. Sua resposta: {num}"))
elif num < n:
enviar_mensagem(driver, wait, bot_msg(f"Quase! Um pouco maior. Sua resposta: {num}"))
except Exception as e:
print(f"[ERRO] {type(e).__name__}: {e}")
time.sleep(1)
def processar_comando(texto):
tokens = texto.split()
if tokens[0] == "!many":
if len(tokens) == 1: # se só tiver "!many"
return bot_msg(
"E aí?! Aqui está a lista de todos os meus comandos:\n"
"- `!many ping` -> testa se estou funcionando\n"
"- `!many jogo` -> jogo de adivinhação\n"
"E ai, vai querer qual? 😄"
)
elif tokens[1] == "ping":
return bot_msg("pong 🏓")
elif tokens[1] == "jogo":
jogo()
return None
while True:
try:
texto = pegar_ultima_mensagem(driver)
if not texto or texto == ultima_mensagem:
time.sleep(CHECK_INTERVAL)
continue
print(f"[MSG] Nova mensagem: '{texto}'")
ultima_mensagem = texto
time.sleep(CHECK_INTERVAL)
resposta = processar_comando(texto)
if resposta:
enviar_mensagem(driver, wait, resposta)
except Exception as e:
print(f"[ERRO] {type(e).__name__}: {e}")
time.sleep(1)

2682
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

10
package.json Executable file
View File

@@ -0,0 +1,10 @@
{
"name": "whatsapp-bot",
"version": "1.1.0",
"type": "module",
"dependencies": {
"qrcode-terminal": "^0.12.0",
"sharp": "^0.34.5",
"whatsapp-web.js": "^1.24.0"
}
}

7
teste.js Normal file
View File

@@ -0,0 +1,7 @@
const str = "<22>»⃟⃫⚡<EFB88F><E283A5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>";
// percorre cada caractere
for (const char of str) {
const code = char.codePointAt(0); // pega o ponto de código Unicode
process.stdout.write(`${char} (U+${code.toString(16).toUpperCase()})\n`);
}

BIN
yt-dlp Executable file

Binary file not shown.