Files
RPAShopee/main.js
T

406 lines
14 KiB
JavaScript

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 = '<button onclick="history.back()" style="margin-right: 5px;">⬅️</button>' +
// '<button onclick="history.forward()" style="margin-right: 5px;">➡️</button>' +
// '<button onclick="location.reload()" style="margin-right: 5px;">🔄</button>';
// 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();
}
});