import puppeteer from 'puppeteer-core'; import fetch from 'node-fetch'; import { app, BrowserWindow, Notification, dialog } from 'electron'; import pie from 'puppeteer-in-electron'; import os from 'os'; import path from 'path'; import fs from 'fs'; // Al inicio del archivo, después de los imports if (process.defaultApp) { if (process.argv.length >= 2) { app.setAsDefaultProtocolClient('shopee', process.execPath, [path.resolve(process.argv[1])]) } } else { app.setAsDefaultProtocolClient('shopee') } // Configuración const CONFIG = { URL: 'https://rpa.mind.brm.co/app', URL_LOG: 'https://rpa.mind.brm.co/log', APP_CODE: 'SHP030', USER: os.userInfo().username, // USER: '45957768', // USER: '1019017231', URL_CONFIRM: 'https://cs.shopee.com.mx/portal/inhouse/', TIMEOUT: 10000, WAIT_TIME: 5000, ICON: app.getAppPath('icon.ico') }; console.log(">> Iniciando configuración de logs.."); const logsDir = 'C:/Shopee'; if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }); } const logFilePath = path.join(logsDir, 'console-log.txt'); // Limpia el archivo de logs al inicio await fs.promises.writeFile(logFilePath, '', 'utf-8'); // Helper para escribir logs en el archivo const writeLogToFile = (message) => { const timestamp = new Date().toISOString(); fs.appendFileSync(logFilePath, `[${timestamp}] ${message}\n`); }; // Sobrescribe métodos de console ['log', 'info', 'warn', 'error'].forEach((method) => { const originalMethod = console[method]; console[method] = (...args) => { const message = args .map((arg) => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : arg)) .join(' '); writeLogToFile(message); originalMethod.apply(console, args); // Mantén la salida en la consola }; }); // Inicialización de puppeteer (async () => { await pie.initialize(app); })(); async function queryEndpoint(user) { try { const response = await fetch(CONFIG.URL, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ user: user, code: CONFIG.APP_CODE }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } // First get the response as text const textResponse = await response.text(); // Check if it's the invalid parameter message if (textResponse === "Invalid parameter") { console.log('>> El usuario no tiene acceso a Shopee'); // Modificar todas las notificaciones para incluir el ícono // En queryEndpoint: await dialog.showErrorBox( 'RPA Shopee', 'El usuario no tiene acceso a Shopee' ); // Add delay before closing to ensure notification is shown setTimeout(() => { app.quit(); }, 3000); return null; } // If it's not "Invalid parameter", try to parse as JSON try { return JSON.parse(textResponse); } catch (parseError) { console.error('>> Error parsing response:', parseError); throw parseError; } } catch (error) { console.error('>> Error querying endpoint:', error); throw error; } } async function fillForm(url_app, user, password, xpath_user, xpath_pass) { // Agregar notificación al inicio new Notification({ title: 'RPA Shopee', body: 'Abriendo Shopee' }).show(); console.log('>> Iniciando proceso de automatizacion'); try { console.log('>> Creando ventana de Electron'); const window = new BrowserWindow({ width: 1200, height: 800, webPreferences: { nodeIntegration: true, contextIsolation: false }, // show: process.argv.includes('--dev'), // Only show window if --dev parameter is present show: true, autoHideMenuBar: true // Ocultar menú por defecto }); if (process.argv.includes('--dev')) { window.openDevTools(); } // Agregar botones de navegación // window.webContents.on('did-finish-load', () => { // window.webContents.executeJavaScript(` // (function() { // const nav = document.createElement('div'); // nav.style.cssText = 'position: fixed; top: 0; left: 0; z-index: 9999; background: #fff; padding: 5px; border-bottom: 1px solid #ddd;'; // nav.innerHTML = '' + // '' + // ''; // document.body.appendChild(nav); // })(); // `, true); // Add the 'true' parameter to use the trusted types // }); console.log('>> Conectando navegador puppeteer'); const browser = await pie.connect(app, puppeteer); const page = await pie.getPage(browser, window); console.log('>> Navegando a:', url_app); await page.goto(url_app); console.log('>> Pagina cargada correctamente'); console.log('>> Buscando boton de login'); await page.waitForSelector('.login-button'); await page.click('.login-button'); console.log('>> Boton de login clickeado'); const isExistingAccount = await checkExistingAccount(page, user); if (!isExistingAccount && !await checkLoginStatus(page)) { console.log('>> No se detectó sesión activa, iniciando login manual'); await performLogin(page, user, password, xpath_user, xpath_pass); } } catch (error) { console.error('>> Error en el proceso de automatizacion:', error); throw error; } } async function checkExistingAccount(page, user) { console.log('>> Verificando cuenta existente'); try { console.log('>> Buscando selector de cuentas'); await page.waitForSelector('::-p-xpath(//*[contains(text(), "Elige una cuenta")])', { timeout: CONFIG.TIMEOUT }); console.log('>> Buscando cuenta del usuario:', user); // Convert username to lowercase before searching user = user.toLowerCase(); const account_active = await page.waitForSelector('::-p-xpath(//*[contains(text(), "'+user+'")])', { timeout: CONFIG.TIMEOUT }); await account_active.click(); console.log('>> Cuenta seleccionada'); try { console.log('>> Buscando boton Allow'); await new Promise(resolve => setTimeout(resolve, CONFIG.WAIT_TIME)); const input_continue = await page.waitForSelector('::-p-xpath(//button[contains(text(), "Allow")])', { timeout: CONFIG.TIMEOUT }); await input_continue.click(); console.log('>> Boton Allow clickeado'); // Agregar verificación de login console.log('>> Verificando login después de Allow'); const loginStatus = await checkLoginStatus(page); if (loginStatus) { console.log('>> Login exitoso con cuenta existente'); return true; } console.log('>> Login no exitoso con cuenta existente'); } catch (error) { console.log('>> No se encontro boton Allow'); } return true; } catch (error) { console.log('>> No se encontraron cuentas existentes'); return false; } } // Add this new function for sending logs // Agregar esta función para obtener IP y MAC async function getNetworkInfo() { const interfaces = os.networkInterfaces(); let ipAddress = ''; let macAddress = ''; // Buscar en todas las interfaces de red for (const name of Object.keys(interfaces)) { for (const net of interfaces[name]) { // Buscar IPv4 y no localhost if (net.family === 'IPv4' && !net.internal) { ipAddress = net.address; macAddress = net.mac; break; } } if (ipAddress && macAddress) break; } return { ip: ipAddress, mac: macAddress }; } // Modificar la función sendLoginLog async function sendLoginLog() { try { const networkInfo = await getNetworkInfo(); const response = await fetch(CONFIG.URL_LOG, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ user: CONFIG.USER, ip: networkInfo.ip, mac: networkInfo.mac, code: CONFIG.APP_CODE, host: os.hostname(), type: 1, status: 1 }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } console.log('>> Log enviado exitosamente'); } catch (error) { console.error('>> Error enviando log:', error); } } // Modify checkLoginStatus function async function checkLoginStatus(page) { console.log('>> Verificando estado de login'); const startTime = Date.now(); while (Date.now() - startTime < CONFIG.TIMEOUT) { const currentUrl = page.url(); console.log('>> URL actual:', currentUrl); if (currentUrl.includes(CONFIG.URL_CONFIRM)) { console.log('>> Usuario logueado exitosamente'); new Notification({ title: 'RPA Shopee', body: 'Usuario logueado exitosamente' }).show(); // Mostrar la ventana después del login exitoso const windows = BrowserWindow.getAllWindows(); if (windows.length > 0) { windows[0].maximize(); windows[0].show(); windows[0].focus(); } // Add log sending after successful login await sendLoginLog(); return true; } await new Promise(resolve => setTimeout(resolve, 500)); } console.log('>> Timeout en verificación de login'); return false; } async function performLogin(page, user, password, xpath_user, xpath_pass) { console.log('>> Iniciando proceso de login manual'); console.log('>> Ingresando usuario'); const input_user = await page.waitForSelector('::-p-xpath(' + xpath_user + ')'); await input_user.type(user); await input_user.press('Enter'); console.log('>> Usuario ingresado'); await new Promise(resolve => setTimeout(resolve, CONFIG.WAIT_TIME)); console.log('>> Ingresando contraseña'); const input_pass = await page.waitForSelector('::-p-xpath(' + xpath_pass + ')'); await input_pass.type(password); await input_pass.press('Enter'); console.log('>> Contraseña ingresada'); try { console.log('>> Buscando botón Allow'); const input_continue = await page.waitForSelector('::-p-xpath(//button[contains(text(), "Allow")])'); await input_continue.click(); console.log('>> Botón Allow clickeado'); } catch (error) { console.log('>> No se encontró botón Allow'); } return await checkLoginStatus(page); } // Modificar cerca del inicio del archivo, después de los imports let isOpenedFromURL = false; console.log('Url fuera logica: '+process.argv); // Detectar si se abrió desde URL por argumentos de línea de comandos if (process.argv.includes('shopee://open') || process.argv.some(arg => arg.includes('shopee://open')) || process.argv.includes('--dev')) { isOpenedFromURL = true; } // Modificar el manejo del protocolo app.on('open-url', (event, url) => { console.log('Url apertura: '+url); event.preventDefault(); isOpenedFromURL = true; if (url.includes('shopee://open')) { if (BrowserWindow.getAllWindows().length > 0) { BrowserWindow.getAllWindows()[0].focus(); } } }); // Modificar el main process para manejar segundo inicio const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { app.quit(); } else { app.on('second-instance', (event, commandLine, workingDirectory) => { // Si alguien trata de abrir otra instancia, enfocamos la ventana existente if (BrowserWindow.getAllWindows().length > 0) { const window_open = BrowserWindow.getAllWindows()[0]; if (window_open.isMinimized()) window_open.restore(); window_open.focus(); } }); // El resto de tu código del main process se mantiene igual app.on('ready', async () => { console.log('Aplicacion lista'); console.log('>> Método de apertura:', isOpenedFromURL ? 'URL Protocol' : 'Manual'); if (!isOpenedFromURL) { await dialog.showErrorBox( 'Error de acceso', 'Esta aplicación solo puede ser iniciada desde OKAN' ); app.quit(); return; } //Mostrar alerta del método de apertura // new Notification({ // title: 'RPA Shopee', // body: `Aplicación iniciada ${isOpenedFromURL ? 'desde URL' : 'manualmente'}`, // icon: CONFIG.ICON // }).show(); try { console.log('>> Obteniendo info del usuario: '+CONFIG.USER); const data_user = await queryEndpoint(CONFIG.USER); if (data_user.App) { console.log('Se obtuvo la info del usuario'); await fillForm(data_user.App, data_user.User, data_user.Password, data_user.Xpath_user, data_user.Xpath_pass); } else { console.log('No se pudo obtener la info del usuario'); } } catch (error) { console.error('Error in main process:', error); } }); } // Prevent app from closing when all windows are closed app.on('window-all-closed', () => { if (process.platform !== 'darwin') { app.quit(); } });