@gnpdev/rpa-tools 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +93 -0
- package/package.json +27 -0
- package/src/index.d.ts +75 -0
- package/src/index.js +86 -0
- package/src/logger.js +26 -0
- package/src/storage.js +83 -0
- package/src/watcher.js +137 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @gnpdev/rpa-tools
|
|
2
|
+
|
|
3
|
+
Herramientas de automatización para bots RPA: logging estructurado con **Pino**, almacenamiento en **MinIO** con optimización de imágenes (WebP) y monitoreo de flags en base de datos para capturas de pantalla en tiempo real con **Playwright**.
|
|
4
|
+
|
|
5
|
+
## Requisitos
|
|
6
|
+
|
|
7
|
+
- **Node.js** v18+
|
|
8
|
+
- **pnpm** (recomendado)
|
|
9
|
+
- Instancia de **MinIO** activa.
|
|
10
|
+
- Base de datos **PostgreSQL**.
|
|
11
|
+
|
|
12
|
+
## Instalación
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pnpm add @gnpdev/rpa-tools
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Configuración Centralizada (Best Practice)
|
|
19
|
+
|
|
20
|
+
Crea un archivo en tu proyecto (ej. `src/lib/rpa.js`) para inicializar y exportar las herramientas. Esto asegura que uses la misma instancia en toda la aplicación.
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
// src/lib/rpa.js
|
|
24
|
+
const { createRpaTools } = require('@gnpdev/rpa-tools');
|
|
25
|
+
const { Pool } = require('pg');
|
|
26
|
+
|
|
27
|
+
// 1. Configura tu pool de base de datos (usualmente ya lo tienes)
|
|
28
|
+
const pool = new Pool({
|
|
29
|
+
connectionString: process.env.DATABASE_URL
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 2. Inicializa las herramientas
|
|
33
|
+
async function initRpa() {
|
|
34
|
+
const rpa = await createRpaTools({
|
|
35
|
+
botId: 'BOT-VENTAS-01',
|
|
36
|
+
db: pool,
|
|
37
|
+
minio: {
|
|
38
|
+
endPoint: process.env.MINIO_ENDPOINT,
|
|
39
|
+
port: parseInt(process.env.MINIO_PORT || '9000'),
|
|
40
|
+
useSSL: process.env.MINIO_USE_SSL === 'true',
|
|
41
|
+
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
42
|
+
secretKey: process.env.MINIO_SECRET_KEY,
|
|
43
|
+
bucket: 'rpa-screenshots'
|
|
44
|
+
},
|
|
45
|
+
log: {
|
|
46
|
+
level: process.env.NODE_ENV === 'development' ? 'debug' : 'info',
|
|
47
|
+
pretty: process.env.NODE_ENV === 'development'
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return rpa;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = initRpa;
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Uso en la Aplicación
|
|
57
|
+
|
|
58
|
+
### 1. Logging Estructurado
|
|
59
|
+
Usa el logger preconfigurado que ya incluye el `botId` en cada entrada.
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
const rpa = await initRpa();
|
|
63
|
+
rpa.logger.info({ orderId: '123' }, 'Procesando orden');
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. Capturas de Pantalla bajo demanda (Playwright)
|
|
67
|
+
Activa el watcher pasando la instancia de `page`. El bot detectará cambios en la tabla de la DB para tomar screenshots automáticamente.
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const { chromium } = require('playwright');
|
|
71
|
+
const rpa = await initRpa();
|
|
72
|
+
|
|
73
|
+
const browser = await chromium.launch();
|
|
74
|
+
const page = await browser.newPage();
|
|
75
|
+
|
|
76
|
+
// Inicia el monitoreo (polling cada 3s por defecto)
|
|
77
|
+
rpa.watchDebugFlag(page);
|
|
78
|
+
|
|
79
|
+
// Al terminar el proceso del bot
|
|
80
|
+
// rpa.destroy();
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Variables de Entorno Sugeridas
|
|
84
|
+
|
|
85
|
+
```env
|
|
86
|
+
MINIO_ENDPOINT=localhost
|
|
87
|
+
MINIO_PORT=9000
|
|
88
|
+
MINIO_USE_SSL=false
|
|
89
|
+
MINIO_ACCESS_KEY=admin
|
|
90
|
+
MINIO_SECRET_KEY=password
|
|
91
|
+
MINIO_BUCKET=rpa-screenshots
|
|
92
|
+
DATABASE_URL=postgres://user:pass@localhost:5432/db
|
|
93
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gnpdev/rpa-tools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Libreria para logs y screenshot de bots",
|
|
5
|
+
"author": "Sergio Antonio Trujillo del Valle",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"types": "src/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"src"
|
|
10
|
+
],
|
|
11
|
+
"keywords": [
|
|
12
|
+
"rpa",
|
|
13
|
+
"playwright",
|
|
14
|
+
"minio",
|
|
15
|
+
"pino",
|
|
16
|
+
"screenshots"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"minio": "^8.0.7",
|
|
21
|
+
"pino": "^10.3.1",
|
|
22
|
+
"sharp": "^0.34.5"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"pino-pretty": "^13.1.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { Pool } from 'pg';
|
|
2
|
+
import type { Logger } from 'pino';
|
|
3
|
+
import type { Page } from 'playwright';
|
|
4
|
+
|
|
5
|
+
// ── Opciones de MinIO ────────────────────────────────────────────────────────
|
|
6
|
+
export interface MinioConfig {
|
|
7
|
+
endPoint: string;
|
|
8
|
+
port?: number;
|
|
9
|
+
useSSL?: boolean;
|
|
10
|
+
accessKey: string | any;
|
|
11
|
+
secretKey: string | any;
|
|
12
|
+
bucket?: string | any;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ── Opciones de screenshot ───────────────────────────────────────────────────
|
|
16
|
+
export interface ScreenshotConfig {
|
|
17
|
+
/** Formato final del archivo en MinIO. Default: 'webp' */
|
|
18
|
+
format?: 'webp' | 'jpeg';
|
|
19
|
+
/** Calidad de compresión final (sharp). Default: 70 */
|
|
20
|
+
quality?: number;
|
|
21
|
+
/** Calidad de captura en Playwright. Default: 90 */
|
|
22
|
+
captureQuality?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── Opciones de Pino ─────────────────────────────────────────────────────────
|
|
26
|
+
export interface LogConfig {
|
|
27
|
+
level?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'silent' | any;
|
|
28
|
+
/** Activar pino-pretty (solo dev). Default: false */
|
|
29
|
+
pretty?: boolean;
|
|
30
|
+
pinoOptions?: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ── Opciones principales de createRpaTools ───────────────────────────────────
|
|
34
|
+
export interface RpaToolsOptions {
|
|
35
|
+
/** ID único del bot. Default: process.env.RPA_BOT_ID */
|
|
36
|
+
botId?: string;
|
|
37
|
+
/** Pool de pg ya conectado */
|
|
38
|
+
db: Pool;
|
|
39
|
+
/** Config de MinIO. Si se omite, lee de variables de entorno MINIO_* */
|
|
40
|
+
minio?: MinioConfig;
|
|
41
|
+
/** Opciones del logger Pino */
|
|
42
|
+
log?: LogConfig;
|
|
43
|
+
/** Opciones de captura y compresión de screenshots */
|
|
44
|
+
screenshot?: ScreenshotConfig;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Objeto retornado por createRpaTools ──────────────────────────────────────
|
|
48
|
+
export interface RpaTools {
|
|
49
|
+
/** Instancia de Pino lista para usar en el bot */
|
|
50
|
+
logger: Logger;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Inicia el polling que activa/desactiva screenshots
|
|
54
|
+
* según bot_debug_config en la BD.
|
|
55
|
+
* @param page - Objeto Page de Playwright
|
|
56
|
+
* @param pollMs - Intervalo de polling en ms. Default: 3000
|
|
57
|
+
*/
|
|
58
|
+
watchDebugFlag: (page: Page, pollMs?: number) => void;
|
|
59
|
+
|
|
60
|
+
/** Detiene todos los intervalos. Llamar siempre al cerrar el bot. */
|
|
61
|
+
destroy: () => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ── Export principal ─────────────────────────────────────────────────────────
|
|
65
|
+
/**
|
|
66
|
+
* Inicializa la librería rpa-tools.
|
|
67
|
+
* Crea el cliente MinIO, verifica el bucket y retorna
|
|
68
|
+
* el logger y el watcher listos para usar.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* const rpa = await createRpaTools({ botId: 'bot-001', db: pool });
|
|
72
|
+
* rpa.watchDebugFlag(page);
|
|
73
|
+
* rpa.logger.info('Bot iniciado');
|
|
74
|
+
*/
|
|
75
|
+
export function createRpaTools(options: RpaToolsOptions): Promise<RpaTools>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { createLogger } = require('./logger');
|
|
3
|
+
const { createMinioClient, ensureBucket } = require('./storage');
|
|
4
|
+
const { ScreenshotWatcher } = require('./watcher');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Factory principal de la librería.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} opts
|
|
10
|
+
* @param {string} opts.botId - identificador único del bot
|
|
11
|
+
* @param {import('pg').Pool} opts.db - pool de pg ya conectado
|
|
12
|
+
* @param {object} opts.minio - config de MinIO (ver abajo)
|
|
13
|
+
* @param {string} opts.minio.endPoint
|
|
14
|
+
* @param {number} [opts.minio.port=9000]
|
|
15
|
+
* @param {boolean} [opts.minio.useSSL=false]
|
|
16
|
+
* @param {string} opts.minio.accessKey
|
|
17
|
+
* @param {string} opts.minio.secretKey
|
|
18
|
+
* @param {string} [opts.minio.bucket='rpa-screenshots']
|
|
19
|
+
* @param {object} [opts.log] - opciones de Pino
|
|
20
|
+
* @param {string} [opts.log.level='info']
|
|
21
|
+
* @param {boolean} [opts.log.pretty=false]
|
|
22
|
+
*
|
|
23
|
+
* @returns {{ logger, watchDebugFlag, destroy }}
|
|
24
|
+
*/
|
|
25
|
+
async function createRpaTools(opts = {}) {
|
|
26
|
+
const {
|
|
27
|
+
botId = process.env.RPA_BOT_ID,
|
|
28
|
+
log: logCfg = {},
|
|
29
|
+
db,
|
|
30
|
+
minio: minioCfg,
|
|
31
|
+
} = opts;
|
|
32
|
+
|
|
33
|
+
// Leer minio desde env si no se pasó config
|
|
34
|
+
const minio = minioCfg ?? {
|
|
35
|
+
endPoint: process.env.MINIO_ENDPOINT,
|
|
36
|
+
port: Number(process.env.MINIO_PORT) || 9000,
|
|
37
|
+
useSSL: process.env.MINIO_USE_SSL === 'true',
|
|
38
|
+
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
39
|
+
secretKey: process.env.MINIO_SECRET_KEY,
|
|
40
|
+
bucket: process.env.MINIO_BUCKET ?? 'rpa-screenshots',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// db se sigue pasando desde afuera (el pool ya existe en el bot)
|
|
44
|
+
// pero puedes leer la URL para validar
|
|
45
|
+
if (!db) throw new Error('[rpa-tools] db (pg.Pool) es requerido');
|
|
46
|
+
|
|
47
|
+
// Validar que las env vars críticas existan
|
|
48
|
+
const missing = ['endPoint', 'accessKey', 'secretKey']
|
|
49
|
+
.filter(k => !minio[k]);
|
|
50
|
+
|
|
51
|
+
if (missing.length) {
|
|
52
|
+
throw new Error(`[rpa-tools] Faltan vars de entorno: ${missing.map(k => `MINIO_${k.toUpperCase()}`).join(', ')}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if (!botId) throw new Error('[rpa-tools] botId es requerido');
|
|
58
|
+
if (!db) throw new Error('[rpa-tools] db (pg.Pool) es requerido');
|
|
59
|
+
if (!minioCfg) throw new Error('[rpa-tools] minio config es requerida');
|
|
60
|
+
|
|
61
|
+
const logger = createLogger(botId, logCfg);
|
|
62
|
+
const minioClient = createMinioClient(minioCfg);
|
|
63
|
+
const bucket = minioCfg.bucket ?? 'rpa-screenshots';
|
|
64
|
+
|
|
65
|
+
await ensureBucket(minioClient, bucket);
|
|
66
|
+
logger.info({ bucket }, 'MinIO listo');
|
|
67
|
+
|
|
68
|
+
const watcher = new ScreenshotWatcher({ botId, pool: db, minioClient, bucket, logger });
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
/** Instancia de Pino lista para usar en tu bot */
|
|
72
|
+
logger,
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Arranca el polling. Llama una vez, pasando el objeto `page` de Playwright.
|
|
76
|
+
* @param {import('playwright').Page} page
|
|
77
|
+
* @param {number} [pollMs=3000]
|
|
78
|
+
*/
|
|
79
|
+
watchDebugFlag: (page, pollMs) => watcher.watch(page, pollMs),
|
|
80
|
+
|
|
81
|
+
/** Limpia intervalos al finalizar el bot */
|
|
82
|
+
destroy: () => watcher.destroy(),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { createRpaTools };
|
package/src/logger.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const pino = require('pino');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Crea una instancia de Pino ya configurada con el botId como campo base.
|
|
6
|
+
* @param {string} botId
|
|
7
|
+
* @param {object} opts
|
|
8
|
+
* @param {'trace'|'debug'|'info'|'warn'|'error'} [opts.level='info']
|
|
9
|
+
* @param {boolean} [opts.pretty=false] - activar pino-pretty (solo en dev)
|
|
10
|
+
* @param {object} [opts.pinoOptions] - opciones adicionales de Pino
|
|
11
|
+
*/
|
|
12
|
+
function createLogger(botId, opts = {}) {
|
|
13
|
+
const { level = 'info', pretty = false, pinoOptions = {} } = opts;
|
|
14
|
+
|
|
15
|
+
return pino({
|
|
16
|
+
level,
|
|
17
|
+
base: { botId },
|
|
18
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
19
|
+
transport: pretty
|
|
20
|
+
? { target: 'pino-pretty', options: { colorize: true } }
|
|
21
|
+
: undefined,
|
|
22
|
+
...pinoOptions,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { createLogger };
|
package/src/storage.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const Minio = require('minio');
|
|
3
|
+
const sharp = require('sharp');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Crea y retorna el cliente de MinIO.
|
|
7
|
+
* @param {object} cfg
|
|
8
|
+
* @param {string} cfg.endPoint
|
|
9
|
+
* @param {number} [cfg.port=9000]
|
|
10
|
+
* @param {boolean} [cfg.useSSL=false]
|
|
11
|
+
* @param {string} cfg.accessKey
|
|
12
|
+
* @param {string} cfg.secretKey
|
|
13
|
+
*/
|
|
14
|
+
function createMinioClient(cfg) {
|
|
15
|
+
return new Minio.Client({
|
|
16
|
+
endPoint: cfg.endPoint,
|
|
17
|
+
port: cfg.port ?? 9000,
|
|
18
|
+
useSSL: cfg.useSSL ?? false,
|
|
19
|
+
accessKey: cfg.accessKey,
|
|
20
|
+
secretKey: cfg.secretKey,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Garantiza que el bucket exista; lo crea si no.
|
|
26
|
+
* @param {import('minio').Client} client
|
|
27
|
+
* @param {string} bucket
|
|
28
|
+
* @param {string} [region='us-east-1']
|
|
29
|
+
*/
|
|
30
|
+
async function ensureBucket(client, bucket, region = 'us-east-1') {
|
|
31
|
+
const exists = await client.bucketExists(bucket);
|
|
32
|
+
if (!exists) await client.makeBucket(bucket, region);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convierte un buffer de imagen a WebP usando sharp.
|
|
37
|
+
* @param {Buffer} buffer - buffer fuente (jpeg/png)
|
|
38
|
+
* @param {number} [quality=70]
|
|
39
|
+
* @returns {Promise<Buffer>}
|
|
40
|
+
*/
|
|
41
|
+
async function toWebp(buffer, quality = 70) {
|
|
42
|
+
return sharp(buffer)
|
|
43
|
+
.webp({ quality })
|
|
44
|
+
.toBuffer();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Sube un screenshot a MinIO.
|
|
49
|
+
* Si format='webp' convierte el buffer antes de subir.
|
|
50
|
+
* Ruta generada: {botId}/{YYYY-MM-DD}/{ISO-timestamp}.{ext}
|
|
51
|
+
*
|
|
52
|
+
* @param {import('minio').Client} client
|
|
53
|
+
* @param {string} bucket
|
|
54
|
+
* @param {string} botId
|
|
55
|
+
* @param {Buffer} buffer - buffer raw de Playwright (jpeg)
|
|
56
|
+
* @param {object} [opts]
|
|
57
|
+
* @param {'webp'|'jpeg'} [opts.format='webp']
|
|
58
|
+
* @param {number} [opts.quality=70] - calidad 1-100
|
|
59
|
+
* @returns {Promise<string>} - object key en MinIO
|
|
60
|
+
*/
|
|
61
|
+
async function uploadScreenshot(client, bucket, botId, buffer, opts = {}) {
|
|
62
|
+
const { format = 'webp', quality = 70 } = opts;
|
|
63
|
+
|
|
64
|
+
const finalBuffer = format === 'webp'
|
|
65
|
+
? await toWebp(buffer, quality)
|
|
66
|
+
: buffer;
|
|
67
|
+
|
|
68
|
+
const contentType = format === 'webp' ? 'image/webp' : 'image/jpeg';
|
|
69
|
+
const ext = format === 'webp' ? 'webp' : 'jpg';
|
|
70
|
+
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const date = now.toISOString().slice(0, 10); // 2025-06-01
|
|
73
|
+
const ts = now.toISOString().replace(/[:.]/g, '-'); // 2025-06-01T12-30-00-000Z
|
|
74
|
+
const key = `${botId}.${ext}`;
|
|
75
|
+
|
|
76
|
+
await client.putObject(bucket, key, finalBuffer, finalBuffer.length, {
|
|
77
|
+
'Content-Type': contentType,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
return key;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { createMinioClient, ensureBucket, uploadScreenshot };
|
package/src/watcher.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { uploadScreenshot } = require('./storage');
|
|
3
|
+
|
|
4
|
+
class ScreenshotWatcher {
|
|
5
|
+
/**
|
|
6
|
+
* @param {object} p
|
|
7
|
+
* @param {string} p.botId
|
|
8
|
+
* @param {import('pg').Pool} p.pool
|
|
9
|
+
* @param {import('minio').Client} p.minioClient
|
|
10
|
+
* @param {string} p.bucket
|
|
11
|
+
* @param {import('pino').Logger} p.logger
|
|
12
|
+
* @param {object} [p.screenshot]
|
|
13
|
+
* @param {'webp'|'jpeg'} [p.screenshot.format='webp']
|
|
14
|
+
* @param {number} [p.screenshot.quality=70] - calidad conversión 1-100
|
|
15
|
+
* @param {number} [p.screenshot.captureQuality=90] - calidad captura Playwright
|
|
16
|
+
*/
|
|
17
|
+
constructor({ botId, pool, minioClient, bucket, logger, screenshot = {} }) {
|
|
18
|
+
this.botId = botId;
|
|
19
|
+
this.pool = pool;
|
|
20
|
+
this.minioClient = minioClient;
|
|
21
|
+
this.bucket = bucket;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
|
|
24
|
+
this.screenshotOpts = {
|
|
25
|
+
format: screenshot.format ?? 'webp',
|
|
26
|
+
quality: screenshot.quality ?? 70,
|
|
27
|
+
captureQuality: screenshot.captureQuality ?? 90,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
this._watchTimer = null;
|
|
31
|
+
this._shotTimer = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Inicia el polling que activa/desactiva screenshots según bot_debug_config.
|
|
36
|
+
* Llama una sola vez al arrancar el bot.
|
|
37
|
+
*
|
|
38
|
+
* @param {import('playwright').Page} page
|
|
39
|
+
* @param {number} [pollMs=3000] - cada cuántos ms revisar la BD
|
|
40
|
+
*/
|
|
41
|
+
watch(page, pollMs = 3000) {
|
|
42
|
+
if (this._watchTimer) {
|
|
43
|
+
this.logger.warn('ScreenshotWatcher ya está corriendo, ignorando llamada duplicada');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.logger.info({ pollMs }, 'Iniciando polling de debug config');
|
|
48
|
+
|
|
49
|
+
this._watchTimer = setInterval(async () => {
|
|
50
|
+
try {
|
|
51
|
+
const { rows } = await this.pool.query(
|
|
52
|
+
`SELECT screenshots, interval_sec
|
|
53
|
+
FROM bots.tb_bots
|
|
54
|
+
WHERE bot_id = $1`,
|
|
55
|
+
[this.botId]
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const cfg = rows[0];
|
|
59
|
+
// console.log('DEBUG CONFIG', cfg); // <-- loguear config para debug
|
|
60
|
+
|
|
61
|
+
if (!cfg) {
|
|
62
|
+
this.logger.debug('Sin config en bot_debug_config para este bot');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (cfg.screenshots && !this._shotTimer) {
|
|
67
|
+
this.logger.info({ intervalSec: cfg.interval_sec }, 'Screenshots activados');
|
|
68
|
+
this._startScreenshots(page, cfg.interval_sec * 1000);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!cfg.screenshots && this._shotTimer) {
|
|
73
|
+
this.logger.info('Screenshots desactivados');
|
|
74
|
+
this._stopScreenshots();
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
this.logger.error({ err }, 'Error leyendo bot_debug_config');
|
|
78
|
+
}
|
|
79
|
+
}, pollMs);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* @private
|
|
84
|
+
*/
|
|
85
|
+
_startScreenshots(page, intervalMs) {
|
|
86
|
+
this._shotTimer = setInterval(async () => {
|
|
87
|
+
const t0 = Date.now();
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Playwright captura en jpeg (más rápido), sharp convierte a WebP después
|
|
91
|
+
const rawBuffer = await page.screenshot({
|
|
92
|
+
type: 'jpeg',
|
|
93
|
+
quality: this.screenshotOpts.captureQuality,
|
|
94
|
+
fullPage: false,
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
const key = await uploadScreenshot(
|
|
98
|
+
this.minioClient,
|
|
99
|
+
this.bucket,
|
|
100
|
+
this.botId,
|
|
101
|
+
rawBuffer,
|
|
102
|
+
{
|
|
103
|
+
format: this.screenshotOpts.format,
|
|
104
|
+
quality: this.screenshotOpts.quality,
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
this.logger.debug(
|
|
109
|
+
{ key, ms: Date.now() - t0 },
|
|
110
|
+
'Screenshot subido'
|
|
111
|
+
);
|
|
112
|
+
} catch (err) {
|
|
113
|
+
this.logger.error({ err }, 'Error capturando o subiendo screenshot');
|
|
114
|
+
}
|
|
115
|
+
}, intervalMs);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* @private
|
|
120
|
+
*/
|
|
121
|
+
_stopScreenshots() {
|
|
122
|
+
clearInterval(this._shotTimer);
|
|
123
|
+
this._shotTimer = null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Detiene todos los intervalos. Llamar siempre al cerrar el bot.
|
|
128
|
+
*/
|
|
129
|
+
destroy() {
|
|
130
|
+
this._stopScreenshots();
|
|
131
|
+
clearInterval(this._watchTimer);
|
|
132
|
+
this._watchTimer = null;
|
|
133
|
+
this.logger.info('ScreenshotWatcher destruido');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { ScreenshotWatcher };
|