@pdc-test/chat-io 1.1.0 → 1.1.1
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/bin/cli.js +65 -14
- package/package.json +2 -2
- package/src/chat/index.js +78 -28
- package/src/server/index.js +40 -9
- package/src/server/worker.js +26 -17
package/bin/cli.js
CHANGED
|
@@ -33,16 +33,46 @@ function clearTopLine() {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// Interfaz del Teclado Local
|
|
36
|
-
const commands = ['/users', '/flip', '/rps', '/tiendita', '/help', '/w', '/all', '/blink'];
|
|
36
|
+
const commands = ['/users', '/flip', '/rps', '/tiendita', '/help', '/w', '/all', '/blink', '/exit', '/clear'];
|
|
37
|
+
let knownUsersList = [];
|
|
38
|
+
|
|
37
39
|
function completer(line) {
|
|
38
|
-
const
|
|
40
|
+
const tokens = line.split(" ");
|
|
41
|
+
let completions = [];
|
|
42
|
+
|
|
43
|
+
if (line.startsWith("@")) {
|
|
44
|
+
completions = knownUsersList.map(u => "@" + u);
|
|
45
|
+
} else if (line.toLowerCase().startsWith("/w ") || line.toLowerCase().startsWith("/chat ")) {
|
|
46
|
+
completions = knownUsersList.map(u => tokens[0] + " " + u + " ");
|
|
47
|
+
} else if (line.toLowerCase().startsWith("/rps ")) {
|
|
48
|
+
if (tokens.length === 2) {
|
|
49
|
+
completions = knownUsersList.map(u => "/rps " + u + " ");
|
|
50
|
+
} else if (tokens.length === 3) {
|
|
51
|
+
const rpsOpts = ["piedra", "papel", "tijera"];
|
|
52
|
+
completions = rpsOpts.map(o => `/rps ${tokens[1]} ${o}`);
|
|
53
|
+
}
|
|
54
|
+
} else if (line.includes("@")) {
|
|
55
|
+
const lastAt = line.lastIndexOf("@");
|
|
56
|
+
const prefix = line.substring(0, lastAt);
|
|
57
|
+
completions = knownUsersList.map(u => prefix + "@" + u + " ");
|
|
58
|
+
} else {
|
|
59
|
+
completions = [...commands, ...knownUsersList.map(u => "/w " + u)];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const hits = completions.filter((c) => c.toLowerCase().startsWith(line.toLowerCase()));
|
|
39
63
|
return [hits.length ? hits : [], line];
|
|
40
64
|
}
|
|
41
65
|
|
|
42
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, completer, prompt: '> ' });
|
|
66
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, completer, prompt: '> ', removeHistoryDuplicates: true });
|
|
67
|
+
|
|
68
|
+
rl.on('SIGINT', () => {
|
|
69
|
+
printMessage("\x1b[33mSaliendo...\x1b[0m");
|
|
70
|
+
ws.close();
|
|
71
|
+
process.exit(0);
|
|
72
|
+
});
|
|
43
73
|
|
|
44
74
|
process.stdin.on('keypress', (str, key) => {
|
|
45
|
-
if (key && key.name !== 'return' && key.name !== 'enter' && myName) {
|
|
75
|
+
if (key && key.name !== 'return' && key.name !== 'enter' && key.name !== 'c' && myName) {
|
|
46
76
|
if (!currentIsTyping && ws.readyState === WebSocket.OPEN) {
|
|
47
77
|
currentIsTyping = true;
|
|
48
78
|
ws.send(JSON.stringify({ type: "typing" }));
|
|
@@ -113,6 +143,10 @@ ws.on('message', (data) => {
|
|
|
113
143
|
activeTypers = res.users.filter(u => u !== myName);
|
|
114
144
|
break;
|
|
115
145
|
|
|
146
|
+
case "users_update":
|
|
147
|
+
if (res.plain) knownUsersList = res.plain.filter(u => u !== myName);
|
|
148
|
+
break;
|
|
149
|
+
|
|
116
150
|
case "ding":
|
|
117
151
|
process.stdout.write("\x07"); // ASCII Bell físico disparado remotamente
|
|
118
152
|
break;
|
|
@@ -203,7 +237,13 @@ rl.on('line', (input) => {
|
|
|
203
237
|
}
|
|
204
238
|
|
|
205
239
|
// Local Helper Menú
|
|
206
|
-
if (line === "/
|
|
240
|
+
if (line.toLowerCase() === "/clear") { console.clear(); rl.setPrompt('> '); rl.prompt(true); return; }
|
|
241
|
+
if (line.toLowerCase() === "/exit") {
|
|
242
|
+
printMessage("\x1b[33mSaliendo...\x1b[0m");
|
|
243
|
+
ws.close();
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
if (line.toLowerCase() === "/help") {
|
|
207
247
|
printMessage(`\n──────────── 💡 COMANDOS NATIVOS ────────────
|
|
208
248
|
👥 /users → Ver conectados remotamente
|
|
209
249
|
🪙 /flip → Lanzar moneda 60FPS
|
|
@@ -212,6 +252,8 @@ rl.on('line', (input) => {
|
|
|
212
252
|
🔒 /w <usr> [msj] → Modo Susurro directo a una persona
|
|
213
253
|
🌍 /all → Volver a la Sala Global Abierta
|
|
214
254
|
🏪 /tiendita → Letrero de Neón de colores acelerado
|
|
255
|
+
🧹 /clear → Limpia historial de consola
|
|
256
|
+
❌ /exit → Salir del chat
|
|
215
257
|
ℹ️ /help → Te ayuda desde la memoria Caché
|
|
216
258
|
─────────────────────────────────────────────\n`);
|
|
217
259
|
return;
|
|
@@ -219,18 +261,27 @@ rl.on('line', (input) => {
|
|
|
219
261
|
|
|
220
262
|
// Constructor Arquitectónico y Limpio de Comandos a Servidor
|
|
221
263
|
if (line.startsWith("/")) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
264
|
+
const lowerLine = line.toLowerCase();
|
|
265
|
+
if (lowerLine === "/users" || lowerLine === "/flip" || lowerLine === "/tiendita") {
|
|
266
|
+
ws.send(JSON.stringify({ type: "command", cmd: lowerLine.substring(1) }));
|
|
267
|
+
} else if (lowerLine === "/all") {
|
|
225
268
|
ws.send(JSON.stringify({ type: "command", cmd: "mode_all" }));
|
|
226
|
-
} else if (
|
|
269
|
+
} else if (lowerLine.startsWith("/w ")) {
|
|
227
270
|
const p = line.split(" ");
|
|
228
|
-
if (p.length === 2)
|
|
229
|
-
|
|
230
|
-
|
|
271
|
+
if (p.length === 2) {
|
|
272
|
+
ws.send(JSON.stringify({ type: "command", cmd: "mode_whisper", target: p[1] }));
|
|
273
|
+
} else if (p.length > 2) {
|
|
274
|
+
const inlineMsg = p.slice(2).join(" ").trim();
|
|
275
|
+
if (inlineMsg) {
|
|
276
|
+
ws.send(JSON.stringify({ type: "chat", target: p[1], msg: inlineMsg }));
|
|
277
|
+
} else {
|
|
278
|
+
ws.send(JSON.stringify({ type: "command", cmd: "mode_whisper", target: p[1] }));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} else if (lowerLine.startsWith("/rps ")) {
|
|
231
282
|
const p = line.split(" ");
|
|
232
|
-
if (p.length >= 3) ws.send(JSON.stringify({ type: "command", cmd: "rps", target: p[1], choice: p[2] }));
|
|
233
|
-
} else if (
|
|
283
|
+
if (p.length >= 3) ws.send(JSON.stringify({ type: "command", cmd: "rps", target: p[1], choice: p[2].toLowerCase() }));
|
|
284
|
+
} else if (lowerLine.startsWith("/blink ")) {
|
|
234
285
|
ws.send(JSON.stringify({ type: "command", cmd: "blink", msg: line.substring(7) }));
|
|
235
286
|
} else {
|
|
236
287
|
printMessage(`❌ Comando desconocido. Intenta /help`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pdc-test/chat-io",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"chat-io": "bin/cli.js"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"start": "node src/server/index.js"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@pdc-test/chat-io": "^1.1.
|
|
12
|
+
"@pdc-test/chat-io": "^1.1.1",
|
|
13
13
|
"crypto-js": "^4.2.0",
|
|
14
14
|
"ws": "^8.0.0"
|
|
15
15
|
}
|
package/src/chat/index.js
CHANGED
|
@@ -24,19 +24,39 @@ function startChat(onExit, mode = 'udp') {
|
|
|
24
24
|
let animationInterval;
|
|
25
25
|
|
|
26
26
|
function completer(line) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
const tokens = line.split(" ");
|
|
28
|
+
let completions = [];
|
|
29
|
+
const cmds = ["/all", "/clear", "/help", "/users", "/exit", "/flip", "/blink", "/chat", "/rps", "/w"];
|
|
30
|
+
const users = Array.from(knownUsers);
|
|
31
|
+
|
|
32
|
+
if (line.startsWith("@")) {
|
|
33
|
+
completions = users.map(u => "@" + u);
|
|
34
|
+
} else if (line.toLowerCase().startsWith("/w ") || line.toLowerCase().startsWith("/chat ")) {
|
|
35
|
+
completions = users.map(u => tokens[0] + " " + u + " ");
|
|
36
|
+
} else if (line.toLowerCase().startsWith("/rps ")) {
|
|
37
|
+
if (tokens.length === 2) {
|
|
38
|
+
completions = users.map(u => "/rps " + u + " ");
|
|
39
|
+
} else if (tokens.length === 3) {
|
|
40
|
+
const rpsOpts = ["piedra", "papel", "tijera"];
|
|
41
|
+
completions = rpsOpts.map(o => `/rps ${tokens[1]} ${o}`);
|
|
42
|
+
}
|
|
43
|
+
} else if (line.includes("@")) {
|
|
44
|
+
const lastAt = line.lastIndexOf("@");
|
|
45
|
+
const prefix = line.substring(0, lastAt);
|
|
46
|
+
completions = users.map(u => prefix + "@" + u + " ");
|
|
47
|
+
} else {
|
|
48
|
+
completions = [...cmds, ...users.map(u => "/w " + u)];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const hits = completions.filter((c) => c.toLowerCase().startsWith(line.toLowerCase()));
|
|
33
52
|
return [hits.length ? hits : completions, line];
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
const rl = readline.createInterface({
|
|
37
56
|
input: process.stdin,
|
|
38
57
|
output: process.stdout,
|
|
39
|
-
completer: completer
|
|
58
|
+
completer: completer,
|
|
59
|
+
removeHistoryDuplicates: true
|
|
40
60
|
});
|
|
41
61
|
|
|
42
62
|
animationInterval = setInterval(() => {
|
|
@@ -114,7 +134,10 @@ function startChat(onExit, mode = 'udp') {
|
|
|
114
134
|
return;
|
|
115
135
|
}
|
|
116
136
|
|
|
117
|
-
|
|
137
|
+
// Resolving case insensitivity for target
|
|
138
|
+
const isTargetMe = data.to && data.to.toLowerCase() === username.toLowerCase();
|
|
139
|
+
|
|
140
|
+
if (data.type === "rps_challenge" && isTargetMe) {
|
|
118
141
|
incomingRPS.add(data.from);
|
|
119
142
|
readline.clearLine(process.stdout, 0);
|
|
120
143
|
readline.cursorTo(process.stdout, 0);
|
|
@@ -124,11 +147,13 @@ function startChat(onExit, mode = 'udp') {
|
|
|
124
147
|
return;
|
|
125
148
|
}
|
|
126
149
|
|
|
127
|
-
if (data.type === "rps_response" &&
|
|
128
|
-
|
|
129
|
-
|
|
150
|
+
if (data.type === "rps_response" && isTargetMe) {
|
|
151
|
+
// Allow case insensitive checking for pendingRPS
|
|
152
|
+
const pendingKey = Array.from(pendingRPS.keys()).find(k => k.toLowerCase() === data.from.toLowerCase());
|
|
153
|
+
if (pendingKey) {
|
|
154
|
+
const miJugada = pendingRPS.get(pendingKey).miJugada;
|
|
130
155
|
const suJugada = data.choice;
|
|
131
|
-
pendingRPS.delete(
|
|
156
|
+
pendingRPS.delete(pendingKey);
|
|
132
157
|
|
|
133
158
|
let resultStr = "😐 Es un empate.";
|
|
134
159
|
let win = false;
|
|
@@ -172,7 +197,7 @@ function startChat(onExit, mode = 'udp') {
|
|
|
172
197
|
}
|
|
173
198
|
|
|
174
199
|
if (!data.text) return;
|
|
175
|
-
if (data.type === "message" && data.to && data.to !== username) return;
|
|
200
|
+
if (data.type === "message" && data.to && data.to.toLowerCase() !== username.toLowerCase() && data.from.toLowerCase() !== username.toLowerCase()) return;
|
|
176
201
|
|
|
177
202
|
readline.clearLine(process.stdout, 0);
|
|
178
203
|
readline.cursorTo(process.stdout, 0);
|
|
@@ -257,12 +282,19 @@ function startChat(onExit, mode = 'udp') {
|
|
|
257
282
|
}, 200);
|
|
258
283
|
}
|
|
259
284
|
|
|
285
|
+
rl.on('SIGINT', () => {
|
|
286
|
+
cleanupAndExit();
|
|
287
|
+
});
|
|
288
|
+
|
|
260
289
|
rl.on("line", (line) => {
|
|
261
290
|
if (!username) return;
|
|
262
291
|
const input = line.trim();
|
|
292
|
+
if (!input) { updatePrompt(); return; }
|
|
293
|
+
|
|
294
|
+
const inputLower = input.toLowerCase();
|
|
263
295
|
|
|
264
|
-
if (
|
|
265
|
-
if (
|
|
296
|
+
if (inputLower === "/exit") { cleanupAndExit(); return; }
|
|
297
|
+
if (inputLower === "/users") {
|
|
266
298
|
console.log("──────────── 👥 USUARIOS CONECTADOS ────────────");
|
|
267
299
|
const usersArr = Array.from(knownUsers);
|
|
268
300
|
if (usersArr.length === 0) console.log(" 👻 No hay otros usuarios todavía.");
|
|
@@ -271,22 +303,39 @@ function startChat(onExit, mode = 'udp') {
|
|
|
271
303
|
updatePrompt();
|
|
272
304
|
return;
|
|
273
305
|
}
|
|
274
|
-
if (
|
|
275
|
-
if (
|
|
306
|
+
if (inputLower === "/clear") { console.clear(); updatePrompt(); return; }
|
|
307
|
+
if (inputLower === "/help") {
|
|
276
308
|
console.log("──────────── 💡 COMANDOS DISPONIBLES ────────────");
|
|
277
309
|
console.log(" 💬 /chat <usuario> → Iniciar chat privado");
|
|
310
|
+
console.log(" 💬 /w <usuario> → Susurro / chat privado");
|
|
278
311
|
console.log(" 🌍 /all → Volver al chat público");
|
|
279
312
|
console.log(" 🪙 /flip → Lanzar una moneda");
|
|
280
313
|
console.log(" 🎮 /rps <usr> <j> → Retar a Piedra/Papel/Tijera");
|
|
281
314
|
console.log(" 🌟 /blink <msj> → Enviar msj parpadeante");
|
|
315
|
+
console.log(" 🧹 /clear → Limpiar historial de consola");
|
|
282
316
|
console.log(" ❌ /exit → Salir al menú");
|
|
283
317
|
updatePrompt();
|
|
284
318
|
return;
|
|
285
319
|
}
|
|
286
|
-
if (
|
|
287
|
-
if (
|
|
320
|
+
if (inputLower === "/all") { currentChat = ""; console.log("🌍 Chat público"); updatePrompt(); return; }
|
|
321
|
+
if (inputLower.startsWith("/chat ") || inputLower.startsWith("/w ")) {
|
|
322
|
+
const parts = input.split(" ");
|
|
323
|
+
currentChat = parts[1] || "";
|
|
324
|
+
if (parts.length > 2) {
|
|
325
|
+
const inlineMsg = parts.slice(2).join(" ").trim();
|
|
326
|
+
if (inlineMsg) {
|
|
327
|
+
broadcastData(encrypt(JSON.stringify({ type: "message", from: username, to: currentChat, text: inlineMsg, blink: false })));
|
|
328
|
+
readline.moveCursor(process.stdout, 0, -1);
|
|
329
|
+
readline.clearLine(process.stdout, 0);
|
|
330
|
+
console.log(`🔒 [Tú ${getUserEmoji(username)} → ${getUserEmoji(currentChat)} ${currentChat}]: ${inlineMsg}`);
|
|
331
|
+
updatePrompt();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log(`🔒 Chat privado con ${currentChat}`); updatePrompt(); return;
|
|
336
|
+
}
|
|
288
337
|
|
|
289
|
-
if (
|
|
338
|
+
if (inputLower === "/flip") {
|
|
290
339
|
const choices = ["CARA", "ESCUDO"];
|
|
291
340
|
const result = choices[Math.floor(Math.random() * choices.length)];
|
|
292
341
|
console.log(`🪙 Lanzaste la moneda y salió: \x1b[1m\x1b[33m${result}\x1b[0m`);
|
|
@@ -295,19 +344,20 @@ function startChat(onExit, mode = 'udp') {
|
|
|
295
344
|
return;
|
|
296
345
|
}
|
|
297
346
|
|
|
298
|
-
if (
|
|
347
|
+
if (inputLower.startsWith("/rps ")) {
|
|
299
348
|
const parts = input.split(" ");
|
|
300
349
|
if (parts.length < 3) { console.log("❌ Usa: /rps <usuario> <piedra|papel|tijera>"); updatePrompt(); return; }
|
|
301
350
|
|
|
302
351
|
const targetUser = parts[1];
|
|
303
352
|
const jugada = parts[2].toLowerCase();
|
|
304
353
|
if (!["piedra", "papel", "tijera"].includes(jugada)) { console.log("❌ piedra, papel o tijera."); updatePrompt(); return; }
|
|
305
|
-
if (targetUser === username) { console.log("❌ No puedes jugar contigo mismo."); updatePrompt(); return; }
|
|
354
|
+
if (targetUser.toLowerCase() === username.toLowerCase()) { console.log("❌ No puedes jugar contigo mismo."); updatePrompt(); return; }
|
|
306
355
|
|
|
307
|
-
|
|
308
|
-
|
|
356
|
+
const incomingKey = Array.from(incomingRPS).find(k => k.toLowerCase() === targetUser.toLowerCase());
|
|
357
|
+
if (incomingKey) {
|
|
358
|
+
incomingRPS.delete(incomingKey);
|
|
309
359
|
console.log(`🎮 Respuesta enviada (${jugada})...`);
|
|
310
|
-
broadcastData(encrypt(JSON.stringify({ type: "rps_response", from: username, to:
|
|
360
|
+
broadcastData(encrypt(JSON.stringify({ type: "rps_response", from: username, to: incomingKey, choice: jugada })));
|
|
311
361
|
} else {
|
|
312
362
|
pendingRPS.set(targetUser, { miJugada: jugada, time: Date.now() });
|
|
313
363
|
console.log(`🎮 Retaste a ${targetUser} con ${jugada}...`);
|
|
@@ -317,7 +367,7 @@ function startChat(onExit, mode = 'udp') {
|
|
|
317
367
|
return;
|
|
318
368
|
}
|
|
319
369
|
|
|
320
|
-
if (
|
|
370
|
+
if (inputLower.startsWith("/") && !inputLower.startsWith("/blink ")) {
|
|
321
371
|
console.log("❌ Comando no reconocido.");
|
|
322
372
|
updatePrompt();
|
|
323
373
|
return;
|
|
@@ -325,9 +375,9 @@ function startChat(onExit, mode = 'udp') {
|
|
|
325
375
|
|
|
326
376
|
let textToSend = input;
|
|
327
377
|
let isBlink = false;
|
|
328
|
-
if (
|
|
378
|
+
if (inputLower.startsWith("/blink ")) {
|
|
329
379
|
isBlink = true;
|
|
330
|
-
textToSend = input.
|
|
380
|
+
textToSend = input.substring(7).trim();
|
|
331
381
|
if (!textToSend) { updatePrompt(); return; }
|
|
332
382
|
} else if (!input) { updatePrompt(); return; }
|
|
333
383
|
|
package/src/server/index.js
CHANGED
|
@@ -19,21 +19,42 @@ function broadcast(jsonObj, ignoreWs = null) {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
function sendToTarget(targetName, jsonObj) {
|
|
22
|
+
if (!targetName) return;
|
|
22
23
|
const payload = JSON.stringify(jsonObj);
|
|
23
24
|
for (const [s, data] of sessions.entries()) {
|
|
24
|
-
if (data.name === targetName && s.readyState === WebSocket.OPEN) {
|
|
25
|
+
if (data.name && data.name.toLowerCase() === targetName.toLowerCase() && s.readyState === WebSocket.OPEN) {
|
|
25
26
|
try { s.send(payload); } catch(e) {}
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function targetExists(name) {
|
|
31
|
-
|
|
32
|
+
if (!name) return false;
|
|
33
|
+
for (const d of sessions.values()) if (d.name && d.name.toLowerCase() === name.toLowerCase()) return true;
|
|
32
34
|
return false;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
function getExactName(name) {
|
|
38
|
+
if (!name) return null;
|
|
39
|
+
for (const d of sessions.values()) if (d.name && d.name.toLowerCase() === name.toLowerCase()) return d.name;
|
|
40
|
+
return name;
|
|
41
|
+
}
|
|
42
|
+
|
|
35
43
|
// State para Tiping
|
|
36
44
|
let typingMap = new Map();
|
|
45
|
+
|
|
46
|
+
function broadcastUsers() {
|
|
47
|
+
const list = [];
|
|
48
|
+
const plainList = [];
|
|
49
|
+
for(const d of sessions.values()) {
|
|
50
|
+
if(d.name) {
|
|
51
|
+
list.push(`${d.emoji} ${d.name}`);
|
|
52
|
+
plainList.push(d.name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
broadcast({ type: "users_update", users: list, plain: plainList });
|
|
56
|
+
}
|
|
57
|
+
|
|
37
58
|
function broadcastTyping() {
|
|
38
59
|
const list = Array.from(typingMap.keys());
|
|
39
60
|
broadcast({ type: "typing_event", users: list });
|
|
@@ -64,6 +85,7 @@ wss.on("connection", (ws) => {
|
|
|
64
85
|
|
|
65
86
|
ws.send(JSON.stringify({ type: "registered", name: user.name, emoji: user.emoji }));
|
|
66
87
|
broadcast({ type: "system", msg: `🟢 [\x1b[32m${emoji} ${user.name}\x1b[0m] se ha unido al servidor.` }, ws);
|
|
88
|
+
broadcastUsers();
|
|
67
89
|
return;
|
|
68
90
|
}
|
|
69
91
|
|
|
@@ -108,9 +130,10 @@ wss.on("connection", (ws) => {
|
|
|
108
130
|
ws.send(JSON.stringify({ type: "error", msg: `${target} no está conectado.` }));
|
|
109
131
|
return;
|
|
110
132
|
}
|
|
111
|
-
const
|
|
133
|
+
const exactTarget = getExactName(target);
|
|
134
|
+
const pMsg = { type: "chat", from: myName, emoji: user.emoji, msg: req.msg, isWhisper: true, to: exactTarget };
|
|
112
135
|
ws.send(JSON.stringify(pMsg));
|
|
113
|
-
sendToTarget(
|
|
136
|
+
sendToTarget(exactTarget, pMsg);
|
|
114
137
|
} else {
|
|
115
138
|
const globalMsg = { type: "chat", from: myName, emoji: user.emoji, msg: req.msg, isWhisper: false };
|
|
116
139
|
ws.send(JSON.stringify(globalMsg));
|
|
@@ -169,8 +192,13 @@ wss.on("connection", (ws) => {
|
|
|
169
192
|
}
|
|
170
193
|
}
|
|
171
194
|
else if (req.cmd === "mode_whisper") {
|
|
172
|
-
|
|
173
|
-
|
|
195
|
+
const exactTarget = getExactName(req.target);
|
|
196
|
+
if (!targetExists(exactTarget)) {
|
|
197
|
+
ws.send(JSON.stringify({ type: "error", msg: `${req.target} no está conectado.` }));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
user.privateTarget = exactTarget;
|
|
201
|
+
ws.send(JSON.stringify({ type: "system", msg: `🔒 \x1b[35mModo Privado Fijo con ${exactTarget}.\x1b[0m Usa /all para salir.` }));
|
|
174
202
|
}
|
|
175
203
|
else if (req.cmd === "mode_all") {
|
|
176
204
|
user.privateTarget = null;
|
|
@@ -196,9 +224,12 @@ wss.on("connection", (ws) => {
|
|
|
196
224
|
}
|
|
197
225
|
}
|
|
198
226
|
sessions.delete(ws);
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
227
|
+
if (u && u.name) {
|
|
228
|
+
broadcastUsers();
|
|
229
|
+
pendingRPS.delete(u.name);
|
|
230
|
+
for(const [t, data] of pendingRPS.entries()) {
|
|
231
|
+
if(data.from === u.name) pendingRPS.delete(t);
|
|
232
|
+
}
|
|
202
233
|
}
|
|
203
234
|
});
|
|
204
235
|
});
|
package/src/server/worker.js
CHANGED
|
@@ -50,10 +50,10 @@ export default {
|
|
|
50
50
|
text = text.trim();
|
|
51
51
|
|
|
52
52
|
if (!socketData.name) {
|
|
53
|
-
if (text.startsWith("/name ")) {
|
|
54
|
-
const desiredName = text.
|
|
53
|
+
if (text.toLowerCase().startsWith("/name ")) {
|
|
54
|
+
const desiredName = text.substring(6).trim();
|
|
55
55
|
let alreadyExists = false;
|
|
56
|
-
for (const d of sessions.values()) if (d.name === desiredName) alreadyExists = true;
|
|
56
|
+
for (const d of sessions.values()) if (d.name && d.name.toLowerCase() === desiredName.toLowerCase()) alreadyExists = true;
|
|
57
57
|
|
|
58
58
|
if (alreadyExists) {
|
|
59
59
|
server.send("❌ Ese nombre ya está en uso. Usa /name con otro.");
|
|
@@ -70,20 +70,21 @@ export default {
|
|
|
70
70
|
|
|
71
71
|
const myName = socketData.name;
|
|
72
72
|
const myEmoji = getUserEmoji(myName);
|
|
73
|
+
const textLower = text.toLowerCase();
|
|
73
74
|
|
|
74
|
-
if (
|
|
75
|
+
if (textLower === "/help") {
|
|
75
76
|
server.send(`\n──────────── 💡 COMANDOS CENTRALIZADOS ────────────\n 👥 /users → Ver conectados\n 🪙 /flip → Lanzar moneda al aire\n 🎮 /rps <usr> <j> → Piedra-Papel-Tijera\n 🌟 /blink <msj> → Mensaje parpadeante\n ℹ️ /help → Esta ayuda\n───────────────────────────────────────────────────\n`);
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
if (
|
|
80
|
+
if (textLower === "/users") {
|
|
80
81
|
let userList = [];
|
|
81
82
|
for (const d of sessions.values()) if (d.name) userList.push(`${getUserEmoji(d.name)} ${d.name}`);
|
|
82
83
|
server.send("\n👥 \x1b[36mCONECTADOS AHORA:\x1b[0m " + userList.join(", ") + "\n");
|
|
83
84
|
return;
|
|
84
85
|
}
|
|
85
86
|
|
|
86
|
-
if (
|
|
87
|
+
if (textLower === "/flip") {
|
|
87
88
|
const result = Math.random() < 0.5 ? "CARA" : "ESCUDO";
|
|
88
89
|
const msg = `\n🪙 [${myEmoji} ${myName}] ha lanzado la moneda y cayó en: \x1b[1m\x1b[33m${result}\x1b[0m!\n`;
|
|
89
90
|
server.send(msg); // <--- ECHO LOCAL agregado
|
|
@@ -91,24 +92,32 @@ export default {
|
|
|
91
92
|
return;
|
|
92
93
|
}
|
|
93
94
|
|
|
94
|
-
if (
|
|
95
|
-
const blnkText = text.
|
|
95
|
+
if (textLower.startsWith("/blink ")) {
|
|
96
|
+
const blnkText = text.substring(7).trim();
|
|
96
97
|
const msg = `\n🌟 [${myEmoji} ${myName}]: \x1b[5m${blnkText}\x1b[0m\n`;
|
|
97
98
|
server.send(msg); // <--- ECHO LOCAL agregado
|
|
98
99
|
broadcast(msg, server);
|
|
99
100
|
return;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
|
-
if (
|
|
103
|
+
if (textLower.startsWith("/rps ")) {
|
|
103
104
|
const parts = text.split(" ");
|
|
104
105
|
if (parts.length < 3) { server.send("❌ Error: /rps <usuario> <piedra|papel|tijera>"); return; }
|
|
105
106
|
const target = parts[1];
|
|
106
107
|
const choice = parts[2].toLowerCase();
|
|
107
108
|
|
|
109
|
+
let exactTargetName = target;
|
|
110
|
+
for (const d of sessions.values()) {
|
|
111
|
+
if (d.name && d.name.toLowerCase() === target.toLowerCase()) {
|
|
112
|
+
exactTargetName = d.name;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
108
117
|
if (!["piedra", "papel", "tijera"].includes(choice)) { server.send("❌ Elige piedra, papel o tijera."); return; }
|
|
109
|
-
if (
|
|
118
|
+
if (exactTargetName.toLowerCase() === myName.toLowerCase()) { server.send("❌ No contra ti mismo."); return; }
|
|
110
119
|
|
|
111
|
-
if (pendingRPS.has(myName) && pendingRPS.get(myName).from ===
|
|
120
|
+
if (pendingRPS.has(myName) && pendingRPS.get(myName).from.toLowerCase() === exactTargetName.toLowerCase()) {
|
|
112
121
|
const retoAnterior = pendingRPS.get(myName);
|
|
113
122
|
pendingRPS.delete(myName);
|
|
114
123
|
|
|
@@ -118,20 +127,20 @@ export default {
|
|
|
118
127
|
let winStr;
|
|
119
128
|
if (miJugada === suJugada) { winStr = "😐 Empate"; }
|
|
120
129
|
else if ( (miJugada === "piedra" && suJugada === "tijera") || (miJugada === "papel" && suJugada === "piedra") || (miJugada === "tijera" && suJugada === "papel") ) { winStr = `🎉 \x1b[32m${myName}!\x1b[0m`; }
|
|
121
|
-
else { winStr = `🎉 \x1b[32m${
|
|
130
|
+
else { winStr = `🎉 \x1b[32m${exactTargetName}!\x1b[0m`; }
|
|
122
131
|
|
|
123
|
-
const outMsg = `\n🕹️ RESULTADO RPS 🕹️\n⚔️ \x1b[36m${
|
|
132
|
+
const outMsg = `\n🕹️ RESULTADO RPS 🕹️\n⚔️ \x1b[36m${exactTargetName}\x1b[0m (${suJugada}) VS \x1b[36m${myName}\x1b[0m (${miJugada})\n🏆 Ganador: ${winStr}\n`;
|
|
124
133
|
server.send(outMsg); // ECHO LOCAL
|
|
125
134
|
broadcast(outMsg, server);
|
|
126
135
|
} else {
|
|
127
|
-
pendingRPS.set(
|
|
128
|
-
server.send(`\n🎮 \x1b[33mTu reto (${choice}) ha sido enviado a ${
|
|
129
|
-
sendToTarget(
|
|
136
|
+
pendingRPS.set(exactTargetName, { from: myName, miJugada: choice });
|
|
137
|
+
server.send(`\n🎮 \x1b[33mTu reto (${choice}) ha sido enviado a ${exactTargetName}.\x1b[0m`);
|
|
138
|
+
sendToTarget(exactTargetName, `\n\x1b[5m\x1b[35m🚨 ¡${myName} te reta a RPS!\x1b[0m\n👉 Responde: /rps ${myName} <piedra|papel|tijera>\n`);
|
|
130
139
|
}
|
|
131
140
|
return;
|
|
132
141
|
}
|
|
133
142
|
|
|
134
|
-
if (
|
|
143
|
+
if (textLower.startsWith("/")) {
|
|
135
144
|
server.send("❌ Comando no reconocido. Usa /help");
|
|
136
145
|
return;
|
|
137
146
|
}
|