@getparallel/parx 0.4.4 → 0.6.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/dist/par.js ADDED
@@ -0,0 +1,355 @@
1
+ export const LADO = 16;
2
+ export const TONOS = {
3
+ 300: { cuerpo: '#ae9bff', sombra: '#8b70ff', detalle: '#ddd3ff' },
4
+ 400: { cuerpo: '#8b70ff', sombra: '#6d4aff', detalle: '#ae9bff' },
5
+ 500: { cuerpo: '#7b5cff', sombra: '#5a36e6', detalle: '#ae9bff' },
6
+ 600: { cuerpo: '#6d4aff', sombra: '#5a36e6', detalle: '#ae9bff' },
7
+ 700: { cuerpo: '#5a36e6', sombra: '#4626ba', detalle: '#8b70ff' },
8
+ };
9
+ export const ORDEN_DE_TONOS = [600, 400, 700, 500, 300];
10
+ export const TINTA = '#15131f';
11
+ export const CLARO = '#d3cde8';
12
+ export const ORO = '#f2b632';
13
+ function huella(s) {
14
+ let h = 2166136261;
15
+ for (let i = 0; i < s.length; i++) {
16
+ h ^= s.charCodeAt(i);
17
+ h = Math.imul(h, 16777619);
18
+ }
19
+ return h >>> 0;
20
+ }
21
+ export function tonoDe(semilla) {
22
+ if (!semilla)
23
+ return 600;
24
+ return ORDEN_DE_TONOS[huella(semilla) % ORDEN_DE_TONOS.length];
25
+ }
26
+ export function tonosDelEquipo(semillas) {
27
+ const usados = new Set();
28
+ return semillas.map((s) => {
29
+ let t = tonoDe(s);
30
+ if (usados.size < ORDEN_DE_TONOS.length) {
31
+ let i = ORDEN_DE_TONOS.indexOf(t);
32
+ while (usados.has(t)) {
33
+ i = (i + 1) % ORDEN_DE_TONOS.length;
34
+ t = ORDEN_DE_TONOS[i];
35
+ }
36
+ }
37
+ usados.add(t);
38
+ return t;
39
+ });
40
+ }
41
+ export function colorDe(c, tono) {
42
+ const t = TONOS[tono];
43
+ switch (c) {
44
+ case 'B':
45
+ return t.cuerpo;
46
+ case 'D':
47
+ return t.sombra;
48
+ case 'A':
49
+ return t.detalle;
50
+ case 'E':
51
+ return TINTA;
52
+ case 'W':
53
+ return CLARO;
54
+ case 'Y':
55
+ return ORO;
56
+ default:
57
+ return null;
58
+ }
59
+ }
60
+ const INICIO = [7, 11];
61
+ const ARRIBA = 2;
62
+ const FILAS = 12;
63
+ const FILA_OJO = ARRIBA + 3;
64
+ function pon(m, x, y, c) {
65
+ if (x >= 0 && x < LADO && y >= 0 && y < LADO)
66
+ m[y][x] = c;
67
+ }
68
+ export function matrizDe(c, accesorio) {
69
+ const m = Array.from({ length: LADO }, () => Array(LADO).fill('.'));
70
+ const ojos = Array.isArray(c.ojos) ? c.ojos : [c.ojos ?? 'abierto', c.ojos ?? 'abierto'];
71
+ [c.izq, c.der].forEach((b, k) => {
72
+ const dx = b?.dx ?? 0;
73
+ const dy = b?.dy ?? 0;
74
+ const estira = b?.estira ?? 0;
75
+ const inicio = INICIO[k];
76
+ const xDe = (y) => inicio - Math.floor((y - ARRIBA) / 2) + dx;
77
+ for (let y = ARRIBA - estira; y <= ARRIBA + FILAS; y++) {
78
+ const color = y === ARRIBA + FILAS ? 'D' : 'B';
79
+ for (let i = 0; i < 3; i++)
80
+ pon(m, xDe(y) + i, y + dy, color);
81
+ }
82
+ const filaOjo = FILA_OJO - estira;
83
+ const fy = filaOjo + dy;
84
+ const cx = xDe(filaOjo) + 1;
85
+ const ojo = ojos[k];
86
+ switch (ojo) {
87
+ case 'abierto':
88
+ pon(m, cx, fy, 'E');
89
+ break;
90
+ case 'cerrado':
91
+ pon(m, cx - 1, fy, 'D');
92
+ pon(m, cx, fy, 'D');
93
+ pon(m, cx + 1, fy, 'D');
94
+ break;
95
+ case 'feliz':
96
+ pon(m, cx, fy - 1, 'E');
97
+ pon(m, cx - 1, fy, 'E');
98
+ pon(m, cx + 1, fy, 'E');
99
+ break;
100
+ case 'grande':
101
+ pon(m, cx, fy - 1, 'E');
102
+ pon(m, cx, fy, 'E');
103
+ break;
104
+ case 'x':
105
+ pon(m, cx - 1, fy - 1, 'E');
106
+ pon(m, cx + 1, fy - 1, 'E');
107
+ pon(m, cx, fy, 'E');
108
+ pon(m, cx - 1, fy + 1, 'E');
109
+ pon(m, cx + 1, fy + 1, 'E');
110
+ break;
111
+ case 'abajo':
112
+ pon(m, cx, fy + 1, 'E');
113
+ break;
114
+ case 'arriba':
115
+ pon(m, cx, fy - 1, 'E');
116
+ break;
117
+ case 'izquierda':
118
+ pon(m, cx - 1, fy, 'E');
119
+ break;
120
+ case 'derecha':
121
+ pon(m, cx + 1, fy, 'E');
122
+ break;
123
+ }
124
+ });
125
+ if (accesorio) {
126
+ for (const [x, y, ch] of PIEZAS[accesorio]) {
127
+ const b = x <= 9 ? c.izq : c.der;
128
+ pon(m, x + (b?.dx ?? 0), y + (b?.dy ?? 0) - (b?.estira ?? 0), ch);
129
+ }
130
+ }
131
+ for (const p of c.extra ?? [])
132
+ pon(m, p[0], p[1], p[2] ?? 'A');
133
+ return m.map((f) => f.join(''));
134
+ }
135
+ const FILAS_DE_ACCESORIOS = {
136
+ gorra: { 0: '...........DDD..', 1: '...........DDDDD' },
137
+ copa: { 0: '.......EAE......', 1: '......EEEEE.....' },
138
+ astronauta: { 0: '........WWWWW...', 1: '......WW.....WW.', 2: '......W.......W.', 3: '......W.......W.' },
139
+ corona: { 0: '...........Y.Y..', 1: '...........YYY..' },
140
+ gafas_sol: { 4: '......WEE.WEE...', 5: '......EEEEEEE...' },
141
+ gafas: { 4: '......YYY.YYY...', 5: '......Y.YYY.Y...', 6: '......YYY.YYY...' },
142
+ bigote: { 7: '.......EEE......' },
143
+ pajarita: { 9: '.....EEAEE......' },
144
+ idea: { 0: '.......YYY......', 1: '......A.Y.A.....' },
145
+ gato: { 0: '...........B.B..', 1: '...........BBB..' },
146
+ fiesta: { 0: '........Y.......', 1: '.......YEY......' },
147
+ auriculares: { 0: '............E...', 1: '...........E.E..', 3: '..........E....E', 4: '..........E....E' },
148
+ cocinero: { 0: '......WWWWW.....', 1: '.......WWW......' },
149
+ pirata: { 4: '......EEE.......', 5: '......EEE.......' },
150
+ bufanda: { 10: '...YYY.YYY......', 11: '........YY......' },
151
+ antena: { 0: '............Y...', 1: '............D...' },
152
+ sombrilla: { 0: '.......DDD......', 1: '......DDDDD.....' },
153
+ nube: { 0: '.......WW.......', 1: '......WWWW......', 2: '.....A..........', 3: '....A...........', 4: '.....A..........' },
154
+ halo: { 0: '.......YYY......' },
155
+ pelo: { 0: '......B.B.B.....', 1: '......BBBBB.....' },
156
+ };
157
+ export const LISTA_DE_ACCESORIOS = Object.keys(FILAS_DE_ACCESORIOS);
158
+ const PIEZAS = Object.fromEntries(LISTA_DE_ACCESORIOS.map((id) => [
159
+ id,
160
+ Object.entries(FILAS_DE_ACCESORIOS[id]).flatMap(([y, fila]) => [...fila].flatMap((ch, x) => (ch === '.' ? [] : [[x, Number(y), ch]]))),
161
+ ]));
162
+ export function accesorioDe(semilla) {
163
+ if (!semilla)
164
+ return null;
165
+ return LISTA_DE_ACCESORIOS[huella(`${semilla}·accesorio`) % LISTA_DE_ACCESORIOS.length];
166
+ }
167
+ export function accesoriosDelEquipo(semillas) {
168
+ const usados = new Set();
169
+ return semillas.map((s) => {
170
+ let a = accesorioDe(s || 'parallel');
171
+ if (usados.size < LISTA_DE_ACCESORIOS.length) {
172
+ let i = LISTA_DE_ACCESORIOS.indexOf(a);
173
+ while (usados.has(a)) {
174
+ i = (i + 1) % LISTA_DE_ACCESORIOS.length;
175
+ a = LISTA_DE_ACCESORIOS[i];
176
+ }
177
+ }
178
+ usados.add(a);
179
+ return a;
180
+ });
181
+ }
182
+ const P = (puntos, dx = 0, dy = 0, c = 'A') => puntos.map(([x, y]) => [x + dx, y + dy, c]);
183
+ const INTERROGACION = [[1, 0], [2, 0], [3, 1], [2, 2], [2, 4]];
184
+ const EXCLAMACION = [[2, 0], [2, 1], [2, 2], [2, 4]];
185
+ const TIC = [[0, 2], [1, 3], [2, 2], [3, 1], [4, 0]];
186
+ const CORAZON = [[1, 0], [3, 0], [0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [1, 2], [2, 2], [3, 2], [2, 3]];
187
+ const ZETA = [[0, 0], [1, 0], [2, 0], [1, 1], [0, 2], [1, 2], [2, 2]];
188
+ const NOTA = [[1, 0], [2, 0], [1, 1], [1, 2], [0, 3], [1, 3]];
189
+ export const ANIMACIONES = {
190
+ reposo: [{ ms: 1500 }, { ojos: 'cerrado', ms: 120 }, { ms: 1800 }, { ojos: 'derecha', ms: 700 }, { ms: 500 }, { izq: { dy: 1 }, der: { dy: 1 }, ms: 400 }, { ms: 900 }],
191
+ saludo: [
192
+ { ojos: 'feliz', ms: 200 },
193
+ { ojos: 'feliz', der: { dx: 1, dy: -1 }, ms: 180 },
194
+ { ojos: 'feliz', ms: 180 },
195
+ { ojos: 'feliz', der: { dx: 1, dy: -1 }, ms: 180 },
196
+ { ojos: 'feliz', ms: 600 },
197
+ ],
198
+ pensando: [
199
+ { ojos: 'arriba', ms: 350 },
200
+ { ojos: 'arriba', extra: P([[2, 1]]), ms: 350 },
201
+ { ojos: 'arriba', extra: P([[2, 1], [4, 1]]), ms: 350 },
202
+ { ojos: 'arriba', extra: P([[2, 1], [4, 1], [6, 0]]), ms: 600 },
203
+ { ojos: 'cerrado', extra: P([[2, 1], [4, 1], [6, 0]]), ms: 120 },
204
+ ],
205
+ trabajando: [
206
+ { izq: { dy: -1 }, extra: P([[1, 9], [15, 3]]), ms: 170 },
207
+ { der: { dy: -1 }, extra: P([[0, 7], [15, 6]]), ms: 170 },
208
+ ],
209
+ escribiendo: [
210
+ { ojos: 'abajo', extra: P([[3, 15]]), ms: 220 },
211
+ { ojos: 'abajo', extra: P([[3, 15], [5, 15]]), ms: 220 },
212
+ { ojos: 'abajo', extra: P([[3, 15], [5, 15], [7, 15]]), ms: 220 },
213
+ { ojos: 'abajo', ms: 220 },
214
+ ],
215
+ buscando: [{ ojos: 'izquierda', ms: 380 }, { ms: 200 }, { ojos: 'derecha', ms: 380 }, { ms: 200 }],
216
+ esperando: [{ ms: 450 }, { extra: P(INTERROGACION, 1, 1), ms: 650 }, { extra: P(INTERROGACION, 1, 1), ojos: 'cerrado', ms: 120 }, { extra: P(INTERROGACION, 1, 1), ms: 650 }],
217
+ hecho: [
218
+ { ms: 200 },
219
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', ms: 140 },
220
+ { izq: { dy: -2 }, der: { dy: -2 }, ojos: 'feliz', extra: P(TIC, 1, 1), ms: 240 },
221
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', extra: P(TIC, 1, 1), ms: 140 },
222
+ { ojos: 'feliz', extra: P(TIC, 1, 1), ms: 900 },
223
+ ],
224
+ error: [
225
+ { ojos: 'x', izq: { dx: -1 }, der: { dx: -1 }, ms: 90 },
226
+ { ojos: 'x', izq: { dx: 1 }, der: { dx: 1 }, ms: 90 },
227
+ { ojos: 'x', izq: { dx: -1 }, der: { dx: -1 }, ms: 90 },
228
+ { ojos: 'x', izq: { dx: 1 }, der: { dx: 1 }, ms: 90 },
229
+ { ojos: 'x', ms: 900 },
230
+ ],
231
+ dormido: [
232
+ { ojos: 'cerrado', extra: P(ZETA, 1, 6), ms: 600 },
233
+ { ojos: 'cerrado', izq: { dy: 1 }, der: { dy: 1 }, extra: P(ZETA, 1, 3), ms: 600 },
234
+ { ojos: 'cerrado', extra: P(ZETA, 2, 0), ms: 600 },
235
+ { ojos: 'cerrado', izq: { dy: 1 }, der: { dy: 1 }, ms: 600 },
236
+ ],
237
+ estirarse: [
238
+ { ms: 300 },
239
+ { izq: { estira: 1 }, der: { estira: 1 }, ojos: 'cerrado', ms: 220 },
240
+ { izq: { estira: 2 }, der: { estira: 2 }, ojos: 'cerrado', ms: 500 },
241
+ { izq: { estira: 1 }, der: { estira: 1 }, ojos: 'cerrado', ms: 200 },
242
+ { ojos: 'feliz', ms: 600 },
243
+ ],
244
+ salto: [
245
+ { izq: { dy: 1 }, der: { dy: 1 }, ms: 160 },
246
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', ms: 120 },
247
+ { izq: { dy: -2 }, der: { dy: -2 }, ojos: 'feliz', ms: 220 },
248
+ { izq: { dy: -1 }, der: { dy: -1 }, ojos: 'feliz', ms: 120 },
249
+ { izq: { dy: 1 }, der: { dy: 1 }, ms: 140 },
250
+ { ms: 500 },
251
+ ],
252
+ baile: [
253
+ { izq: { dy: -1, dx: -1 }, der: { dx: -1 }, ojos: 'feliz', extra: P(NOTA, 0, 1), ms: 220 },
254
+ { izq: { dx: 1 }, der: { dy: -1, dx: 1 }, ojos: 'feliz', ms: 220 },
255
+ { izq: { dy: -1, dx: -1 }, der: { dx: -1 }, ojos: 'feliz', ms: 220 },
256
+ { izq: { dx: 1 }, der: { dy: -1, dx: 1 }, ojos: 'feliz', extra: P(NOTA, 0, 0), ms: 220 },
257
+ ],
258
+ guino: [{ ms: 600 }, { ojos: ['abierto', 'cerrado'], ms: 260 }, { ojos: 'feliz', ms: 700 }],
259
+ corazon: [
260
+ { ojos: 'feliz', extra: P(CORAZON, 0, 3), ms: 260 },
261
+ { ojos: 'feliz', extra: P(CORAZON, 0, 2), ms: 260 },
262
+ { ojos: 'feliz', extra: P(CORAZON, 0, 1), ms: 260 },
263
+ { ojos: 'feliz', extra: P(CORAZON, 0, 0), ms: 700 },
264
+ ],
265
+ fiesta: [
266
+ { ojos: 'feliz', izq: { dy: -1 }, der: { dy: -1 }, extra: P([[1, 2], [4, 0], [15, 1], [14, 9], [0, 11]]), ms: 200 },
267
+ { ojos: 'feliz', extra: P([[2, 4], [5, 1], [14, 3], [15, 11], [1, 13]]), ms: 200 },
268
+ { ojos: 'feliz', izq: { dy: -1 }, der: { dy: -1 }, extra: P([[0, 6], [3, 0], [15, 5], [13, 13], [2, 15]]), ms: 200 },
269
+ { ojos: 'feliz', extra: P([[1, 8], [6, 0], [14, 7], [15, 14], [0, 1]]), ms: 200 },
270
+ ],
271
+ musica: [
272
+ { ojos: 'cerrado', izq: { dx: -1 }, der: { dx: -1 }, extra: P(NOTA, 1, 5), ms: 380 },
273
+ { ojos: 'cerrado', extra: P(NOTA, 1, 3), ms: 380 },
274
+ { ojos: 'cerrado', izq: { dx: 1 }, der: { dx: 1 }, extra: P(NOTA, 2, 1), ms: 380 },
275
+ { ojos: 'cerrado', extra: P(NOTA, 2, 0), ms: 380 },
276
+ ],
277
+ mirar: [{ ojos: 'izquierda', ms: 600 }, { ms: 250 }, { ojos: 'derecha', ms: 600 }, { ojos: 'arriba', ms: 500 }, { ms: 500 }],
278
+ sorpresa: [{ ms: 300 }, { ojos: 'grande', izq: { dy: -1 }, der: { dy: -1 }, extra: P(EXCLAMACION, 1, 1), ms: 200 }, { ojos: 'grande', extra: P(EXCLAMACION, 1, 1), ms: 900 }],
279
+ asomarse: [
280
+ { izq: { dy: 12 }, der: { dy: 12 }, ms: 500 },
281
+ { izq: { dy: 8 }, der: { dy: 8 }, ms: 140 },
282
+ { izq: { dy: 5 }, der: { dy: 5 }, ms: 140 },
283
+ { izq: { dy: 3 }, der: { dy: 3 }, ms: 900 },
284
+ { izq: { dy: 3 }, der: { dy: 3 }, ojos: 'cerrado', ms: 120 },
285
+ { izq: { dy: 3 }, der: { dy: 3 }, ojos: 'derecha', ms: 700 },
286
+ { izq: { dy: 6 }, der: { dy: 6 }, ms: 140 },
287
+ { izq: { dy: 12 }, der: { dy: 12 }, ms: 700 },
288
+ ],
289
+ };
290
+ export const LISTA_DE_ANIMACIONES = Object.keys(ANIMACIONES);
291
+ export const DEL_TRABAJO = ['saludo', 'pensando', 'trabajando', 'escribiendo', 'buscando', 'esperando', 'hecho', 'error'];
292
+ export function animacionDelEstado(estado) {
293
+ switch (estado) {
294
+ case 'trabajando':
295
+ return 'trabajando';
296
+ case 'espera':
297
+ return 'esperando';
298
+ case 'parado':
299
+ return 'error';
300
+ case 'hecho':
301
+ return 'hecho';
302
+ default:
303
+ return 'reposo';
304
+ }
305
+ }
306
+ const cache = new Map();
307
+ export function fotogramasDe(a, accesorio) {
308
+ const clave = `${a}|${accesorio ?? ''}`;
309
+ let f = cache.get(clave);
310
+ if (!f) {
311
+ f = ANIMACIONES[a].map((c) => ({ matriz: matrizDe(c, accesorio), ms: c.ms }));
312
+ cache.set(clave, f);
313
+ }
314
+ return f;
315
+ }
316
+ export function duracionDe(a) {
317
+ return fotogramasDe(a).reduce((s, f) => s + f.ms, 0);
318
+ }
319
+ export function fotogramaEn(a, t, accesorio) {
320
+ const fs = fotogramasDe(a, accesorio);
321
+ const total = duracionDe(a);
322
+ let r = ((t % total) + total) % total;
323
+ for (const f of fs) {
324
+ if (r < f.ms)
325
+ return f;
326
+ r -= f.ms;
327
+ }
328
+ return fs[fs.length - 1];
329
+ }
330
+ export function rectangulosDe(matriz, tono) {
331
+ const out = [];
332
+ matriz.forEach((fila, y) => {
333
+ let x = 0;
334
+ while (x < LADO) {
335
+ const c = fila[x];
336
+ const color = colorDe(c, tono);
337
+ if (!color) {
338
+ x++;
339
+ continue;
340
+ }
341
+ let fin = x + 1;
342
+ while (fin < LADO && fila[fin] === c)
343
+ fin++;
344
+ out.push({ x, y, ancho: fin - x, color });
345
+ x = fin;
346
+ }
347
+ });
348
+ return out;
349
+ }
350
+ export function svgDe(matriz, tono, px = 64) {
351
+ const r = rectangulosDe(matriz, tono)
352
+ .map((q) => `<rect x="${q.x}" y="${q.y}" width="${q.ancho}" height="1" fill="${q.color}"/>`)
353
+ .join('');
354
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" viewBox="0 0 16 16" shape-rendering="crispEdges">${r}</svg>`;
355
+ }
package/dist/remoto.js ADDED
@@ -0,0 +1,284 @@
1
+ import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { abrirRemoto, cerrarRemoto, ErrorDeParallel, latidoRemoto, ordenesRemotas } from "./api.js";
5
+ import { sinColor } from "./marca.js";
6
+ export function seContestaDesdeFuera(clase) {
7
+ return clase !== 'credencial' && clase !== 'bypass';
8
+ }
9
+ const SECRETOS = [
10
+ /\b(sk|pk|rk)[-_](live|test|proj|ant)?[-_]?[A-Za-z0-9_-]{16,}/g,
11
+ /\bgh[pousr]_[A-Za-z0-9]{20,}/g,
12
+ /\bxox[abprs]-[A-Za-z0-9-]{10,}/g,
13
+ /\bAKIA[0-9A-Z]{16}\b/g,
14
+ /\bAIza[0-9A-Za-z_-]{30,}/g,
15
+ /\bpar_(term|key)_[A-Za-z0-9_-]{8,}/g,
16
+ /\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
17
+ ];
18
+ const CON_NOMBRE = [/\b(Bearer\s+)[A-Za-z0-9._~+/-]{16,}=*/gi, /\b((?:password|passwd|secret|token|api[_-]?key)\s*[:=]\s*['"]?)[^\s'"]{6,}/gi];
19
+ export function taparSecretos(s) {
20
+ let r = s;
21
+ for (const re of CON_NOMBRE)
22
+ r = r.replace(re, (_m, nombre) => `${nombre}[hidden]`);
23
+ for (const re of SECRETOS)
24
+ r = r.replace(re, '[hidden]');
25
+ return r;
26
+ }
27
+ const MAX_TEXTO = 8000;
28
+ export function paraElMovil(v, max = MAX_TEXTO) {
29
+ const s = taparSecretos(sinColor(typeof v === 'string' ? v : v == null ? '' : String(v)));
30
+ return s.length > max ? `${s.slice(0, max - 1)}…` : s;
31
+ }
32
+ function limpiar(datos) {
33
+ const out = {};
34
+ for (const [k, v] of Object.entries(datos)) {
35
+ if (typeof v === 'string')
36
+ out[k] = paraElMovil(v);
37
+ else if (Array.isArray(v))
38
+ out[k] = v.slice(0, 60).map((x) => (typeof x === 'string' ? paraElMovil(x, 400) : x && typeof x === 'object' ? limpiar(x) : x));
39
+ else if (v && typeof v === 'object')
40
+ out[k] = limpiar(v);
41
+ else
42
+ out[k] = v;
43
+ }
44
+ return out;
45
+ }
46
+ const API = { abrir: abrirRemoto, latido: latidoRemoto, ordenes: ordenesRemotas, cerrar: cerrarRemoto };
47
+ let contadorDePreguntas = 0;
48
+ export class Remoto {
49
+ id = null;
50
+ url = null;
51
+ o;
52
+ api;
53
+ n = 0;
54
+ cola = [];
55
+ estado = 'libre';
56
+ tarea = null;
57
+ acciones = 0;
58
+ modo = '';
59
+ modelo = '';
60
+ activo = false;
61
+ enviando = false;
62
+ otraVez = false;
63
+ relojEnvio = null;
64
+ relojLatido = null;
65
+ escucha = null;
66
+ ultimoEnvio = 0;
67
+ preguntas = new Map();
68
+ constructor(o) {
69
+ this.o = o;
70
+ this.api = { ...API, ...o.api };
71
+ }
72
+ get encendido() {
73
+ return this.activo;
74
+ }
75
+ async abrir(datos) {
76
+ const r = await this.api.abrir(this.o.servidor, this.o.token, datos);
77
+ this.id = r.id;
78
+ this.url = r.url;
79
+ this.modo = datos.modo;
80
+ this.modelo = datos.modelo;
81
+ this.activo = true;
82
+ this.relojLatido = setInterval(() => void this.enviar(), this.o.latidoMs ?? 15_000);
83
+ this.relojLatido.unref?.();
84
+ void this.enviar();
85
+ void this.escuchar();
86
+ return r;
87
+ }
88
+ async cerrar() {
89
+ if (!this.activo)
90
+ return;
91
+ this.apagar();
92
+ if (this.id) {
93
+ try {
94
+ await this.api.cerrar(this.o.servidor, this.o.token, this.id);
95
+ }
96
+ catch {
97
+ }
98
+ }
99
+ }
100
+ apagar() {
101
+ this.activo = false;
102
+ if (this.carpeta && !this.o.carpetaDeFotos)
103
+ rmSync(this.carpeta, { recursive: true, force: true });
104
+ if (this.relojEnvio)
105
+ clearTimeout(this.relojEnvio);
106
+ if (this.relojLatido)
107
+ clearInterval(this.relojLatido);
108
+ this.relojEnvio = this.relojLatido = null;
109
+ this.escucha?.abort();
110
+ this.escucha = null;
111
+ this.preguntas.clear();
112
+ }
113
+ evento(tipo, datos) {
114
+ if (!this.activo)
115
+ return;
116
+ this.cola.push({ n: this.n++, tipo, datos: limpiar(datos) });
117
+ if (!this.relojEnvio) {
118
+ this.relojEnvio = setTimeout(() => {
119
+ this.relojEnvio = null;
120
+ void this.enviar();
121
+ }, this.o.cadaMs ?? 400);
122
+ this.relojEnvio.unref?.();
123
+ }
124
+ }
125
+ fijarEstado(estado, tarea) {
126
+ if (!this.activo)
127
+ return;
128
+ const cambia = estado !== this.estado || (tarea !== undefined && tarea !== this.tarea);
129
+ this.estado = estado;
130
+ if (tarea !== undefined)
131
+ this.tarea = tarea ? paraElMovil(tarea, 200) : null;
132
+ if (cambia)
133
+ void this.enviar();
134
+ }
135
+ fijarModo(modo, modelo) {
136
+ this.modo = modo;
137
+ this.modelo = modelo;
138
+ }
139
+ progreso(acciones) {
140
+ this.acciones = acciones;
141
+ }
142
+ preguntaRemota(texto, clase, opciones) {
143
+ const id = `p${Date.now().toString(36)}${(contadorDePreguntas++).toString(36)}`;
144
+ const fuera = seContestaDesdeFuera(clase);
145
+ let deFuera = null;
146
+ const valores = opciones.map((x) => x.valor);
147
+ if (this.activo && fuera)
148
+ deFuera = new Promise((resolver) => this.preguntas.set(id, { resolver, valores }));
149
+ const anterior = this.estado;
150
+ this.evento('pregunta', { id, texto, clase, opciones: opciones.map((x) => ({ etiqueta: x.etiqueta, valor: x.valor })), remota: fuera });
151
+ this.fijarEstado('pregunta');
152
+ return {
153
+ id,
154
+ deFuera,
155
+ contestada: (valor, desde) => {
156
+ this.preguntas.delete(id);
157
+ this.evento('respuesta', { id, valor, desde });
158
+ this.fijarEstado(anterior === 'pregunta' ? 'trabajando' : anterior);
159
+ },
160
+ };
161
+ }
162
+ async enviar() {
163
+ if (!this.activo || !this.id)
164
+ return;
165
+ if (this.enviando) {
166
+ this.otraVez = true;
167
+ return;
168
+ }
169
+ this.enviando = true;
170
+ try {
171
+ const tanda = this.cola.slice(0, 200);
172
+ const r = await this.api.latido(this.o.servidor, this.o.token, this.id, {
173
+ estado: this.estado,
174
+ tarea: this.tarea,
175
+ acciones: this.acciones,
176
+ modo: this.modo,
177
+ modelo: this.modelo,
178
+ eventos: tanda,
179
+ });
180
+ this.cola.splice(0, tanda.length);
181
+ this.ultimoEnvio = Date.now();
182
+ if (r.cerrado) {
183
+ this.apagar();
184
+ this.o.alApagarse('web');
185
+ return;
186
+ }
187
+ }
188
+ catch (e) {
189
+ if (this.cola.length > 2000)
190
+ this.cola.splice(0, this.cola.length - 2000);
191
+ if (e instanceof ErrorDeParallel && (e.codigo === 'sesion' || e.codigo === 'sin_acceso')) {
192
+ this.apagar();
193
+ this.o.alApagarse(e.codigo);
194
+ return;
195
+ }
196
+ }
197
+ finally {
198
+ this.enviando = false;
199
+ }
200
+ if (this.otraVez || (this.cola.length && !this.relojEnvio)) {
201
+ this.otraVez = false;
202
+ void this.enviar();
203
+ }
204
+ }
205
+ async escuchar() {
206
+ let pausa = 1000;
207
+ while (this.activo && this.id) {
208
+ this.escucha = new AbortController();
209
+ try {
210
+ const r = await this.api.ordenes(this.o.servidor, this.o.token, this.id, this.o.esperaMs ?? 25_000, this.escucha.signal);
211
+ pausa = 1000;
212
+ if (r.cerrado) {
213
+ this.apagar();
214
+ this.o.alApagarse('web');
215
+ return;
216
+ }
217
+ for (const orden of r.ordenes)
218
+ this.cumplir(orden);
219
+ }
220
+ catch (e) {
221
+ if (!this.activo)
222
+ return;
223
+ if (e instanceof ErrorDeParallel && (e.codigo === 'sesion' || e.codigo === 'sin_acceso')) {
224
+ this.apagar();
225
+ this.o.alApagarse(e.codigo);
226
+ return;
227
+ }
228
+ await new Promise((r) => setTimeout(r, pausa).unref?.());
229
+ pausa = Math.min(30_000, pausa * 2);
230
+ }
231
+ }
232
+ }
233
+ cumplir(orden) {
234
+ if (!this.activo)
235
+ return;
236
+ if (orden.tipo === 'mensaje') {
237
+ const texto = typeof orden.datos.texto === 'string' ? orden.datos.texto.trim().slice(0, MAX_TEXTO) : '';
238
+ const rutas = this.guardarFotos(orden);
239
+ const conFotos = [texto || (rutas.length ? 'Look at this photo.' : ''), ...rutas.map((r) => `"${r}"`)].filter(Boolean).join(' ');
240
+ if (conFotos)
241
+ this.o.alMensaje(conFotos);
242
+ return;
243
+ }
244
+ if (orden.tipo === 'parar') {
245
+ this.o.alParar();
246
+ return;
247
+ }
248
+ if (orden.tipo === 'respuesta') {
249
+ const id = String(orden.datos.pregunta ?? '');
250
+ const valor = orden.datos.valor;
251
+ const p = this.preguntas.get(id);
252
+ if (!p || (valor !== 'si' && valor !== 'siempre' && valor !== 'no') || !p.valores.includes(valor))
253
+ return;
254
+ this.preguntas.delete(id);
255
+ p.resolver(valor);
256
+ }
257
+ }
258
+ carpeta = null;
259
+ guardarFotos(orden) {
260
+ const fotos = Array.isArray(orden.datos.fotos) ? orden.datos.fotos.slice(0, 3) : [];
261
+ const rutas = [];
262
+ fotos.forEach((f, i) => {
263
+ const ext = f.mime === 'image/png' ? 'png' : f.mime === 'image/webp' ? 'webp' : f.mime === 'image/jpeg' ? 'jpg' : null;
264
+ if (!ext || typeof f.datos !== 'string' || !/^[A-Za-z0-9+/]+={0,2}$/.test(f.datos))
265
+ return;
266
+ const buf = Buffer.from(f.datos, 'base64');
267
+ if (buf.length < 16 || buf.length > 2_500_000)
268
+ return;
269
+ try {
270
+ this.carpeta ??= this.o.carpetaDeFotos ?? join(tmpdir(), `parx-remoto-${process.pid}`);
271
+ mkdirSync(this.carpeta, { recursive: true, mode: 0o700 });
272
+ const ruta = join(this.carpeta, `photo-${orden.id.replace(/[^a-z0-9-]/gi, '').slice(0, 12)}-${i + 1}.${ext}`);
273
+ writeFileSync(ruta, buf, { mode: 0o600 });
274
+ rutas.push(ruta);
275
+ }
276
+ catch {
277
+ }
278
+ });
279
+ return rutas;
280
+ }
281
+ get msDesdeElUltimoEnvio() {
282
+ return this.ultimoEnvio ? Date.now() - this.ultimoEnvio : Number.POSITIVE_INFINITY;
283
+ }
284
+ }