34849-vm/includes/shalom_api_lookup.js
2026-07-18 19:42:46 +00:00

290 lines
8.0 KiB
JavaScript

#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const puppeteer = require('puppeteer');
const SHALOM_SECRET = '.Ov3rsku112024l4r43l.';
const SHALOM_ENCRYPTION_KEY_B64 = 'uQn/bQ94PXBEfId70zjN+VE1hSU7kh9VBXTOUd68Ssc=';
const SHALOM_TRACKING_HOME = 'https://shalom.com.pe/rastrea';
const SHALOM_SEARCH_URL = 'https://serviceswebapi.shalomcontrol.com/api/v1/web/rastrea/buscar';
const SHALOM_STATUS_URL = 'https://serviceswebapi.shalomcontrol.com/api/v1/web/rastrea/estados';
function detectChromeExecutable() {
const candidates = [];
const cacheRoot = '/home/ubuntu/.cache/puppeteer/chrome';
try {
if (fs.existsSync(cacheRoot)) {
const versions = fs.readdirSync(cacheRoot).sort().reverse();
for (const version of versions) {
candidates.push(path.join(cacheRoot, version, 'chrome-linux64', 'chrome'));
}
}
} catch (error) {
// Ignoramos y seguimos con rutas fijas.
}
if (process.env.PUPPETEER_EXECUTABLE_PATH) {
candidates.unshift(process.env.PUPPETEER_EXECUTABLE_PATH);
}
candidates.push(
'/usr/bin/google-chrome-stable',
'/usr/bin/google-chrome',
'/usr/bin/chromium',
'/usr/bin/chromium-browser'
);
return candidates.find((candidate) => candidate && fs.existsSync(candidate)) || null;
}
function uuidv4() {
const bytes = crypto.randomBytes(16);
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = bytes.toString('hex');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
function buildAuthorizationHeader() {
const token = `web-${uuidv4()}`;
const expiresAt = Math.floor(Date.now() / 1000) + 300;
const payload = `${token}@${expiresAt}`;
const signature = crypto.createHmac('sha256', SHALOM_SECRET).update(payload).digest('hex');
return `Bearer ${payload}@${signature}`;
}
function decryptShalomPayload(payload) {
if (!payload || payload.encrypted !== true || !payload.data) {
return payload;
}
const key = Buffer.from(SHALOM_ENCRYPTION_KEY_B64, 'base64');
if (key.length !== 32) {
throw new Error('No se pudo preparar la clave de descifrado.');
}
const cipherBuffer = Buffer.from(String(payload.data), 'base64');
if (cipherBuffer.length <= 16) {
throw new Error('La respuesta cifrada de Shalom es inválida.');
}
const iv = cipherBuffer.subarray(0, 16);
const ciphertext = cipherBuffer.subarray(16);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
const plainText = Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString('utf8');
try {
return JSON.parse(plainText);
} catch (error) {
return plainText;
}
}
async function obtainRecaptchaToken(page) {
try {
await page.goto(SHALOM_TRACKING_HOME, {
waitUntil: 'domcontentloaded',
timeout: 60000,
});
} catch (error) {
// La página puede reintentar/cargar de nuevo por el limpiador de service workers.
}
await page.waitForFunction(() => window.RECAPTCHA_SITE_KEY && window.grecaptcha, {
timeout: 60000,
});
return page.evaluate(async () => {
await new Promise((resolve) => window.grecaptcha.ready(resolve));
return window.grecaptcha.execute(window.RECAPTCHA_SITE_KEY, { action: 'rastrea_buscar' });
});
}
async function postShalomApi(url, fields) {
const form = new FormData();
for (const [key, value] of Object.entries(fields)) {
if (value === undefined || value === null) continue;
form.append(key, String(value));
}
const response = await fetch(url, {
method: 'POST',
body: form,
headers: {
Accept: 'application/json',
Authorization: buildAuthorizationHeader(),
Origin: 'https://shalom.com.pe',
Referer: 'https://shalom.com.pe/',
'X-Requested-With': 'XMLHttpRequest',
},
});
const rawText = await response.text();
let wrappedPayload = null;
try {
wrappedPayload = JSON.parse(rawText);
} catch (error) {
return {
success: false,
error: `Respuesta no JSON de Shalom (${response.status}).`,
details: rawText.trim() ? rawText.slice(0, 1200) : 'Sin contenido útil.',
};
}
let plainPayload = wrappedPayload;
try {
plainPayload = decryptShalomPayload(wrappedPayload);
} catch (error) {
return {
success: false,
error: `No se pudo descifrar la respuesta de Shalom (${response.status}).`,
details: error.message,
};
}
if (response.status >= 400) {
const message = plainPayload && typeof plainPayload === 'object'
? String(plainPayload.message || plainPayload.error || '').trim()
: '';
return {
success: false,
error: message || `Shalom respondió HTTP ${response.status}.`,
details: plainPayload,
};
}
if (!plainPayload || typeof plainPayload !== 'object') {
return {
success: false,
error: 'Shalom devolvió una respuesta vacía o inválida.',
details: plainPayload,
};
}
if (plainPayload.success === false) {
return {
success: false,
error: String(plainPayload.message || plainPayload.error || 'Shalom rechazó la solicitud.'),
details: plainPayload,
};
}
return {
success: true,
data: plainPayload,
};
}
function printJson(payload) {
console.log(JSON.stringify(payload));
}
async function main() {
const orderNumber = String(process.argv[2] || '').trim();
const orderCode = String(process.argv[3] || '').trim();
if (!orderNumber || !orderCode) {
printJson({
success: false,
error: 'Número de orden y código de orden son requeridos.',
});
process.exit(1);
}
let browser = null;
try {
const executablePath = detectChromeExecutable();
if (!executablePath) {
printJson({
success: false,
error: 'No se encontró un navegador Chromium compatible para consultar Shalom.',
});
process.exit(2);
}
browser = await puppeteer.launch({
headless: 'new',
executablePath,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-features=SSLCommonNameMismatchHandling',
],
});
const page = await browser.newPage();
await page.setViewport({ width: 1280, height: 900 });
await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36');
const recaptchaToken = await obtainRecaptchaToken(page);
const searchResult = await postShalomApi(SHALOM_SEARCH_URL, {
numero: orderNumber,
codigo: orderCode,
ose_id: '',
recaptcha_token: recaptchaToken,
});
if (!searchResult.success) {
printJson(searchResult);
process.exit(3);
}
const searchPayload = searchResult.data;
const oseId = searchPayload && searchPayload.data ? searchPayload.data.ose_id : null;
if (!oseId) {
printJson({
success: false,
error: 'La búsqueda de Shalom no devolvió un N° interno válido.',
details: searchPayload,
});
process.exit(4);
}
const statusResult = await postShalomApi(SHALOM_STATUS_URL, {
ose_id: String(oseId),
});
if (!statusResult.success) {
printJson(statusResult);
process.exit(5);
}
const statusPayload = statusResult.data;
const trackingUrl = `${SHALOM_TRACKING_HOME}/${encodeURIComponent(String(oseId))}/${encodeURIComponent(orderCode)}`;
printJson({
success: true,
source: 'shalom_search_api',
search: searchPayload,
status: statusPayload,
resolved: {
ose_id: oseId,
tracking_url: trackingUrl,
},
});
} catch (error) {
printJson({
success: false,
error: error && error.message ? error.message : 'Error al consultar Shalom.',
details: error && error.stack ? error.stack.split('\n').slice(0, 4).join(' | ') : undefined,
});
process.exit(6);
} finally {
if (browser) {
try {
await browser.close();
} catch (error) {
// Ignoramos errores al cerrar el navegador.
}
}
}
}
main();