AmendHub

Download

jcs

/

subtext

/

chat.c

 

(View History)

jcs   session+chat: Finally implement sysop paging/answering Latest amendment: 477 on 2023-04-10

1 /*
2 * Copyright (c) 2021 joshua stein <jcs@jcs.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <time.h>
18 #include <stdarg.h>
19 #include <stdio.h>
20 #include <string.h>
21
22 #include "ansi.h"
23 #include "chat.h"
24 #include "db.h"
25 #include "session.h"
26 #include "user.h"
27 #include "util.h"
28
29 #define CHAT_MAX_INPUT 80
30
31 static char chat_tbuf[256];
32
33 void chat_output_bar(struct session *s, char *left_str, char *right_str);
34 void chat_help(struct session *s);
35 void chat_who(struct session *s);
36 void chat_privmsg(struct session *s, char *user_and_msg);
37
38 void
39 chat_broadcast(struct session *s, char *str)
40 {
41 short n;
42
43 for (n = 0; n < MAX_SESSIONS; n++) {
44 if (sessions[n] == NULL || !sessions[n]->logged_in ||
45 !sessions[n]->chatting)
46 continue;
47 if (strcmp(s->chatting_with_node,
48 sessions[n]->chatting_with_node) != 0 &&
49 strcmp(sessions[n]->chatting_with_node, s->node) != 0)
50 continue;
51
52 chat_printf_line(sessions[n], 1, "%s", str);
53 }
54 }
55
56 size_t
57 chat_printf_line(struct session *s, short around_bar, char *format, ...)
58 {
59 static char chat_pbuf[256];
60 static char chat_final_pbuf[512];
61 char *pbuf_line;
62 va_list ap;
63 struct tm *tm;
64 size_t final_len = 0, plen, max_llen;
65 ssize_t llen;
66
67 if (around_bar)
68 final_len = strlcpy(chat_final_pbuf,
69 ansi(s, ANSI_SAVE_CURSOR, ANSI_END),
70 sizeof(chat_final_pbuf));
71
72 /* prepend time */
73 tm = localtime((time_t *)&Time);
74 plen = strftime(chat_pbuf, sizeof(chat_pbuf), "[%H:%M] ", tm);
75
76 /* append orignal line */
77 va_start(ap, format);
78 plen += vsnprintf(chat_pbuf + plen, sizeof(chat_pbuf) - plen, format,
79 ap);
80 va_end(ap);
81
82 pbuf_line = chat_pbuf;
83 max_llen = s->terminal_columns;
84
85 while (plen > 0) {
86 if (plen > max_llen) {
87 /* chop at last word boundary */
88 llen = max_llen;
89 while (llen >= 0 && pbuf_line[llen] != ' ')
90 llen--;
91 if (llen == -1)
92 /* no words to break on, hard break */
93 llen = max_llen;
94 } else {
95 llen = plen;
96 }
97
98 if (around_bar)
99 final_len = strlcat(chat_final_pbuf,
100 ansi(s, ANSI_CURSOR_LINE_COL, 1, 1,
101 ANSI_DELETE_LINES_N, 1,
102 ANSI_CURSOR_LINE_COL, s->terminal_lines - 2, 1,
103 ANSI_INSERT_LINES_N, 1,
104 ANSI_END),
105 sizeof(chat_final_pbuf));
106
107 if (pbuf_line != chat_pbuf) {
108 if (!around_bar)
109 strlcat(chat_final_pbuf, "\r\n", sizeof(chat_final_pbuf));
110 final_len = strlcat(chat_final_pbuf, " ",
111 sizeof(chat_final_pbuf));
112 }
113
114 /* manually copy since pbuf_line+llen is not null terminated */
115 if (final_len + llen + 1 > sizeof(chat_final_pbuf))
116 break;
117 memcpy(chat_final_pbuf + final_len, pbuf_line, llen);
118 chat_final_pbuf[final_len + llen] = '\0';
119
120 pbuf_line += llen;
121 plen -= llen;
122 }
123
124 if (around_bar)
125 final_len = strlcat(chat_final_pbuf,
126 ansi(s, ANSI_RESTORE_SAVED_CURSOR, ANSI_END),
127 sizeof(chat_final_pbuf));
128 else
129 final_len = strlcat(chat_final_pbuf, "\r\n",
130 sizeof(chat_final_pbuf));
131
132 if (final_len > sizeof(chat_final_pbuf))
133 panic("chat_final_pbuf overflow! %ld", final_len);
134
135 /*
136 * If this session's output buffer is too full to take this, kick them
137 * out of chat
138 */
139 if (s->obuflen + final_len > sizeof(s->obuf)) {
140 s->chatting = 0;
141 s->abort_input = 1;
142 return 0;
143 }
144
145 memcpy(s->obuf + s->obuflen, chat_final_pbuf, final_len);
146 s->obuflen += final_len;
147
148 return final_len;
149 }
150
151 void
152 chat_start(struct session *s, char *with_node)
153 {
154 static char chat_bar[255];
155 char chatting_with[DB_USERNAME_LENGTH + 1];
156 size_t len, n;
157 char *input;
158 bool lagged = false;
159
160 chatting_with[0] = '\0';
161 s->chatting_with_node[0] = '\0';
162
163 if (with_node && strcmp(with_node, CHAT_WITH_SYSOP) == 0) {
164 strlcpy(s->chatting_with_node, CHAT_WITH_SYSOP,
165 sizeof(s->chatting_with_node));
166 strlcpy(chatting_with, "sysop", sizeof(chatting_with));
167 } else if (with_node) {
168 for (n = 0; n < MAX_SESSIONS; n++) {
169 if (sessions[n] == NULL || !sessions[n]->logged_in)
170 continue;
171 if (strcmp(sessions[n]->node, with_node) == 0) {
172 strlcpy(chatting_with, sessions[n]->chat_username,
173 sizeof(chatting_with));
174 break;
175 }
176 }
177
178 if (chatting_with[0] == '\0') {
179 session_logf(s, "Tried to enter chat with node %s but no "
180 "user logged in there", with_node);
181 session_printf(s, "Error: Couldn't find user on node %s",
182 with_node);
183 return;
184 }
185
186 strlcpy(s->chatting_with_node, with_node,
187 sizeof(s->chatting_with_node));
188 } else {
189 strlcpy(s->chatting_with_node, CHAT_WITH_ALL,
190 sizeof(s->chatting_with_node));
191 }
192
193 s->chatting = true;
194
195 session_logf(s, "Entered chat with %s", chatting_with[0] ?
196 chatting_with : "everyone");
197
198 session_printf(s,
199 ansi(s, ANSI_CURSOR_LINE_COL, s->terminal_lines, 1, ANSI_END));
200
201 chat_printf_line(s, 0, "%sWelcome to Multi-User Chat%s",
202 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
203 chat_printf_line(s, 0, "Type %s/help%s for available commands",
204 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
205 session_flush(s);
206
207 len = snprintf(chat_bar, sizeof(chat_bar),
208 " [ %s ] [ Chatting with %s ] ",
209 s->chat_username, chatting_with[0] ? chatting_with : "everyone");
210
211 while (len < sizeof(chat_bar) && len < s->terminal_columns) {
212 chat_bar[len - 1] = ' ';
213 chat_bar[len] = '\0';
214 len++;
215 }
216
217 session_printf(s, "\r%s",
218 ansi(s, ANSI_REVERSE, ANSI_ERASE_LINE, ANSI_END));
219 session_output(s, chat_bar, len);
220 session_printf(s, "%s\r\n",
221 ansi(s, ANSI_RESET, ANSI_END));
222 session_flush(s);
223
224 snprintf(chat_tbuf, sizeof(chat_tbuf),
225 "*** %s%s has joined chat",
226 s->chat_username, s->user && s->user->is_sysop ? " (sysop)" : "");
227 chat_broadcast(s, chat_tbuf);
228 session_flush(s);
229
230 chat_who(s);
231
232 if (strcmp(s->chatting_with_node, CHAT_WITH_SYSOP) == 0) {
233 snprintf(chat_tbuf, sizeof(chat_tbuf),
234 "*** Waiting for sysop to answer page...");
235 chat_broadcast(s, chat_tbuf);
236 }
237
238 for (;;) {
239 input = session_field_input(s, CHAT_MAX_INPUT - 1,
240 s->terminal_columns - 1, NULL, false, 0);
241 if (!input)
242 break;
243 session_printf(s,
244 ansi(s, ANSI_COL_N, 1, ANSI_ERASE_LINE, ANSI_END));
245 session_flush(s);
246
247 if (input[0] == '/') {
248 if (strcmp(input, "/quit") == 0 ||
249 strcmp(input, "/exit") == 0 ||
250 strcmp(input, "/leave") == 0) {
251 xfree(&input);
252 break;
253 } else if (strcmp(input, "/help") == 0) {
254 chat_help(s);
255 } else if (strncmp(input, "/msg ", 5) == 0 ||
256 strcmp(input, "/msg") == 0) {
257 chat_privmsg(s, input + 5);
258 session_flush(s);
259 } else if (strcmp(input, "/who") == 0) {
260 chat_who(s);
261 } else {
262 chat_printf_line(s, 1, "*** Unknown command: %s",
263 input + 1);
264 session_flush(s);
265 }
266 } else {
267 snprintf(chat_tbuf, sizeof(chat_tbuf), "<%s> %s",
268 s->chat_username, input);
269 chat_broadcast(s, chat_tbuf);
270 session_flush(s);
271 }
272
273 xfree(&input);
274 }
275
276 session_logf(s, "Left chat with %s", chatting_with[0] ?
277 chatting_with : "everyone");
278
279 snprintf(chat_tbuf, sizeof(chat_tbuf),
280 "*** %s%s has left chat",
281 s->chat_username, s->user && s->user->is_sysop ? " (sysop)" : "");
282
283 if (s->ending)
284 strlcat(chat_tbuf, " (disconnected)", sizeof(chat_tbuf));
285 else if (s->abort_input)
286 strlcat(chat_tbuf, " (too lagged)", sizeof(chat_tbuf));
287 chat_broadcast(s, chat_tbuf);
288 session_flush(s);
289
290 if (s->abort_input && !s->chatting)
291 lagged = true;
292
293 s->chatting = false;
294 memset(s->chatting_with_node, 0, sizeof(s->chatting_with_node));
295
296 /* clear chat bar */
297 session_printf(s, ansi(s, ANSI_CURSOR_LINE_COL,
298 s->terminal_lines - 1, 1, ANSI_ERASE_LINE, ANSI_END));
299
300 if (lagged) {
301 /*
302 * We were kicked out for being too lagged, so we didn't see
303 * that last broadcast; show it once we've flushed our output on
304 * our own time.
305 */
306 session_printf(s, "%s\r\n", chat_tbuf);
307 session_flush(s);
308 }
309
310 session_output(s, "\r\n", 2);
311 session_flush(s);
312 }
313
314 void
315 chat_help(struct session *s)
316 {
317 chat_printf_line(s, 1, "*** Available chat commands:");
318 chat_printf_line(s, 1, "*** %s/quit%s : Leave chat",
319 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
320 chat_printf_line(s, 1, "*** %s/msg user message%s : "
321 "Send \"message\" only to user \"user\"",
322 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
323 chat_printf_line(s, 1, "*** %s/who%s : "
324 "List users in this chat",
325 ansi(s, ANSI_BOLD, ANSI_END), ansi(s, ANSI_RESET, ANSI_END));
326 session_flush(s);
327 }
328
329 void
330 chat_who(struct session *s)
331 {
332 short n, printed = 0;
333 size_t len = 0;
334
335 for (n = 0; n < MAX_SESSIONS; n++) {
336 if (sessions[n] == NULL || !sessions[n]->logged_in ||
337 !sessions[n]->chatting)
338 continue;
339 if (strcmp(s->chatting_with_node,
340 sessions[n]->chatting_with_node) != 0 &&
341 strcmp(sessions[n]->chatting_with_node, s->node) != 0)
342 continue;
343
344 len += snprintf(chat_tbuf + len, sizeof(chat_tbuf), "[ %c%-16s ]",
345 sessions[n]->user && sessions[n]->user->is_sysop ? '@' : ' ',
346 sessions[n]->chat_username);
347
348 if (++printed == 3) {
349 chat_printf_line(s, 1, chat_tbuf);
350 printed = 0;
351 len = 0;
352 }
353 }
354
355 if (printed)
356 chat_printf_line(s, 1, chat_tbuf);
357 }
358
359 void
360 chat_privmsg(struct session *s, char *user_and_msg)
361 {
362 char *username = NULL;
363 size_t n;
364 bool found = false;
365
366 for (n = 0; user_and_msg[n] != '\0'; n++) {
367 if (user_and_msg[n] == ' ') {
368 user_and_msg[n] = '\0';
369
370 username = user_and_msg;
371 user_and_msg = user_and_msg + n + 1;
372 break;
373 }
374 }
375
376 if (username == NULL || user_and_msg[0] == '\0') {
377 chat_printf_line(s, 1, "*** Usage: /msg user message");
378 session_flush(s);
379 return;
380 }
381
382 for (n = 0; n < MAX_SESSIONS; n++) {
383 if (sessions[n] == NULL || !sessions[n]->logged_in ||
384 !sessions[n]->chatting)
385 continue;
386 if (strcmp(sessions[n]->chat_username, username) != 0)
387 continue;
388
389 chat_printf_line(sessions[n], 1, "*[priv] %s* %s",
390 s->chat_username, user_and_msg);
391 found = true;
392 }
393
394 if (!found) {
395 chat_printf_line(s, 1, "*** User %s does not exist or "
396 "is not chatting", username);
397 session_flush(s);
398 return;
399 }
400
401 chat_printf_line(s, 1, "*-> %s* %s", username, user_and_msg);
402 session_flush(s);
403 }