AmendHub

Download

jcs

/

subtext

/

chat.c

 

(View History)

jcs   chat: Fix session iteration Latest amendment: 293 on 2022-11-29

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