Skip to content
Snippets Groups Projects
chat.cpp 53.4 KiB
Newer Older
/* Synchronet real-time chat functions */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
 *																			*
 * This program is free software; you can redistribute it and/or			*
 * modify it under the terms of the GNU General Public License				*
 * as published by the Free Software Foundation; either version 2			*
 * of the License, or (at your option) any later version.					*
 * See the GNU General Public License for more details: gpl.txt or			*
 * http://www.fsf.org/copyleft/gpl.html										*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * Note: If this box doesn't appear square, then you need to fix your tabs.	*
 ****************************************************************************/

#include "sbbs.h"

#define PCHAT_LEN 1000      /* Size of Private chat file */
const char *weekday[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday"
	                     , "Saturday"};
const char *month[] = {"January", "February", "March", "April", "May", "June"
	                   , "July", "August", "September", "October", "November", "December"};
/****************************************************************************/
/****************************************************************************/
void sbbs_t::multinodechat(int channel)
	char   line[256], str[256], ch, done
	, usrs, preusrs, qusrs, *gurubuf = NULL, savch, *p
	, pgraph[400], buf[400]
	, usr[MAX_NODES], preusr[MAX_NODES], qusr[MAX_NODES];
	char   guru_lastanswer[512];
	char   tmp[512];
	int    file;
	long   i, j, k, n;
	node_t node;

	if (useron.rest & FLAG('C')) {
		bputs(text[R_Chat]);
	if (channel < 1 || channel > cfg.total_chans)
		channel = 1;
	if (!chan_access(channel - 1))
		return;
	if (useron.misc & (RIP) || !(useron.misc & EXPERT))
		menu("multchat");
	bputs(text[WelcomeToMultiChat]);
	if (getnodedat(cfg.node_num, &thisnode, true)) {
		thisnode.aux = channel;
		putnodedat(cfg.node_num, &thisnode);
	bprintf(text[WelcomeToChannelN], channel, cfg.chan[channel - 1]->name);
	if (cfg.chan[channel - 1]->misc & CHAN_GURU && cfg.chan[channel - 1]->guru < cfg.total_gurus
	    && chk_ar(cfg.guru[cfg.chan[channel - 1]->guru]->ar, &useron, &client)) {
		snprintf(str, sizeof str, "%s%s.dat", cfg.ctrl_dir, cfg.guru[cfg.chan[channel - 1]->guru]->code);
		if ((file = nopen(str, O_RDONLY)) == -1) {
			errormsg(WHERE, ERR_OPEN, str, O_RDONLY);
		if ((gurubuf = (char *)malloc((size_t)filelength(file) + 1)) == NULL) {
			close(file);
			errormsg(WHERE, ERR_ALLOC, str, (size_t)filelength(file) + 1);
		if (read(file, gurubuf, (size_t)filelength(file)) < 1)
			gurubuf[filelength(file)] = 0;
	usrs = 0;
	for (i = 1; i <= cfg.sys_nodes && i <= cfg.sys_lastnode; i++) {
		if (i == cfg.node_num)
			continue;
		if (node.action != NODE_MCHT || node.status != NODE_INUSE)
			continue;
		if (node.aux && (node.aux & 0xff) != channel)
			continue;
		printnodedat(i, &node);
		preusr[usrs] = (char)i;
		usr[usrs++] = (char)i;
	preusrs = usrs;
	if (gurubuf)
		bprintf(text[NodeInMultiChatLocally]
		        , cfg.sys_nodes + 1, cfg.guru[cfg.chan[channel - 1]->guru]->name, channel);
	bputs(text[YoureOnTheAir]);
	done = 0;
	while (online && !done) {
		checkline();
		gettimeleft();
		action = NODE_MCHT;
		qusrs = usrs = 0;
		for (i = 1; i <= cfg.sys_nodes; i++) {
			if (i == cfg.node_num)
				continue;
			getnodedat(i, &node);
			if (node.action != NODE_MCHT
			    || (node.aux && channel && (node.aux & 0xff) != channel))
				continue;
			if (node.status == NODE_QUIET)
				qusr[qusrs++] = (char)i;
			else if (node.status == NODE_INUSE)
				usr[usrs++] = (char)i;
		if (preusrs > usrs) {
			if (!usrs && channel && cfg.chan[channel - 1]->misc & CHAN_GURU
			    && cfg.chan[channel - 1]->guru < cfg.total_gurus)
				bprintf(text[NodeJoinedMultiChat]
				        , cfg.sys_nodes + 1, cfg.guru[cfg.chan[channel - 1]->guru]->name
				        , channel);
			outchar(BEL);
			for (i = 0; i < preusrs; i++) {
				for (j = 0; j < usrs; j++)
					if (preusr[i] == usr[j])
					if (node.misc & NODE_ANON)
						snprintf(str, sizeof str, "%.80s", text[UNKNOWN_USER]);
						username(&cfg, node.useron, str);
					bprintf(text[NodeLeftMultiChat]
					        , preusr[i], str, channel);
		else if (preusrs < usrs) {
			if (!preusrs && channel && cfg.chan[channel - 1]->misc & CHAN_GURU
			    && cfg.chan[channel - 1]->guru < cfg.total_gurus)
				bprintf(text[NodeLeftMultiChat]
				        , cfg.sys_nodes + 1, cfg.guru[cfg.chan[channel - 1]->guru]->name
				        , channel);
			outchar(BEL);
			for (i = 0; i < usrs; i++) {
				for (j = 0; j < preusrs; j++)
					if (usr[i] == preusr[j])
				if (j == preusrs) {
					if (node.misc & NODE_ANON)
						snprintf(str, sizeof str, "%.80s", text[UNKNOWN_USER]);
						username(&cfg, node.useron, str);
					bprintf(text[NodeJoinedMultiChat]
					        , usr[i], str, channel);
		preusrs = usrs;
		for (i = 0; i < usrs; i++)
			preusr[i] = usr[i];
		attr(cfg.color[clr_multichat]);
		sys_status &= ~SS_ABORT;
		if ((ch = inkey(K_NONE, 250)) != 0 || wordwrap[0]) {
			if (ch == '/') {
				bputs(text[MultiChatCommandPrompt]);
				strcpy(str, "ACELWQ?*");
				if (SYSOP)
					SAFECAT(str, "0");
				i = getkeys(str, cfg.total_chans);
				if (i & 0x80000000L) {  /* change channel */
					savch = (char)(i & ~0x80000000L);
					if (savch == channel)
						continue;
					if (!chan_access(savch - 1))
						continue;
					bprintf(text[WelcomeToChannelN]
					        , savch, cfg.chan[savch - 1]->name);
					usrs = 0;
					for (i = 1; i <= cfg.sys_nodes; i++) {
						if (i == cfg.node_num)
							continue;
						if (node.action != NODE_MCHT
						    || node.status != NODE_INUSE)
							continue;
						if (node.aux && (node.aux & 0xff) != savch)
							continue;
						printnodedat(i, &node);
						if (node.aux & 0x1f00) {   /* password */
							bprintf(text[PasswordProtected]
							        , node.misc & NODE_ANON
							    ? text[UNKNOWN_USER]
							    : username(&cfg, node.useron, tmp));
							if (!getstr(str, 8, K_UPPER | K_ALPHA | K_LINE))
							if (strcmp(str, unpackchatpass(tmp, &node)))
							bputs(text[CorrectPassword]);
						}
						preusr[usrs] = (char)i;
						usr[usrs++] = (char)i;
					if (i <= cfg.sys_nodes) {  /* failed password */
						bputs(text[WrongPassword]);
						free(gurubuf);
					if (cfg.chan[savch - 1]->misc & CHAN_GURU
					    && cfg.chan[savch - 1]->guru < cfg.total_gurus
					    && chk_ar(cfg.guru[cfg.chan[savch - 1]->guru]->ar, &useron, &client
					              )) {
						snprintf(str, sizeof str, "%s%s.dat", cfg.ctrl_dir
						         , cfg.guru[cfg.chan[savch - 1]->guru]->code);
						if ((file = nopen(str, O_RDONLY)) == -1) {
							errormsg(WHERE, ERR_OPEN, str, O_RDONLY);
						if ((gurubuf = (char *)malloc((size_t)filelength(file) + 1)) == NULL) {
							errormsg(WHERE, ERR_ALLOC, str
							         , (size_t)filelength(file) + 1);
							close(file);
						if (read(file, gurubuf, (size_t)filelength(file)) < 1)
							gurubuf[filelength(file)] = 0;
					preusrs = usrs;
					if (gurubuf)
						bprintf(text[NodeInMultiChatLocally]
						        , cfg.sys_nodes + 1
						        , cfg.guru[cfg.chan[savch - 1]->guru]->name
						        , savch);
					channel = savch;
					if (!usrs && cfg.chan[savch - 1]->misc & CHAN_PW
					    && !noyes(text[PasswordProtectChanQ])) {
						bputs(text[PasswordPrompt]);
						if (getstr(str, 8, K_UPPER | K_ALPHA | K_LINE)) {
							getnodedat(cfg.node_num, &thisnode, true);
							thisnode.aux = channel;
							packchatpass(str, &thisnode);
							getnodedat(cfg.node_num, &thisnode, true);
							thisnode.aux = channel;
						getnodedat(cfg.node_num, &thisnode, true);
						thisnode.aux = channel;
					putnodedat(cfg.node_num, &thisnode);
					bputs(text[YoureOnTheAir]);
					if (cfg.chan[channel - 1]->cost
					    && !(useron.exempt & FLAG('J')))
						subtract_cdt(&cfg, &useron, cfg.chan[channel - 1]->cost);
				else switch (i) {    /* other command */
						case '0': /* Global channel */
							if (!SYSOP)
								break;
							usrs = 0;
							for (i = 1; i <= cfg.sys_nodes; i++) {
								if (i == cfg.node_num)
									continue;
								getnodedat(i, &node);
								if (node.action != NODE_MCHT
								    || node.status != NODE_INUSE)
									continue;
								printnodedat(i, &node);
								preusr[usrs] = (char)i;
								usr[usrs++] = (char)i;
							}
							preusrs = usrs;
							if (getnodedat(cfg.node_num, &thisnode, true)) {
								thisnode.aux = channel = 0;
								putnodedat(cfg.node_num, &thisnode);
							}
						case 'A': /* Action commands */
							useron.chat ^= CHAT_ACTION;
							bprintf("\r\nAction commands are now %s\r\n"
							        , useron.chat & CHAT_ACTION
							putuserchat(useron.number, useron.chat);
							break;
						case 'C': /* List of action commands */
							CRLF;
							for (i = 0; channel && i < cfg.total_chatacts; i++) {
								if (cfg.chatact[i]->actset
								    != cfg.chan[channel - 1]->actset)
									continue;
								bprintf("%-*.*s", LEN_CHATACTCMD
								        , LEN_CHATACTCMD, cfg.chatact[i]->cmd);
								if (!((i + 1) % 8)) {
									CRLF;
								}
								else
									bputs(" ");
							CRLF;
							break;
						case 'E': /* Toggle echo */
							useron.chat ^= CHAT_ECHO;
							bprintf(text[EchoIsNow]
							        , useron.chat & CHAT_ECHO
							putuserchat(useron.number, useron.chat);
						case 'L': /* list nodes */
							CRLF;
							for (i = 1; i <= cfg.sys_nodes && i <= cfg.sys_lastnode; i++) {
								getnodedat(i, &node);
								printnodedat(i, &node);
							}
							CRLF;
						case 'W': /* page node(s) */
							j = getnodetopage(0, 0);
							if (!j)
								break;
							for (i = 0; i < usrs; i++)
								if (usr[i] == j)
									break;
							if (i >= usrs) {
								bputs(text[UserNotFound]);
								break;
							}
							bputs(text[NodeMsgPrompt]);
							if (!getstr(line, 66, K_LINE | K_MSG))
								break;
							snprintf(buf, sizeof buf, text[ChatLineFmt]
							         , thisnode.misc & NODE_ANON
							? text[AnonUserChatHandle]
							: useron.handle
							         , cfg.node_num, '*', line);
							SAFECAT(buf, crlf);
							if (useron.chat & CHAT_ECHO)
								bputs(buf);
							putnmsg(j, buf);
							break;
						case 'Q': /* quit */
							done = 1;
							break;
						case '*':
							if (!menu("chan", P_NOERROR)) {
								bputs(text[ChatChanLstHdr]);
								bputs(text[ChatChanLstTitles]);
								if (cfg.total_chans >= 10) {
									bputs("     ");
									bputs(text[ChatChanLstTitles]);
								}
								CRLF;
								bputs(text[ChatChanLstUnderline]);
								if (cfg.total_chans >= 10) {
									bputs("     ");
									bputs(text[ChatChanLstUnderline]);
								}
								CRLF;
								if (cfg.total_chans >= 10)
									j = (cfg.total_chans / 2) + (cfg.total_chans & 1);
								else
									j = cfg.total_chans;
								for (i = 0; i < j && !msgabort(); i++) {
									bprintf(text[ChatChanLstFmt], i + 1
									        , cfg.chan[i]->name
									        , cfg.chan[i]->cost);
									if (cfg.total_chans >= 10) {
										k = (cfg.total_chans / 2)
										    + i + (cfg.total_chans & 1);
										if (k < cfg.total_chans) {
											bputs("     ");
											bprintf(text[ChatChanLstFmt]
											        , k + 1
											        , cfg.chan[k]->name
											        , cfg.chan[k]->cost);
										}
							break;
						case '?': /* menu */
							menu("multchat");
							break;
					}
				ungetkey(ch);
				j = 0;
				pgraph[0] = 0;
				while (j < 5) {
					if (!getstr(line, 66, K_WRAP | K_MSG | K_CHAT))
						snprintf(str, sizeof str, text[ChatLineFmt]
						         , thisnode.misc & NODE_ANON
						    ? text[AnonUserChatHandle]
						    : useron.handle
						         , cfg.node_num, ':', nulstr);
						snprintf(tmp, sizeof tmp, "%*s", (int)bstrlen(str), nulstr);
						SAFECAT(pgraph, tmp);
					SAFECAT(pgraph, line);
					SAFECAT(pgraph, crlf);
					if (!wordwrap[0])
				if (pgraph[0]) {
					if (channel && useron.chat & CHAT_ACTION) {
						for (i = 0; i < cfg.total_chatacts; i++) {
							if (cfg.chatact[i]->actset
							    != cfg.chan[channel - 1]->actset)
								continue;
							snprintf(str, sizeof str, "%s ", cfg.chatact[i]->cmd);
							if (!strnicmp(str, pgraph, strlen(str)))
							snprintf(str, sizeof str, "%.*s"
							         , LEN_CHATACTCMD + 2, pgraph);
							str[strlen(str) - 2] = 0;
							if (!stricmp(cfg.chatact[i]->cmd, str))
						if (i < cfg.total_chatacts) {
							p = pgraph + strlen(str);
							n = atoi(p);
							for (j = 0; j < usrs; j++) {
								if (usrs == 1) /* no need to search */
								if (n) {
									if (usr[j] == n)
								username(&cfg, node.useron, str);
								if (!strnicmp(str, p, strlen(str)))
								getuserstr(&cfg, node.useron, USER_HANDLE, str, sizeof(str));
								if (!strnicmp(str, p, strlen(str)))
							if (!usrs
							    && cfg.chan[channel - 1]->guru < cfg.total_gurus)
								strcpy(str
								       , cfg.guru[cfg.chan[channel - 1]->guru]->name);
							else if (j >= usrs)
								strcpy(str, "everyone");
							else if (node.misc & NODE_ANON)
								strcpy(str, text[UNKNOWN_USER]);
								username(&cfg, node.useron, str);

							/* Display on same node */
							bprintf(cfg.chatact[i]->out
							        , thisnode.misc & NODE_ANON
							    ? text[UNKNOWN_USER] : useron.alias
							        , str);
							if (usrs && j < usrs) {
								/* Display to dest user */
								snprintf(buf, sizeof buf, cfg.chatact[i]->out
								         , thisnode.misc & NODE_ANON
								    ? text[UNKNOWN_USER] : useron.alias
								         , "you");
								SAFECAT(buf, crlf);
								putnmsg(usr[j], buf);


							/* Display to all other users */
							snprintf(buf, sizeof buf, cfg.chatact[i]->out
							         , thisnode.misc & NODE_ANON
							    ? text[UNKNOWN_USER] : useron.alias
							         , str);
							SAFECAT(buf, crlf);
							for (i = 0; i < usrs; i++) {
								if (i == j)
									continue;
								putnmsg(usr[i], buf);
							for (i = 0; i < qusrs; i++) {
								putnmsg(qusr[i], buf);
					snprintf(buf, sizeof buf, text[ChatLineFmt]
					         , thisnode.misc & NODE_ANON
					    ? text[AnonUserChatHandle]
					    : useron.handle
					         , cfg.node_num, ':', pgraph);
					if (useron.chat & CHAT_ECHO)
						bputs(buf);
					for (i = 0; i < usrs; i++) {
						putnmsg(usr[i], buf);
					for (i = 0; i < qusrs; i++) {
						putnmsg(qusr[i], buf);
					if (!usrs && channel && gurubuf
					    && cfg.chan[channel - 1]->misc & CHAN_GURU)
						guruchat(pgraph, gurubuf, cfg.chan[channel - 1]->guru, guru_lastanswer);
		if (sys_status & SS_ABORT)
	lncntr = 0;
	if (gurubuf != NULL)
/****************************************************************************/
/****************************************************************************/
bool sbbs_t::guru_page(void)
{
	char  path[MAX_PATH + 1];
	char* gurubuf;
	int   file;
	long  i;
	if (useron.rest & FLAG('C')) {
		bputs(text[R_Chat]);
	if (!cfg.total_gurus) {
		bprintf(text[SysopIsNotAvailable], "The Guru");
	if (cfg.total_gurus == 1 && chk_ar(cfg.guru[0]->ar, &useron, &client))
		i = 0;
		for (i = 0; i < cfg.total_gurus; i++)
			uselect(1, i, nulstr, cfg.guru[i]->name, cfg.guru[i]->ar);
		i = uselect(0, 0, 0, 0, 0);
		if (i < 0)
	snprintf(path, sizeof path, "%s%s.dat", cfg.ctrl_dir, cfg.guru[i]->code);
	if ((file = nopen(path, O_RDONLY)) == -1) {
		errormsg(WHERE, ERR_OPEN, path, O_RDONLY);
	long length = (long)filelength(file);
	if (length < 0) {
		errormsg(WHERE, ERR_CHK, path, length);
		close(file);
		return false;
	}
	if ((gurubuf = (char *)malloc(length + 1)) == NULL) {
		errormsg(WHERE, ERR_ALLOC, path, length + 1);
		close(file);
	if (read(file, gurubuf, length) != length)
		errormsg(WHERE, ERR_READ, path, length);
	gurubuf[length] = 0;
	close(file);
	localguru(gurubuf, i);
	free(gurubuf);
}

/****************************************************************************/
/* The chat section                                                         */
/****************************************************************************/
void sbbs_t::chatsection()
{
	exec_bin(cfg.chatsec_mod, &main_csi);
/****************************************************************************/
/****************************************************************************/
bool sbbs_t::sysop_page(void)
	if (useron.rest & FLAG('C')) {
		bputs(text[R_Chat]);
	if (sysop_available(&cfg)
	    || (cfg.sys_chat_ar[0] && chk_ar(cfg.sys_chat_ar, &useron, &client))
	    || useron.exempt & FLAG('C')) {
		if (!(sys_status & SS_SYSPAGE)) {
			logline("C", "paged sysop for chat");
			notify(text[SysopPageNotification]);
			char topic[128];
			SAFEPRINTF(topic, "page/node/%u", cfg.node_num);
			snprintf(str, sizeof(str), "%u\t%s", useron.number, useron.alias);
			mqtt_pub_timestamped_msg(mqtt, TOPIC_BBS_ACTION, topic, time(NULL), str);
		for (i = 0; i < cfg.total_pages; i++)
			if (chk_ar(cfg.page[i]->ar, &useron, &client))
		if (i < cfg.total_pages) {
			bprintf(text[PagingGuru], cfg.sys_op);
			if (cfg.page[i]->misc & XTRN_STDIO)
			if (cfg.page[i]->misc & XTRN_NATIVE)
				mode |= EX_NATIVE;
			if (cfg.page[i]->misc & XTRN_SH)
			external(cmdstr(cfg.page[i]->cmd, nulstr, nulstr, NULL, mode), mode);
		else if (cfg.sys_misc & SM_SHRTPAGE) {
			bprintf(text[PagingGuru], cfg.sys_op);
			for (i = 0; i < 10; i++) {
				sbbs_beep(1000, 200);
				mswait(200);
			sys_status ^= SS_SYSPAGE;
			bprintf(text[SysopPageIsNow]
			        , sys_status & SS_SYSPAGE ? text[On] : text[Off]);
		if (!(sys_status & SS_SYSPAGE))
	bprintf(text[SysopIsNotAvailable], cfg.sys_op);
}

/****************************************************************************/
/* Returns 1 if user online has access to channel "channum"                 */
/****************************************************************************/
	if (!cfg.total_chans || cnum >= cfg.total_chans || !chk_ar(cfg.chan[cnum]->ar, &useron, &client)) {
		bputs(text[CantAccessThatChannel]);
	if (!(useron.exempt & FLAG('J')) && cfg.chan[cnum]->cost > user_available_credits(&useron)) {
		bputs(text[NotEnoughCredits]);
/****************************************************************************/
/* Private split-screen (or interspersed) chat with node or local sysop		*/
/****************************************************************************/
void sbbs_t::privchat(bool forced, int node_num)
	char str[128], c, *p, localbuf[5][81], remotebuf[5][81]
	, localline = 0, remoteline = 0, localchar = 0, remotechar = 0
	, *sep = text[PrivateChatSeparator]
	, *local_sep = text[SysopChatSeparator]
	;
	char   tmp[512];
	char   outpath[MAX_PATH + 1];
	char   inpath[MAX_PATH + 1];
	uchar  ch;
	int    wr;
	int    in, out, i, n, echo = 1, x, y, activity, remote_activity;
	int    local_y = 1, remote_y = 1;
	node_t node;
	time_t last_nodechk = 0;

	if (forced)
		if (useron.rest & FLAG('C')) {
		n = getnodetopage(0, 0);
		if (!n)
		if (n == cfg.node_num) {
			bputs(text[NoNeedToPageSelf]);
		if (node.action == NODE_PCHT && node.aux != cfg.node_num) {
			bprintf(text[NodeNAlreadyInPChat], n);
		if (SYSOP && getnodedat(n, &node, true)) {
			node.misc |= NODE_FCHAT;
			putnodedat(n, &node);
		} else {
			if ((node.action != NODE_PAGE || node.aux != cfg.node_num)
			    && node.misc & NODE_POFF) {
				bprintf(text[CantPageNode], node.misc & NODE_ANON
				    ? text[UNKNOWN_USER] : username(&cfg, node.useron, tmp));
			if (node.action != NODE_PAGE) {
				        , node.misc & NODE_ANON ? text[UNKNOWN_USER] : username(&cfg, node.useron, tmp)
				        , node.misc & NODE_ANON ? 0 : node.useron);
				snprintf(str, sizeof str, text[NodePChatPageMsg]
				         , cfg.node_num, thisnode.misc & NODE_ANON
				        ? text[UNKNOWN_USER] : useron.alias);
				putnmsg(n, str);
				snprintf(str, sizeof str, "paged %s on node %d to private chat"
				         , username(&cfg, node.useron, tmp), n);
				logline("C", str);
		if (getnodedat(cfg.node_num, &thisnode, true)) {
			thisnode.action = action = NODE_PAGE;
			thisnode.aux = n;
			putnodedat(cfg.node_num, &thisnode);
		if (node.action != NODE_PAGE || node.aux != cfg.node_num) {
			bprintf(text[WaitingForNodeInPChat], n);
			while (online && !(sys_status & SS_ABORT)) {
				if ((node.action == NODE_PAGE || node.action == NODE_PCHT)
				    && node.aux == cfg.node_num) {
					bprintf(text[NodeJoinedPrivateChat]
					        , n, node.misc & NODE_ANON ? text[UNKNOWN_USER]
					        : username(&cfg, node.useron, tmp));
				action = NODE_PAGE;
				checkline();
				gettimeleft();
				inkey(K_NONE, 500);
	if (getnodedat(cfg.node_num, &thisnode, true)) {
		thisnode.action = action = NODE_PCHT;
		thisnode.aux = n;
		thisnode.misc &= ~(NODE_LCHAT | NODE_FCHAT);
		putnodedat(cfg.node_num, &thisnode);
	if (!online || (!forced && (sys_status & SS_ABORT)))
	if (forced && n == 0) {
		/* If an external sysop chat event handler is installed, just run that and do nothing else */
		if (user_event(EVENT_LOCAL_CHAT))
	if (((sys_status & SS_USERON && useron.chat & CHAT_SPLITP) || !(sys_status & SS_USERON))
	    && term_supports(ANSI) && rows >= 24 && cols >= 80)
		sys_status |= SS_SPLITP;
		sys_status &= ~SS_SPLITP;
	/*
	if(!(useron.misc&EXPERT))
	if (!(sys_status & SS_SPLITP)) {
		if (forced)
			bprintf(text[SysopIsHere], cfg.sys_op);
		else
			bputs(text[WelcomeToPrivateChat]);
	}

	snprintf(outpath, sizeof outpath, "%schat.dab", cfg.node_dir);
	if ((out = sopen(outpath, O_RDWR | O_CREAT | O_BINARY, SH_DENYNO, DEFFILEMODE)) == -1) {
		errormsg(WHERE, ERR_OPEN, outpath, O_RDWR | O_DENYNONE | O_CREAT);
	if (forced && n == 0)
		snprintf(inpath, sizeof inpath, "%slchat.dab", cfg.node_dir);
		snprintf(inpath, sizeof inpath, "%schat.dab", cfg.node_path[n - 1]);
	if (!fexist(inpath))     /* Wait while it's created for the first time */
	if ((in = sopen(inpath, O_RDWR | O_CREAT | O_BINARY, SH_DENYNO, DEFFILEMODE)) == -1) {
		errormsg(WHERE, ERR_OPEN, inpath, O_RDWR | O_DENYNONE | O_CREAT);
	if ((p = (char *)malloc(PCHAT_LEN)) == NULL) {
		close(in);
		close(out);
		errormsg(WHERE, ERR_ALLOC, nulstr, PCHAT_LEN);
	memset(p, 0, PCHAT_LEN);
	if (write(in, p, PCHAT_LEN) != PCHAT_LEN)
		errormsg(WHERE, ERR_WRITE, inpath, PCHAT_LEN);
	if (write(out, p, PCHAT_LEN) != PCHAT_LEN)
		errormsg(WHERE, ERR_WRITE, outpath, PCHAT_LEN);
	free(p);
	lseek(in, 0L, SEEK_SET);
	lseek(out, 0L, SEEK_SET);
	if (getnodedat(cfg.node_num, &thisnode, true)) {
		thisnode.misc &= ~NODE_RPCHT;         /* Clear "reset pchat flag" */
		putnodedat(cfg.node_num, &thisnode);
	if (n) { // not local
		if (getnodedat(n, &node, true)) {
			node.misc |= NODE_RPCHT;              /* Set "reset pchat flag" */
			putnodedat(n, &node);                /* on other node */
		/* Wait for other node */
		/* to acknowledge and reset */
		while (online && !(sys_status & SS_ABORT)) {
			if (!(node.misc & NODE_RPCHT))
			getnodedat(cfg.node_num, &thisnode);
			if (thisnode.misc & NODE_RPCHT)
				break;
			checkline();
			gettimeleft();
	action = NODE_PCHT;
	if (sys_status & SS_SPLITP) {
		lncntr = 0;
		ansi_gotoxy(1, 13);
		remote_y = 1;
		        , thisnode.misc & NODE_MSGW ? 'T':' '
		        , sectostr(timeleft, tmp)
		        , thisnode.misc & NODE_NMSG ? 'M':' ');
		ansi_gotoxy(1, 14);
		local_y = 14;
	while (online && (forced || !(sys_status & SS_ABORT))) {
		lncntr = 0;
		if (sys_status & SS_SPLITP)
			lbuflen = 0;
		action = NODE_PCHT;
		activity = 0;
		remote_activity = 0;
		if ((ch = inkey(K_GETSTR, 100)) != 0) {
			activity = 1;
			if (echo)
				attr(cfg.color[clr_chatlocal]);
			if (ch == BS || ch == DEL) {
				if (localchar) {
					if (echo)
					localbuf[localline][localchar] = 0;
			else if (ch == TAB) {
				if (echo)
				localbuf[localline][localchar] = ' ';
				while (localchar < 78 && localchar % 8) {
					if (echo)
					localbuf[localline][localchar++] = ' ';
			else if (ch == CTRL_R) {
				if (sys_status & SS_SPLITP) {
					CLS;
					attr(cfg.color[clr_chatremote]);
					remotebuf[remoteline][remotechar] = 0;
					for (i = 0; i <= remoteline; i++) {
						if (i != remoteline)
					remote_y = 1 + remoteline;
					bputs("\1i_\1n");  /* Fake cursor */
					ansi_gotoxy(1, 13);
					        , thisnode.misc & NODE_MSGW ? 'T':' '
					        , sectostr(timeleft, tmp)
					        , thisnode.misc & NODE_NMSG ? 'M':' ');
					ansi_gotoxy(1, 14);
					attr(cfg.color[clr_chatlocal]);
					localbuf[localline][localchar] = 0;
					for (i = 0; i <= localline; i++) {
						if (i != localline)
					local_y = 15 + localline;
			else if (ch >= ' ' || ch == CR) {
				if (ch != CR) {
					if (echo)
					localbuf[localline][localchar] = ch;
				if (ch == CR || (localchar > 68 && ch == ' ') || ++localchar > 78) {
					lprintf(LOG_DEBUG, "chat line wrapped, localchar=%d, ch=%x", localchar, ch);
					localbuf[localline][localchar] = 0;
					localchar = 0;
					if (sys_status & SS_SPLITP && local_y >= rows) {
						ansi_gotoxy(1, 13);
						        , thisnode.misc & NODE_MSGW ? 'T':' '
						        , sectostr(timeleft, tmp)
						        , thisnode.misc & NODE_NMSG ? 'M':' ');
						attr(cfg.color[clr_chatlocal]);
						for (x = 13, y = 0; x < rows; x++, y++) {
							comprintf("\x1b[%d;1H\x1b[K", x + 1);
							if (y <= localline)
								bprintf("%s\r\n", localbuf[y]);
						ansi_gotoxy(1, local_y = (15 + localline));
						localline = 0;
						if (localline >= 4)
							for (i = 0; i < 4; i++)
								memcpy(localbuf[i], localbuf[i + 1], 81);
							local_y++;
							if (sys_status & SS_SPLITP)
			if (read(out, &c, 1) < 1) {
				lprintf(LOG_ERR, "Error reading char from %s", outpath);
				continue;
			}
			(void)lseek(out, -1L, SEEK_CUR);
			if (!c)      /* hasn't wrapped */
				wr = write(out, &ch, 1);
				if (!tell(out))
					lseek(out, 0L, SEEK_END);
				lseek(out, -1L, SEEK_CUR);
				ch = 0;
				wr = write(out, &ch, 1);