From 197757b073845c1a759101fd8611d425c9f63203 Mon Sep 17 00:00:00 2001 From: zyperch Date: Tue, 2 Jun 2026 20:56:00 -0300 Subject: [PATCH] refact(webhook): corrige integracao do webhook e trata erros de cookies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Migra arquivos de webhook de TS para JS - Corrige imports quebrados e destructuring de SendMessageWebhook e ErrorType - Trata arquivo cookies.json vazio/invalido com try/catch - Corrige digitação de porcentagemPromo no ProductEntity - Envia eventos de sucesso para busca_produto e erros para error_busca --- package-lock.json | 69 +++++++++++++++++++++ package.json | 10 +-- src/application/ProcessPromotionsUseCase.js | 5 ++ src/domain/ProductEntity.js | 4 +- src/infrastructure/AffiliateGenerator.js | 28 +++++++-- src/infrastructure/ApiClient.js | 3 + src/infrastructure/MercadoLivreScraper.js | 12 ++-- webhook/ErrorType.js | 7 +++ webhook/webhook.js | 18 ++++++ 9 files changed, 140 insertions(+), 16 deletions(-) create mode 100644 webhook/ErrorType.js create mode 100644 webhook/webhook.js diff --git a/package-lock.json b/package-lock.json index f755667..4d7284d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "ISC", "dependencies": { "axios": "^1.16.1", + "discord-webhook-node": "^1.1.8", "puppeteer": "^25.0.4", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-stealth": "^2.11.2" @@ -441,6 +442,32 @@ "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==", "license": "BSD-3-Clause" }, + "node_modules/discord-webhook-node": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/discord-webhook-node/-/discord-webhook-node-1.1.8.tgz", + "integrity": "sha512-3u0rrwywwYGc6HrgYirN/9gkBYqmdpvReyQjapoXARAHi0P0fIyf3W5tS5i3U3cc7e44E+e7dIHYUeec7yWaug==", + "license": "MIT", + "dependencies": { + "form-data": "^3.0.0", + "node-fetch": "^2.6.0" + } + }, + "node_modules/discord-webhook-node/node_modules/form-data": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.4.tgz", + "integrity": "sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1017,6 +1044,26 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1437,6 +1484,12 @@ "b4a": "^1.6.4" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/typed-query-selector": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz", @@ -1458,6 +1511,22 @@ "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==", "license": "Apache-2.0" }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 2c4d0a3..9d40218 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,21 @@ { "dependencies": { "axios": "^1.16.1", + "discord-webhook-node": "^1.1.8", "puppeteer": "^25.0.4", "puppeteer-extra": "^3.3.6", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "name": "codevscodesjs", - "version": "1.0.0", + "version": "1.1.0", "description": "", - "main": "index.js", + "main": "src/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node src/index.js" }, "keywords": [], "author": "", "license": "ISC", "type": "commonjs" -} +} \ No newline at end of file diff --git a/src/application/ProcessPromotionsUseCase.js b/src/application/ProcessPromotionsUseCase.js index 70e3302..78a453e 100644 --- a/src/application/ProcessPromotionsUseCase.js +++ b/src/application/ProcessPromotionsUseCase.js @@ -1,3 +1,5 @@ +const ErrorType = require('../../webhook/ErrorType'); +const SendMessageWebhook = require('../../webhook/webhook'); const ProductEntity = require('../domain/ProductEntity'); class ProcessPromotionsUseCase { @@ -24,7 +26,10 @@ class ProcessPromotionsUseCase { } } + SendMessageWebhook('Processo finalizado!', `${enviados} produtos foram enviados para a API.`, ErrorType.busca_produto); + console.log(`Processo finalizado! ${enviados} produtos foram enviados para a API.`); + } } diff --git a/src/domain/ProductEntity.js b/src/domain/ProductEntity.js index 2d7996f..3e3c5f9 100644 --- a/src/domain/ProductEntity.js +++ b/src/domain/ProductEntity.js @@ -1,9 +1,9 @@ class ProductEntity { - constructor({ nomeProduto, precoOriginal, precoPromocao, porcetagemPromo, imagem, linkProduto }) { + constructor({ nomeProduto, precoOriginal, precoPromocao, porcentagemPromo, imagem, linkProduto }) { this.nomeProduto = nomeProduto || 'sem título'; this.precoOriginal = precoOriginal || 0; this.precoPromocao = precoPromocao || 0; - this.porcetagemPromo = porcetagemPromo || 0; + this.porcentagemPromo = porcentagemPromo || 0; this.imagem = imagem || ''; this.linkProduto = linkProduto || ''; this.plataforma = 'Mercado Livre'; // Regra de negócio fixa para este robô diff --git a/src/infrastructure/AffiliateGenerator.js b/src/infrastructure/AffiliateGenerator.js index 0e6dd46..0dd814e 100644 --- a/src/infrastructure/AffiliateGenerator.js +++ b/src/infrastructure/AffiliateGenerator.js @@ -2,21 +2,23 @@ const puppeteer = require('puppeteer-extra'); const StealthPlugin = require('puppeteer-extra-plugin-stealth'); puppeteer.use(StealthPlugin()); const fs = require('fs'); +const SendMessageWebhook = require('../../webhook/webhook'); +const ErrorType = require('../../webhook/ErrorType'); class AffiliateGenerator { async converterLista(produtosBrutos) { console.log('\nIniciando conversão para links de afiliado (Modo Docker/Invisível)...'); - const browser = await puppeteer.launch({ - headless: false, + const browser = await puppeteer.launch({ + headless: false, args: [ - '--no-sandbox', + '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-blink-features=AutomationControlled', '--disable-gpu', '--window-size=1920,1080' - ] + ] }); const page = await browser.newPage(); @@ -25,8 +27,19 @@ class AffiliateGenerator { // 2. Injeta os cookies de autorização if (fs.existsSync('./cookies.json')) { - const cookies = JSON.parse(fs.readFileSync('./cookies.json', 'utf8')); - await page.setCookie(...cookies); + try { + const rawCookies = fs.readFileSync('./cookies.json', 'utf8').trim(); + if (rawCookies) { + const cookies = JSON.parse(rawCookies); + await page.setCookie(...cookies); + console.log('✅ Cookies injetados com sucesso.'); + } else { + console.log('⚠️ O arquivo cookies.json está vazio.'); + } + } catch (erro) { + console.error('❌ Erro ao ler ou analisar o cookies.json:', erro.message); + SendMessageWebhook('Erro nos Cookies!', `Falha ao processar cookies.json: ${erro.message}`, ErrorType.error_busca); + } } // 3. Acessa o gerador com a sessão já "aquecida" @@ -35,6 +48,7 @@ class AffiliateGenerator { try { await page.waitForSelector('textarea#url-0', { timeout: 15000 }); } catch (erro) { + SendMessageWebhook('Erro crítico!', 'Bloqueio detectado ou cookies inválidos.', ErrorType.error_busca); console.log('\n❌ ERRO CRÍTICO: Bloqueio detectado ou cookies inválidos.'); await browser.close(); return []; // Retorna array vazio para não quebrar a API com Erro 500 @@ -65,9 +79,11 @@ class AffiliateGenerator { produtosConvertidos.push(produto); // Só adiciona se a conversão foi um sucesso console.log(`✅ Convertido: ${produto.nomeProduto.substring(0, 30)}...`); } else { + SendMessageWebhook('Erro na conversão!', `Link inválido ou não gerado para o produto: ${produto.nomeProduto}`, ErrorType.error_busca); throw new Error("Link inválido ou não gerado."); } } catch (error) { + SendMessageWebhook('Erro na conversão!', `Erro ao gerar link de afiliado para o produto: ${produto.nomeProduto}`, ErrorType.error_busca); console.log(`⚠️ Descartado (Falha na conversão): ${produto.nomeProduto.substring(0, 30)}...`); } } diff --git a/src/infrastructure/ApiClient.js b/src/infrastructure/ApiClient.js index 7b1e4b2..9a8a461 100644 --- a/src/infrastructure/ApiClient.js +++ b/src/infrastructure/ApiClient.js @@ -1,4 +1,6 @@ const axios = require('axios'); +const SendMessageWebhook = require('../../webhook/webhook'); +const ErrorType = require('../../webhook/ErrorType'); class ApiClient { constructor(baseURL) { @@ -19,6 +21,7 @@ class ApiClient { await axios.post(this.urlDestino, payload); console.log(`✅ Enviado com sucesso: ${produto.nomeProduto}`); } catch (erro) { + SendMessageWebhook('Erro ao enviar!', `Erro ao enviar o produto para a API: ${erro.message}`, ErrorType.error_busca); console.log(`❌ Erro ao enviar ${produto.nomeProduto} | Motivo: ${erro.message}`); } } diff --git a/src/infrastructure/MercadoLivreScraper.js b/src/infrastructure/MercadoLivreScraper.js index 3f21164..2dd8a08 100644 --- a/src/infrastructure/MercadoLivreScraper.js +++ b/src/infrastructure/MercadoLivreScraper.js @@ -1,5 +1,7 @@ const puppeteer = require('puppeteer-extra'); const StealthPlugin = require('puppeteer-extra-plugin-stealth'); +const SendMessageWebhook = require('../../webhook/webhook'); +const ErrorType = require('../../webhook/ErrorType'); // Ativa o modo de camuflagem para evitar detecção puppeteer.use(StealthPlugin()); @@ -8,13 +10,13 @@ class MercadoLivreScraper { async extrairDados() { console.log('Iniciando extração de promoções do Mercado Livre...'); - const browser = await puppeteer.launch({ - headless: true, + const browser = await puppeteer.launch({ + headless: true, args: [ - '--no-sandbox', + '--no-sandbox', '--disable-setuid-sandbox', '--disable-blink-features=AutomationControlled' - ] + ] }); const page = await browser.newPage(); @@ -68,6 +70,7 @@ class MercadoLivreScraper { return resultados; }); + SendMessageWebhook('Processo finalizado!', `${produtos.length} produtos foram encontrados.`, ErrorType.busca_produto); console.log(`\n--- DIAGNÓSTICO FINAL ---`); console.log(`O robô enxergou ${produtos.length} cartões na página em modo invisível.`); console.log(`-------------------------\n`); @@ -76,6 +79,7 @@ class MercadoLivreScraper { return produtos; } catch (error) { + SendMessageWebhook('Erro durante a extração!', `Erro durante a extração: ${error.message}`, ErrorType.error_busca); console.error('Erro durante a extração:', error.message); await browser.close(); return []; diff --git a/webhook/ErrorType.js b/webhook/ErrorType.js new file mode 100644 index 0000000..2104fca --- /dev/null +++ b/webhook/ErrorType.js @@ -0,0 +1,7 @@ +const ErrorType = { + busca_produto: "https://discord.com/api/webhooks/1511503190489825280/JmeEKSpkUtBzhBrUKLE8aOp0W_8aUVpYUktpWonHuynYFOJgKiJp31MRreJpjmwLv868", + error_busca: "https://discord.com/api/webhooks/1510999432039239832/Vq82_mb0ThlM6Py6KbAF-QPV4r_SPEE5Tvk5vLJadSYdIlKAVUMUdqT_7jcBJV6u2ddl" + +} + +module.exports = ErrorType; \ No newline at end of file diff --git a/webhook/webhook.js b/webhook/webhook.js new file mode 100644 index 0000000..ac85bc8 --- /dev/null +++ b/webhook/webhook.js @@ -0,0 +1,18 @@ +const { Webhook } = require('discord-webhook-node'); + + +function SendMessageWebhook(title, description, webhookUrl) { + let url = webhookUrl; + let content = `**${title}**\n${description}`; + + if (!url) { + // Fallback for 2 arguments: SendMessageWebhook(content, url) + url = description; + content = title; + } + + const hook = new Webhook(url); + hook.send(content); +} + +module.exports = SendMessageWebhook; \ No newline at end of file