cheatchat 1.0.0 → 1.2.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/package.json +2 -1
- package/src/commands/ai.js +79 -0
- package/src/commands/connect.js +29 -0
- package/src/commands/help.js +16 -0
- package/src/commands/solve.js +87 -0
- package/src/index.js +16 -0
- package/src/services/api.js +28 -0
- package/src/services/socket.js +31 -0
- package/src/ui/printer.js +64 -0
- package/src/websocket/receiver.js +47 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cheatchat",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"bin": {
|
|
6
6
|
"chat": "./bin/chat.js"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"cli-table3": "^0.6.5",
|
|
28
28
|
"commander": "^15.0.0",
|
|
29
29
|
"dotenv": "^17.4.2",
|
|
30
|
+
"fs-extra": "^11.3.6",
|
|
30
31
|
"inquirer": "^14.0.2",
|
|
31
32
|
"ora": "^9.4.1",
|
|
32
33
|
"ws": "^8.21.0"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
import { aiChat } from "../services/api.js";
|
|
3
|
+
import { showBanner } from "../ui/banner.js";
|
|
4
|
+
import { createSpinner } from "../ui/spinner.js";
|
|
5
|
+
import {
|
|
6
|
+
printBlank,
|
|
7
|
+
printSystem,
|
|
8
|
+
printError,
|
|
9
|
+
} from "../ui/printer.js";
|
|
10
|
+
import { brand, accent, muted, whiteBold, success as successColor } from "../ui/color.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Interactive AI chat mode.
|
|
14
|
+
* Completely separate from the main chat application.
|
|
15
|
+
*/
|
|
16
|
+
export async function aiCommand() {
|
|
17
|
+
showBanner();
|
|
18
|
+
printBlank();
|
|
19
|
+
console.log(` ${whiteBold("🤖 AI Chat Mode")}`);
|
|
20
|
+
console.log(` ${muted("Ask anything. Type")} ${brand("exit")} ${muted("to quit.")}`);
|
|
21
|
+
printBlank();
|
|
22
|
+
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout,
|
|
26
|
+
prompt: ` ${accent("You")} ${brand("›")} `,
|
|
27
|
+
terminal: true,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
rl.prompt();
|
|
31
|
+
|
|
32
|
+
rl.on("line", async (input) => {
|
|
33
|
+
const line = input.trim();
|
|
34
|
+
|
|
35
|
+
if (!line) {
|
|
36
|
+
rl.prompt();
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (line.toLowerCase() === "exit") {
|
|
41
|
+
printBlank();
|
|
42
|
+
printSystem("Exiting AI chat. Goodbye! 👋");
|
|
43
|
+
printBlank();
|
|
44
|
+
rl.close();
|
|
45
|
+
process.exit(0);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const spinner = createSpinner("Thinking...");
|
|
50
|
+
spinner.start();
|
|
51
|
+
|
|
52
|
+
const result = await aiChat(line);
|
|
53
|
+
|
|
54
|
+
if (result.reply) {
|
|
55
|
+
spinner.stop();
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(` ${successColor("AI")} ${brand("›")} ${result.reply}`);
|
|
58
|
+
console.log();
|
|
59
|
+
} else {
|
|
60
|
+
spinner.fail("AI error");
|
|
61
|
+
printError(result.error || "Failed to get a response");
|
|
62
|
+
printBlank();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
rl.prompt();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
rl.on("close", () => {
|
|
69
|
+
process.exit(0);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
process.on("SIGINT", () => {
|
|
73
|
+
printBlank();
|
|
74
|
+
printSystem("Exiting AI chat. Goodbye! 👋");
|
|
75
|
+
printBlank();
|
|
76
|
+
rl.close();
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
79
|
+
}
|
package/src/commands/connect.js
CHANGED
|
@@ -4,6 +4,7 @@ import { connectWebSocket } from "../websocket/connect.js";
|
|
|
4
4
|
import { disconnectWebSocket } from "../websocket/disconnect.js";
|
|
5
5
|
import { handleIncomingMessage } from "../websocket/receiver.js";
|
|
6
6
|
import { sendMessage } from "../websocket/sender.js";
|
|
7
|
+
import { requestHistory, requestOnline, requestUsers } from "../services/socket.js";
|
|
7
8
|
import { showChatHeader } from "../ui/banner.js";
|
|
8
9
|
import {
|
|
9
10
|
printChatHelp,
|
|
@@ -109,8 +110,13 @@ export async function connectCommand() {
|
|
|
109
110
|
showChatHeader(username, chatMode, dmTarget);
|
|
110
111
|
printBlank();
|
|
111
112
|
printSystem("You are in Global Chat. Type a message or /help for commands.");
|
|
113
|
+
printSystem("Loading chat history...");
|
|
112
114
|
printBlank();
|
|
113
115
|
|
|
116
|
+
// ─── Auto-load global history on connect ─────────────────────────────
|
|
117
|
+
|
|
118
|
+
requestHistory(ws, "global");
|
|
119
|
+
|
|
114
120
|
// ─── Start Interactive Readline ──────────────────────────────────────
|
|
115
121
|
|
|
116
122
|
rl = readline.createInterface({
|
|
@@ -141,6 +147,8 @@ export async function connectCommand() {
|
|
|
141
147
|
chatMode = CHAT_MODE.GLOBAL;
|
|
142
148
|
dmTarget = null;
|
|
143
149
|
printModeSwitch("global");
|
|
150
|
+
// Auto-load global history on mode switch
|
|
151
|
+
requestHistory(ws, "global");
|
|
144
152
|
refreshPrompt();
|
|
145
153
|
return;
|
|
146
154
|
|
|
@@ -159,10 +167,31 @@ export async function connectCommand() {
|
|
|
159
167
|
chatMode = CHAT_MODE.PRIVATE;
|
|
160
168
|
dmTarget = target;
|
|
161
169
|
printModeSwitch("private", target);
|
|
170
|
+
// Auto-load private history when switching to DM
|
|
171
|
+
requestHistory(ws, "private", target);
|
|
162
172
|
refreshPrompt();
|
|
163
173
|
return;
|
|
164
174
|
}
|
|
165
175
|
|
|
176
|
+
case "/history":
|
|
177
|
+
if (chatMode === CHAT_MODE.GLOBAL) {
|
|
178
|
+
requestHistory(ws, "global");
|
|
179
|
+
} else if (chatMode === CHAT_MODE.PRIVATE && dmTarget) {
|
|
180
|
+
requestHistory(ws, "private", dmTarget);
|
|
181
|
+
}
|
|
182
|
+
rl.prompt();
|
|
183
|
+
return;
|
|
184
|
+
|
|
185
|
+
case "/online":
|
|
186
|
+
requestOnline(ws);
|
|
187
|
+
rl.prompt();
|
|
188
|
+
return;
|
|
189
|
+
|
|
190
|
+
case "/users":
|
|
191
|
+
requestUsers(ws);
|
|
192
|
+
rl.prompt();
|
|
193
|
+
return;
|
|
194
|
+
|
|
166
195
|
case "/help":
|
|
167
196
|
printChatHelp();
|
|
168
197
|
rl.prompt();
|
package/src/commands/help.js
CHANGED
|
@@ -27,6 +27,9 @@ export function helpCommand() {
|
|
|
27
27
|
const chatCommands = [
|
|
28
28
|
["/global", "Switch to global chat"],
|
|
29
29
|
["/dm <username>", "Switch to private messaging"],
|
|
30
|
+
["/history", "Load message history"],
|
|
31
|
+
["/online", "Show online users"],
|
|
32
|
+
["/users", "Show all registered users"],
|
|
30
33
|
["/help", "Show chat commands"],
|
|
31
34
|
["/exit", "Disconnect and exit"],
|
|
32
35
|
];
|
|
@@ -35,5 +38,18 @@ export function helpCommand() {
|
|
|
35
38
|
console.log(` ${accent(cmd.padEnd(20))} ${muted(desc)}`);
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
printBlank();
|
|
42
|
+
console.log(` ${whiteBold("AI Tools:")}`);
|
|
43
|
+
printBlank();
|
|
44
|
+
|
|
45
|
+
const aiCommands = [
|
|
46
|
+
["chat ai", "Interactive AI chat"],
|
|
47
|
+
["chat solve <file>", "Solve/improve a file with AI"],
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
for (const [cmd, desc] of aiCommands) {
|
|
51
|
+
console.log(` ${brand(cmd.padEnd(20))} ${muted(desc)}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
38
54
|
printBlank();
|
|
39
55
|
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { aiFile } from "../services/api.js";
|
|
4
|
+
import { showBanner } from "../ui/banner.js";
|
|
5
|
+
import { createSpinner } from "../ui/spinner.js";
|
|
6
|
+
import {
|
|
7
|
+
printBlank,
|
|
8
|
+
printSuccess,
|
|
9
|
+
printError,
|
|
10
|
+
} from "../ui/printer.js";
|
|
11
|
+
import { muted } from "../ui/color.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Strip markdown code fences from AI response (client-side safety net).
|
|
15
|
+
*/
|
|
16
|
+
function stripCodeFences(text) {
|
|
17
|
+
const trimmed = text.trim();
|
|
18
|
+
const fenceRegex = /^```[\w]*\n?([\s\S]*?)\n?```$/;
|
|
19
|
+
const match = trimmed.match(fenceRegex);
|
|
20
|
+
if (match) {
|
|
21
|
+
return match[1];
|
|
22
|
+
}
|
|
23
|
+
return trimmed;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Solve/improve a file using AI.
|
|
28
|
+
* Reads the file, sends it to POST /ai/file, overwrites with the result.
|
|
29
|
+
*/
|
|
30
|
+
export async function solveCommand(filePath) {
|
|
31
|
+
showBanner();
|
|
32
|
+
printBlank();
|
|
33
|
+
|
|
34
|
+
// Resolve the file path relative to cwd
|
|
35
|
+
const resolvedPath = path.resolve(process.cwd(), filePath);
|
|
36
|
+
const filename = path.basename(resolvedPath);
|
|
37
|
+
|
|
38
|
+
// Check if file exists
|
|
39
|
+
if (!(await fs.pathExists(resolvedPath))) {
|
|
40
|
+
printError(`File not found: ${resolvedPath}`);
|
|
41
|
+
printBlank();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check it's a file, not a directory
|
|
46
|
+
const stat = await fs.stat(resolvedPath);
|
|
47
|
+
if (!stat.isFile()) {
|
|
48
|
+
printError(`Not a file: ${resolvedPath}`);
|
|
49
|
+
printBlank();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Read file content
|
|
54
|
+
const content = await fs.readFile(resolvedPath, "utf-8");
|
|
55
|
+
|
|
56
|
+
if (content.trim().length === 0) {
|
|
57
|
+
printError("File is empty. Nothing to solve.");
|
|
58
|
+
printBlank();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(` ${muted("File:")} ${filename}`);
|
|
63
|
+
console.log(` ${muted("Size:")} ${content.length} characters`);
|
|
64
|
+
printBlank();
|
|
65
|
+
|
|
66
|
+
const spinner = createSpinner("🤖 Solving...");
|
|
67
|
+
spinner.start();
|
|
68
|
+
|
|
69
|
+
const result = await aiFile(filename, content);
|
|
70
|
+
|
|
71
|
+
if (result.content) {
|
|
72
|
+
// Strip code fences if present (client-side safety)
|
|
73
|
+
const cleanContent = stripCodeFences(result.content);
|
|
74
|
+
|
|
75
|
+
// Overwrite the original file
|
|
76
|
+
await fs.writeFile(resolvedPath, cleanContent, "utf-8");
|
|
77
|
+
|
|
78
|
+
spinner.succeed("🤖 Solving complete!");
|
|
79
|
+
printBlank();
|
|
80
|
+
printSuccess(`File updated: ${resolvedPath}`);
|
|
81
|
+
} else {
|
|
82
|
+
spinner.fail("Solving failed");
|
|
83
|
+
printError(result.error || "Failed to process the file");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
printBlank();
|
|
87
|
+
}
|
package/src/index.js
CHANGED
|
@@ -5,6 +5,8 @@ import { loginCommand } from "./commands/login.js";
|
|
|
5
5
|
import { logoutCommand } from "./commands/logout.js";
|
|
6
6
|
import { helpCommand } from "./commands/help.js";
|
|
7
7
|
import { connectCommand } from "./commands/connect.js";
|
|
8
|
+
import { aiCommand } from "./commands/ai.js";
|
|
9
|
+
import { solveCommand } from "./commands/solve.js";
|
|
8
10
|
import { whoamiFlow } from "./auth/whoami.js";
|
|
9
11
|
import { APP_NAME, APP_VERSION, APP_DESCRIPTION } from "./utils/constants.js";
|
|
10
12
|
|
|
@@ -67,6 +69,20 @@ program
|
|
|
67
69
|
helpCommand();
|
|
68
70
|
});
|
|
69
71
|
|
|
72
|
+
program
|
|
73
|
+
.command("ai")
|
|
74
|
+
.description("Enter interactive AI chat mode")
|
|
75
|
+
.action(async () => {
|
|
76
|
+
await aiCommand();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
program
|
|
80
|
+
.command("solve <file>")
|
|
81
|
+
.description("Solve/improve a source file using AI")
|
|
82
|
+
.action(async (file) => {
|
|
83
|
+
await solveCommand(file);
|
|
84
|
+
});
|
|
85
|
+
|
|
70
86
|
// ─── Default (no command) ────────────────────────────────────────────────────
|
|
71
87
|
|
|
72
88
|
if (process.argv.length <= 2) {
|
package/src/services/api.js
CHANGED
|
@@ -53,6 +53,34 @@ export async function getMe(token) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// ─── AI Endpoints (Public — No Auth Required) ───────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Send a prompt to the AI chat endpoint.
|
|
60
|
+
* Returns { reply } or { error }
|
|
61
|
+
*/
|
|
62
|
+
export async function aiChat(prompt) {
|
|
63
|
+
try {
|
|
64
|
+
const { data } = await api.post("/ai/chat", { prompt }, { timeout: 60000 });
|
|
65
|
+
return data;
|
|
66
|
+
} catch (err) {
|
|
67
|
+
return extractError(err);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Send a file to the AI file solver endpoint.
|
|
73
|
+
* Returns { content } or { error }
|
|
74
|
+
*/
|
|
75
|
+
export async function aiFile(filename, content) {
|
|
76
|
+
try {
|
|
77
|
+
const { data } = await api.post("/ai/file", { filename, content }, { timeout: 60000 });
|
|
78
|
+
return data;
|
|
79
|
+
} catch (err) {
|
|
80
|
+
return extractError(err);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
56
84
|
// ─── Error Extraction ────────────────────────────────────────────────────────
|
|
57
85
|
|
|
58
86
|
function extractError(err) {
|
package/src/services/socket.js
CHANGED
|
@@ -69,6 +69,37 @@ export function sendPrivateMessage(ws, to, text) {
|
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Request chat history (global or private).
|
|
74
|
+
*/
|
|
75
|
+
export function requestHistory(ws, type, withUser, limit = 50) {
|
|
76
|
+
if (ws.readyState !== WebSocket.OPEN) return false;
|
|
77
|
+
const payload = { action: "history", type, limit };
|
|
78
|
+
if (type === "private" && withUser) {
|
|
79
|
+
payload.with = withUser;
|
|
80
|
+
}
|
|
81
|
+
ws.send(JSON.stringify(payload));
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Request the list of currently online users.
|
|
87
|
+
*/
|
|
88
|
+
export function requestOnline(ws) {
|
|
89
|
+
if (ws.readyState !== WebSocket.OPEN) return false;
|
|
90
|
+
ws.send(JSON.stringify({ action: "online" }));
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Request the list of all registered users.
|
|
96
|
+
*/
|
|
97
|
+
export function requestUsers(ws) {
|
|
98
|
+
if (ws.readyState !== WebSocket.OPEN) return false;
|
|
99
|
+
ws.send(JSON.stringify({ action: "users" }));
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
72
103
|
/**
|
|
73
104
|
* Close the WebSocket connection gracefully.
|
|
74
105
|
*/
|
package/src/ui/printer.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
warning,
|
|
14
14
|
accent,
|
|
15
15
|
brand,
|
|
16
|
+
white,
|
|
16
17
|
whiteBold,
|
|
17
18
|
dim,
|
|
18
19
|
} from "./color.js";
|
|
@@ -93,6 +94,9 @@ export function printChatHelp() {
|
|
|
93
94
|
console.log(` ${whiteBold("Chat Commands:")}`);
|
|
94
95
|
console.log(` ${brand("/global")} ${muted("Switch to global chat")}`);
|
|
95
96
|
console.log(` ${brand("/dm <username>")} ${muted("Switch to private messaging")}`);
|
|
97
|
+
console.log(` ${brand("/history")} ${muted("Load message history for current chat")}`);
|
|
98
|
+
console.log(` ${brand("/online")} ${muted("Show currently connected users")}`);
|
|
99
|
+
console.log(` ${brand("/users")} ${muted("Show all registered users")}`);
|
|
96
100
|
console.log(` ${brand("/help")} ${muted("Show this help menu")}`);
|
|
97
101
|
console.log(` ${brand("/exit")} ${muted("Disconnect and exit")}`);
|
|
98
102
|
console.log();
|
|
@@ -111,6 +115,66 @@ export function printModeSwitch(mode, target) {
|
|
|
111
115
|
console.log();
|
|
112
116
|
}
|
|
113
117
|
|
|
118
|
+
/**
|
|
119
|
+
* Print a history header.
|
|
120
|
+
*/
|
|
121
|
+
export function printHistoryHeader(type, target) {
|
|
122
|
+
console.log();
|
|
123
|
+
const sep = muted("─".repeat(50));
|
|
124
|
+
console.log(` ${sep}`);
|
|
125
|
+
if (type === "global") {
|
|
126
|
+
console.log(` ${systemTag(" HISTORY ")} ${accent("🌍 Global Chat History")}`);
|
|
127
|
+
} else {
|
|
128
|
+
console.log(` ${systemTag(" HISTORY ")} ${accent(`🔒 DM History with ${whiteBold(target)}`)}`);
|
|
129
|
+
}
|
|
130
|
+
console.log(` ${sep}`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Print a history footer.
|
|
135
|
+
*/
|
|
136
|
+
export function printHistoryFooter(count) {
|
|
137
|
+
const sep = muted("─".repeat(50));
|
|
138
|
+
console.log(` ${sep}`);
|
|
139
|
+
console.log(` ${muted(`${count} message${count !== 1 ? "s" : ""} loaded`)}`);
|
|
140
|
+
console.log(` ${sep}`);
|
|
141
|
+
console.log();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Print online users list.
|
|
146
|
+
*/
|
|
147
|
+
export function printOnlineUsers(users) {
|
|
148
|
+
console.log();
|
|
149
|
+
console.log(` ${whiteBold("Online Users")} ${muted(`(${users.length})`)}`);
|
|
150
|
+
console.log(` ${muted("─".repeat(30))}`);
|
|
151
|
+
if (users.length === 0) {
|
|
152
|
+
console.log(` ${muted("No users online")}`);
|
|
153
|
+
} else {
|
|
154
|
+
for (const u of users) {
|
|
155
|
+
console.log(` ${success("●")} ${accent(u)}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
console.log();
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Print all registered users list.
|
|
163
|
+
*/
|
|
164
|
+
export function printRegisteredUsers(users) {
|
|
165
|
+
console.log();
|
|
166
|
+
console.log(` ${whiteBold("Registered Users")} ${muted(`(${users.length})`)}`);
|
|
167
|
+
console.log(` ${muted("─".repeat(30))}`);
|
|
168
|
+
if (users.length === 0) {
|
|
169
|
+
console.log(` ${muted("No users found")}`);
|
|
170
|
+
} else {
|
|
171
|
+
for (const u of users) {
|
|
172
|
+
console.log(` ${brand("•")} ${white(u)}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
|
177
|
+
|
|
114
178
|
/**
|
|
115
179
|
* Print a blank line.
|
|
116
180
|
*/
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
printGlobalMessage,
|
|
3
|
+
printPrivateMessage,
|
|
4
|
+
printError,
|
|
5
|
+
printSystem,
|
|
6
|
+
printHistoryHeader,
|
|
7
|
+
printHistoryFooter,
|
|
8
|
+
printOnlineUsers,
|
|
9
|
+
printRegisteredUsers,
|
|
10
|
+
} from "../ui/printer.js";
|
|
2
11
|
|
|
3
12
|
/**
|
|
4
13
|
* Handle incoming WebSocket messages and dispatch to the appropriate printer.
|
|
@@ -15,6 +24,18 @@ export function handleIncomingMessage(data, currentUser) {
|
|
|
15
24
|
printPrivateMessage(data.sender, data.text, data.timestamp, currentUser);
|
|
16
25
|
break;
|
|
17
26
|
|
|
27
|
+
case "history":
|
|
28
|
+
renderHistory(data, currentUser);
|
|
29
|
+
break;
|
|
30
|
+
|
|
31
|
+
case "online":
|
|
32
|
+
printOnlineUsers(data.users || []);
|
|
33
|
+
break;
|
|
34
|
+
|
|
35
|
+
case "users":
|
|
36
|
+
printRegisteredUsers(data.users || []);
|
|
37
|
+
break;
|
|
38
|
+
|
|
18
39
|
case "error":
|
|
19
40
|
printError(data.message || "Unknown server error");
|
|
20
41
|
break;
|
|
@@ -24,3 +45,28 @@ export function handleIncomingMessage(data, currentUser) {
|
|
|
24
45
|
break;
|
|
25
46
|
}
|
|
26
47
|
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Render a history response (global or private).
|
|
51
|
+
*/
|
|
52
|
+
function renderHistory(data, currentUser) {
|
|
53
|
+
const messages = data.messages || [];
|
|
54
|
+
const type = data.type;
|
|
55
|
+
const target = data.with;
|
|
56
|
+
|
|
57
|
+
printHistoryHeader(type, target);
|
|
58
|
+
|
|
59
|
+
if (messages.length === 0) {
|
|
60
|
+
printSystem("No messages yet. Start the conversation!");
|
|
61
|
+
} else {
|
|
62
|
+
for (const msg of messages) {
|
|
63
|
+
if (type === "global") {
|
|
64
|
+
printGlobalMessage(msg.sender, msg.text, msg.timestamp, currentUser);
|
|
65
|
+
} else {
|
|
66
|
+
printPrivateMessage(msg.sender, msg.text, msg.timestamp, currentUser);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
printHistoryFooter(messages.length);
|
|
72
|
+
}
|