[repo] reorganization

This commit is contained in:
synt-xerror
2026-03-16 18:32:57 -03:00
parent 04765868db
commit e60c5819e2
34 changed files with 1023 additions and 829 deletions

36
src/logger/formatter.js Normal file
View File

@@ -0,0 +1,36 @@
// ── Paleta ANSI ──────────────────────────────────────────────
export const c = {
reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
green: "\x1b[32m", yellow: "\x1b[33m", cyan: "\x1b[36m",
red: "\x1b[31m", gray: "\x1b[90m", white: "\x1b[37m",
blue: "\x1b[34m", magenta: "\x1b[35m",
};
export const SEP = `${c.gray}${"─".repeat(52)}${c.reset}`;
export const now = () =>
new Date().toLocaleString("pt-BR", { dateStyle: "short", timeStyle: "medium" });
export const formatType = (type) => ({
sticker: `${c.magenta}sticker${c.reset}`,
image: `${c.cyan}imagem${c.reset}`,
video: `${c.cyan}vídeo${c.reset}`,
audio: `${c.cyan}áudio${c.reset}`,
ptt: `${c.cyan}áudio${c.reset}`,
document: `${c.cyan}arquivo${c.reset}`,
chat: `${c.white}texto${c.reset}`,
}[type] ?? `${c.gray}${type}${c.reset}`);
export const formatContext = (chatName, isGroup) =>
isGroup
? `${c.bold}${chatName}${c.reset} ${c.dim}(grupo)${c.reset}`
: `${c.bold}${chatName}${c.reset} ${c.dim}(privado)${c.reset}`;
export const formatBody = (body, isCommand) =>
body?.trim()
? `${isCommand ? c.yellow : c.green}"${body.length > 200 ? body.slice(0, 200) + "..." : body}"${c.reset}`
: `${c.dim}<mídia>${c.reset}`;
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} ↩ Msg: ${c.reset}${c.dim}${quotedPreview}${c.reset}`;

53
src/logger/logger.js Normal file
View File

@@ -0,0 +1,53 @@
import {
c, SEP, now,
formatType, formatContext, formatBody, formatReply,
} from "./formatter.js";
/**
* Logger central do ManyBot.
* Cada método lida apenas com saída — sem lógica de negócio ou I/O externo.
*/
export const logger = {
info: (...a) => console.log(`${SEP}\n${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),
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),
/**
* Loga uma mensagem recebida a partir de um contexto já resolvido.
* @param {import("./messageContext.js").MessageContext} ctx
*/
msg(ctx) {
const { chatName, isGroup, senderName, senderNumber, type, body, isCommand, quoted } = ctx;
const typeLabel = formatType(type);
const context = formatContext(chatName, isGroup);
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 = "") =>
console.log(
`${c.gray}[${now()}]${c.reset} ${c.yellow}CMD ${c.reset}` +
`${c.bold}${cmd}${c.reset}` +
(extra ? ` ${c.dim}${extra}${c.reset}` : "")
),
done: (cmd, detail = "") =>
console.log(
`${c.gray}[${now()}]${c.reset} ${c.green}DONE ${c.reset}` +
`${c.dim}${cmd}${c.reset}` +
(detail ? `${detail}` : "")
),
};

View File

@@ -0,0 +1,83 @@
import client from "../client/whatsappClient.js";
/**
* Extrai o número limpo de uma mensagem.
* @param {import("whatsapp-web.js").Message} msg
* @returns {Promise<string>}
*/
export async function getNumber(msg) {
if (msg.fromMe) return String(msg.from).split("@")[0];
const contact = await msg.getContact();
return contact.number;
}
/**
* Monta o contexto completo de uma mensagem para logging.
* Resolve contato, quoted message e metadados do chat.
*
* @param {import("whatsapp-web.js").Message} msg
* @param {import("whatsapp-web.js").Chat} chat
* @param {string} botPrefix
* @returns {Promise<MessageContext>}
*
* @typedef {Object} MessageContext
* @property {string} chatName
* @property {string} chatId
* @property {boolean} isGroup
* @property {string} senderName
* @property {string} senderNumber
* @property {string} type
* @property {string} body
* @property {boolean} isCommand
* @property {{ name: string, number: string, preview: string } | null} quoted
*/
export async function buildMessageContext(msg, chat, botPrefix) {
const chatId = chat.id._serialized;
const isGroup = /@g\.us$/.test(chatId);
const number = await getNumber(msg);
const name = msg._data?.notifyName || String(msg.from).replace(/(:\d+)?@.*$/, "");
const quoted = await resolveQuotedMessage(msg);
return {
chatName: chat.name || chat.id.user,
chatId,
isGroup,
senderName: name,
senderNumber: number,
type: msg?.type || "text",
body: msg.body,
isCommand: !!msg.body?.trimStart().startsWith(botPrefix),
quoted,
};
}
/**
* Resolve os dados da mensagem citada, se existir.
* Retorna null em caso de erro ou ausência.
*
* @param {import("whatsapp-web.js").Message} msg
* @returns {Promise<{ name: string, number: string, preview: string } | null>}
*/
async function resolveQuotedMessage(msg) {
if (!msg?.hasQuotedMsg) return null;
try {
const quoted = await msg.getQuotedMessage();
const quotedNumber = String(quoted.from).split("@")[0];
let quotedName = quotedNumber;
try {
const contact = await client.getContactById(quoted.from);
quotedName = contact?.pushname || contact?.formattedName || quotedNumber;
} catch { /* contato não encontrado — usa o número */ }
const quotedPreview = quoted.body?.trim()
? `"${quoted.body.length > 80 ? quoted.body.slice(0, 80) + "…" : quoted.body}"`
: `<${quoted.type}>`;
return { name: quotedName, number: quotedNumber, preview: quotedPreview };
} catch {
return null;
}
}