Skip to content
Snippets Groups Projects
chat.cpp 50.5 KiB
Newer Older
/* chat.cpp */

/* Synchronet real-time chat functions */

/* $Id$ */

/****************************************************************************
 * @format.tab-size 4		(Plain Text/Source Code File Header)			*
 * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
 *																			*
 * Copyright 2000 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										*
 *																			*
 * Anonymous FTP access to the most recent released source is available at	*
 * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
 *																			*
 * Anonymous CVS access to the development source and modification history	*
 * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
 *     (just hit return, no password is necessary)							*
 * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
 *																			*
 * For Synchronet coding style and modification guidelines, see				*
 * http://www.synchro.net/source.html										*
 *																			*
 * You are encouraged to submit any modifications (preferably in Unix diff	*
 * format) via e-mail to mods@synchro.net									*
 *																			*
 * 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"};

/****************************************************************************/
/* The chat section                                                         */
/****************************************************************************/
void sbbs_t::chatsection()
{
	char	line[256],str[256],ch,done,no_rip_menu
			,usrs,preusrs,qusrs,*gurubuf=NULL,channel,savch,*p
			,pgraph[400],buf[400]
			,usr[MAX_NODES],preusr[MAX_NODES],qusr[MAX_NODES];
	int 	file;
	long	i,j,k,n;
	node_t 	node;

	action=NODE_CHAT;
	if(useron.misc&(RIP|WIP) || !(useron.misc&EXPERT))
	ASYNC;
	bputs(text[ChatPrompt]);
	while(online) {
		no_rip_menu=0;
		ch=(char)getkeys("ACDJPQST?\r",0);
		if(ch>SP)
			logch(ch,0);
		switch(ch) {
			case 'S':
				useron.chat^=CHAT_SPLITP;
				putuserrec(&cfg,useron.number,U_CHAT,8
					,ultoa(useron.chat,str,16));
				bprintf("\r\nPrivate split-screen chat is now: %s\r\n"
					,useron.chat&CHAT_SPLITP ? text[ON]:text[OFF]);
				break;
			case 'A':
				CRLF;
				useron.chat^=CHAT_NOACT;
				putuserrec(&cfg,useron.number,U_CHAT,8
					,ultoa(useron.chat,str,16));
				getnodedat(cfg.node_num,&thisnode,1);
				thisnode.misc^=NODE_AOFF;
				printnodedat(cfg.node_num,&thisnode);
				putnodedat(cfg.node_num,&thisnode);
				no_rip_menu=true;
				break;
			case 'D':
				CRLF;
				useron.chat^=CHAT_NOPAGE;
				putuserrec(&cfg,useron.number,U_CHAT,8
					,ultoa(useron.chat,str,16));
				getnodedat(cfg.node_num,&thisnode,1);
				thisnode.misc^=NODE_POFF;
				printnodedat(cfg.node_num,&thisnode);
				putnodedat(cfg.node_num,&thisnode);
				no_rip_menu=true;
				break;
			case 'J':
				if(!chan_access(0))
					break;
				if(useron.misc&(RIP|WIP) ||!(useron.misc&EXPERT))
				getnodedat(cfg.node_num,&thisnode,1);
				bputs(text[WelcomeToMultiChat]);
				channel=1;
				thisnode.aux=1;		/* default channel 1 */
				putnodedat(cfg.node_num,&thisnode);
				bprintf(text[WelcomeToChannelN],channel,cfg.chan[0]->name);
				if(gurubuf) {
					FREE(gurubuf);
					gurubuf=NULL; }
				if(cfg.chan[0]->misc&CHAN_GURU && cfg.chan[0]->guru<cfg.total_gurus
					&& chk_ar(cfg.guru[cfg.chan[0]->guru]->ar,&useron)) {
					sprintf(str,"%s%s.dat",cfg.ctrl_dir,cfg.guru[cfg.chan[0]->guru]->code);
					if((file=nopen(str,O_RDONLY))==-1) {
						errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
						break; }
					if((gurubuf=(char *)MALLOC(filelength(file)+1))==NULL) {
						close(file);
						errormsg(WHERE,ERR_ALLOC,str,filelength(file)+1);
						break; }
					read(file,gurubuf,filelength(file));
					gurubuf[filelength(file)]=0;
					close(file); }
				usrs=0;
				for(i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
					if(i==cfg.node_num)
						continue;
					getnodedat(i,&node,0);
					if(node.action!=NODE_MCHT || node.status!=NODE_INUSE)
						continue;
					if(node.aux && (node.aux&0xff)!=channel)
						continue;
					printnodedat(i,&node);
					preusr[usrs]=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,0);
						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);
						for(i=0;i<preusrs;i++) {
							for(j=0;j<usrs;j++)
								if(preusr[i]==usr[j])
									break;
							if(j==usrs) {
								getnodedat(preusr[i],&node,0);
								if(node.misc&NODE_ANON)
									sprintf(str,"%.80s",text[UNKNOWN_USER]);
								else
									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);
						for(i=0;i<usrs;i++) {
							for(j=0;j<preusrs;j++)
								if(usr[i]==preusr[j])
									break;
							if(j==preusrs) {
								getnodedat(usr[i],&node,0);
								if(node.misc&NODE_ANON)
									sprintf(str,"%.80s",text[UNKNOWN_USER]);
								else
									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]);
					SYNC;
					sys_status&=~SS_ABORT;
					if((ch=inkey(0))!=0 || wordwrap[0]) {
						if(ch=='/') {
							bputs(text[MultiChatCommandPrompt]);
							strcpy(str,"ACELWQ?*");
							if(SYSOP)
								strcat(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;
									getnodedat(i,&node,0);
									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))
											break;
										if(strcmp(str,unpackchatpass(tmp,&node)))
											break;
											bputs(text[CorrectPassword]);  }
									preusr[usrs]=usr[usrs++]=(char)i; }
								if(i<=cfg.sys_nodes) {	/* failed password */
									bputs(text[WrongPassword]);
									continue; }
								if(gurubuf) {
									FREE(gurubuf);
									gurubuf=NULL; }
								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
									)) {
										,cfg.guru[cfg.chan[savch-1]->guru]->code);
									if((file=nopen(str,O_RDONLY))==-1) {
										errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
										break; }
									if((gurubuf=(char *)MALLOC(filelength(file)+1))==NULL) {
										close(file);
										errormsg(WHERE,ERR_ALLOC,str
											,filelength(file)+1);
										break; }
									read(file,gurubuf,filelength(file));
									gurubuf[filelength(file)]=0;
									close(file); }
								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,1);
										thisnode.aux=channel;
										packchatpass(str,&thisnode); }
									else {
										getnodedat(cfg.node_num,&thisnode,1);
										thisnode.aux=channel; } }
								else {
									getnodedat(cfg.node_num,&thisnode,1);
									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,0);
										if(node.action!=NODE_MCHT
											|| node.status!=NODE_INUSE)
											continue;
										printnodedat(i,&node);
										preusr[usrs]=usr[usrs++]=(char)i; }
									preusrs=usrs;
									getnodedat(cfg.node_num,&thisnode,1);
									thisnode.aux=channel=0;
									putnodedat(cfg.node_num,&thisnode);
									break;
								case 'A':   /* Action commands */
									useron.chat^=CHAT_ACTION;
									bprintf("\r\nAction commands are now %s\r\n"
										,useron.chat&CHAT_ACTION
										? text[ON]:text[OFF]);
									putuserrec(&cfg,useron.number,U_CHAT,8
										,ultoa(useron.chat,str,16));
									break;
								case 'C':   /* List of action commands */
									CRLF;
									for(i=0;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
										? text[ON]:text[OFF]);
									putuserrec(&cfg,useron.number,U_CHAT,8
										,ultoa(useron.chat,str,16));
									break;
								case 'L':	/* list nodes */
									CRLF;
									for(i=1;i<=cfg.sys_nodes && i<=cfg.sys_lastnode;i++) {
										getnodedat(i,&node,0);
										printnodedat(i,&node); }
									CRLF;
									break;
								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;

									sprintf(buf,text[ChatLineFmt]
										,thisnode.misc&NODE_ANON
										? text[AnonUserChatHandle]
										: useron.handle
										,cfg.node_num,'*',line);
									strcat(buf,crlf);
									if(useron.chat&CHAT_ECHO)
										bputs(buf);
									putnmsg(j,buf);
									break;
								case 'Q':	/* quit */
									done=1;
									break;
								case '*':
									if(fexist(str))
									else {
										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); } }
											CRLF; }
										CRLF; }
									break;
								case '?':	/* menu */
									break;	} }
						else {
							ungetkey(ch);
							j=0;
							pgraph[0]=0;
							while(j<5) {
								if(!getstr(line,66,K_WRAP|K_MSG|K_CHAT))
									break;
								if(j) {
									sprintf(str,text[ChatLineFmt]
										,thisnode.misc&NODE_ANON
										? text[AnonUserChatHandle]
										: useron.handle
										,cfg.node_num,':',nulstr);
									sprintf(tmp,"%*s",bstrlen(str),nulstr);
									strcat(pgraph,tmp); }
								strcat(pgraph,line);
								strcat(pgraph,crlf);
								if(!wordwrap[0])
									break;
								j++; }
							if(pgraph[0]) {
								if(useron.chat&CHAT_ACTION) {
									for(i=0;i<cfg.total_chatacts;i++) {
										if(cfg.chatact[i]->actset
											!=cfg.chan[channel-1]->actset)
											continue;
										sprintf(str,"%s ",cfg.chatact[i]->cmd);
										if(!strnicmp(str,pgraph,strlen(str)))
											break;
										sprintf(str,"%.*s"
											,LEN_CHATACTCMD+2,pgraph);
										str[strlen(str)-2]=0;
										if(!stricmp(cfg.chatact[i]->cmd,str))
											break; }

									if(i<cfg.total_chatacts) {
										p=pgraph+strlen(str);
										n=atoi(p);
										for(j=0;j<usrs;j++) {
											getnodedat(usr[j],&node,0);
											if(usrs==1) /* no need to search */
												break;
											if(n) {
												if(usr[j]==n)
													break;
												continue; }
											username(&cfg,node.useron,str);
											if(!strnicmp(str,p,strlen(str)))
												break;
											getuserrec(&cfg,node.useron,U_HANDLE
												,LEN_HANDLE,str);
											if(!strnicmp(str,p,strlen(str)))
												break; }
										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]);
										else
											username(&cfg,node.useron,str);

										/* Display on same node */
										bprintf(cfg.chatact[i]->out
											,thisnode.misc&NODE_ANON
											? text[UNKNOWN_USER] : useron.alias
											,str);
										CRLF;

										if(usrs && j<usrs) {
											/* Display to dest user */
											sprintf(buf,cfg.chatact[i]->out
												,thisnode.misc&NODE_ANON
												? text[UNKNOWN_USER] : useron.alias
												,"you");
											strcat(buf,crlf);
											putnmsg(usr[j],buf); }


										/* Display to all other users */
										sprintf(buf,cfg.chatact[i]->out
											,thisnode.misc&NODE_ANON
											? text[UNKNOWN_USER] : useron.alias
											,str);
										strcat(buf,crlf);

										for(i=0;i<usrs;i++) {
											if(i==j)
												continue;
											getnodedat(usr[i],&node,0);
											putnmsg(usr[i],buf); }
										for(i=0;i<qusrs;i++) {
											getnodedat(qusr[i],&node,0);
											putnmsg(qusr[i],buf); }
										continue; } }

								sprintf(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++) {
									getnodedat(usr[i],&node,0);
									putnmsg(usr[i],buf); }
								for(i=0;i<qusrs;i++) {
									getnodedat(qusr[i],&node,0);
									putnmsg(qusr[i],buf); }
								if(!usrs && channel && gurubuf
									&& cfg.chan[channel-1]->misc&CHAN_GURU)
									guruchat(pgraph,gurubuf,cfg.chan[channel-1]->guru);
									} } }
					else
						mswait(1);
					if(sys_status&SS_ABORT)
						break; }
				lncntr=0;
				break;
			case 'P':   /* private node-to-node chat */
				privchat();
				break;
			case 'C':
				no_rip_menu=1;
				if(cfg.startup->options&BBS_OPT_SYSOP_AVAILABLE
					|| (cfg.sys_chat_ar[0] && chk_ar(cfg.sys_chat_ar,&useron))
					|| useron.exempt&FLAG('C')) {
					sysop_page();
					break; }
				bprintf(text[SysopIsNotAvailable],cfg.sys_op);
				if(cfg.total_gurus && chk_ar(cfg.guru[0]->ar,&useron)) {
					sprintf(str,text[ChatWithGuruInsteadQ],cfg.guru[0]->name);
					if(!yesno(str))
						break; }
				else
					break;
			case 'T':
				if(!cfg.total_gurus) {
					bprintf(text[SysopIsNotAvailable],"The Guru");
					break; }
				if(cfg.total_gurus==1 && chk_ar(cfg.guru[0]->ar,&useron))
					i=0;
				else {
					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)
						break; }
				if(gurubuf)
					FREE(gurubuf);
				sprintf(str,"%s%s.dat",cfg.ctrl_dir,cfg.guru[i]->code);
				if((file=nopen(str,O_RDONLY))==-1) {
					errormsg(WHERE,ERR_OPEN,str,O_RDONLY);
					return; }
				if((gurubuf=(char *)MALLOC(filelength(file)+1))==NULL) {
					close(file);
					errormsg(WHERE,ERR_ALLOC,str,filelength(file)+1);
					return; }
				read(file,gurubuf,filelength(file));
				gurubuf[filelength(file)]=0;
				close(file);
				localguru(gurubuf,i);
				no_rip_menu=1;
				FREE(gurubuf);
				gurubuf=NULL;
				break;
			case '?':
				if(useron.misc&EXPERT)
				break;
			default:	/* 'Q' or <CR> */
				lncntr=0;
				if(gurubuf)
					FREE(gurubuf);
				return; }
		action=NODE_CHAT;
		if(!(useron.misc&EXPERT) || useron.misc&WIP
			|| (useron.misc&RIP && !no_rip_menu)) {
	#if 0 /* legacy */
			sys_status&=~SS_ABORT;
			if(lncntr) {
				SYNC;
				CRLF;
				if(lncntr)			/* CRLF or SYNC can cause pause */
					pause(); }
	#endif
		ASYNC;
		bputs(text[ChatPrompt]); }
	if(gurubuf)
		FREE(gurubuf);
}

void sbbs_t::sysop_page(void)
{
	int i;

	for(i=0;i<cfg.total_pages;i++)
		if(chk_ar(cfg.page[i]->ar,&useron))
			break;
	if(i<cfg.total_pages) {
		bprintf(text[PagingGuru],cfg.sys_op);
		external(cmdstr(cfg.page[i]->cmd,nulstr,nulstr,NULL)
			,cfg.page[i]->misc&IO_INTS ? EX_OUTL|EX_OUTR|EX_INR
				: EX_OUTL); }
	else if(cfg.sys_misc&SM_SHRTPAGE) {
		bprintf(text[PagingGuru],cfg.sys_op);
		for(i=0;i<10 && !lkbrd(1);i++) {
			mswait(200);
			outchar('.'); }
		CRLF; }
	else {
		sys_status^=SS_SYSPAGE;
		bprintf(text[SysopPageIsNow]
			,sys_status&SS_SYSPAGE ? text[ON] : text[OFF]);
		nosound();	}
}

/****************************************************************************/
/* Returns 1 if user online has access to channel "channum"                 */
/****************************************************************************/
bool sbbs_t::chan_access(uint cnum)
{

	if(!cfg.total_chans || cnum>=cfg.total_chans || !chk_ar(cfg.chan[cnum]->ar,&useron)) {
		bputs(text[CantAccessThatChannel]);
		return(false); }
	if(!(useron.exempt&FLAG('J')) && cfg.chan[cnum]->cost>useron.cdt+useron.freecdt) {
		bputs(text[NotEnoughCredits]);
		return(false); }
	return(true);
}

/****************************************************************************/
/* Private split-screen (or interspersed) chat with node or local sysop		*/
/****************************************************************************/
#define CHAT_REDRAW 18	/* Ctrl-R */
void sbbs_t::privchat(bool local)
{
	char	str[128],c,*p,localbuf[5][81],remotebuf[5][81]
			,localline=0,remoteline=0,localchar=0,remotechar=0
			,*sep="\1w\1h[\1i\1r%c\1n\1h]Ĵ "
				"\1yPrivate Chat - \1rCtrl-C to Quit \1y- "
				"Time Left: \1g%-8s\1w"
				" [\1i\1b%c\1n\1h]"
			,*local_sep="\1w\1h[\1i\1r%c\1n\1h]Ĵ "
				"\1rSplit-Screen \1cSysop\1r Chat          \1y"
				"Time Left: \1g%-8s\1w"
				" [\1i\1b%c\1n\1h]";
	uchar	ch;
	int 	in,out,i,n,echo=1,x,y,activity,remote_activity;
    int		local_y,remote_y;
	node_t	node;

	if(local) 
		n=0;
	else {
		n=getnodetopage(0,0);
		if(!n)
			return;
		if(n==cfg.node_num) {
			bputs(text[NoNeedToPageSelf]);
			return; }
		getnodedat(n,&node,0);
		if(node.action==NODE_PCHT && node.aux!=cfg.node_num) {
			bprintf(text[NodeNAlreadyInPChat],n);
			return; }
		if((node.action!=NODE_PAGE || node.aux!=cfg.node_num)
			&& node.misc&NODE_POFF && !SYSOP) {
			bprintf(text[CantPageNode],node.misc&NODE_ANON
				? text[UNKNOWN_USER] : username(&cfg,node.useron,tmp));
			return; }
		if(node.action!=NODE_PAGE) {
			bprintf("\r\n\1n\1mPaging \1h%s #%u\1n\1m for private chat\r\n"
				,node.misc&NODE_ANON ? text[UNKNOWN_USER] : username(&cfg,node.useron,tmp)
				,node.useron);
			sprintf(str,text[NodePChatPageMsg]
				,cfg.node_num,thisnode.misc&NODE_ANON
					? text[UNKNOWN_USER] : useron.alias);
			putnmsg(n,str);
			sprintf(str,"Paged %s on node %d to private chat"
				,username(&cfg,node.useron,tmp),n);
			logline("C",str); }

		getnodedat(cfg.node_num,&thisnode,1);
		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)) {
				getnodedat(n,&node,0);
				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));
					break; }
				if(!inkey(0))
					mswait(1);
				action=NODE_PAGE;
				checkline();
				gettimeleft();
				SYNC; } }
	}

	getnodedat(cfg.node_num,&thisnode,1);
	thisnode.action=action=NODE_PCHT;
	thisnode.aux=n;
	thisnode.misc&=~NODE_LCHAT;
	putnodedat(cfg.node_num,&thisnode);

	if(!online || sys_status&SS_ABORT)
		return;

	if(useron.chat&CHAT_SPLITP && useron.misc&ANSI && rows>=24)
		sys_status|=SS_SPLITP;
	/*
	if(!(useron.misc&EXPERT))
	*/

	if(!(sys_status&SS_SPLITP)) {
		if(local)
			bprintf(text[SysopIsHere],cfg.sys_op);
		else
			bputs(text[WelcomeToPrivateChat]);
	}

	if((out=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO))==-1) {
		errormsg(WHERE,ERR_OPEN,str,O_RDWR|O_DENYNONE|O_CREAT);
		return; }

	if(local)
		sprintf(str,"%schat.dab",cfg.node_path[n-1]);
	if(!fexist(str))		/* Wait while it's created for the first time */
		mswait(2000);
	if((in=sopen(str,O_RDWR|O_CREAT|O_BINARY,SH_DENYNO))==-1) {
		close(out);
		errormsg(WHERE,ERR_OPEN,str,O_RDWR|O_DENYNONE|O_CREAT);
		return; }

	if((p=(char *)MALLOC(PCHAT_LEN))==NULL) {
		close(in);
		close(out);
		errormsg(WHERE,ERR_ALLOC,str,PCHAT_LEN);
		return; }
	memset(p,0,PCHAT_LEN);
	write(in,p,PCHAT_LEN);
	write(out,p,PCHAT_LEN);
	FREE(p);
	lseek(in,0L,SEEK_SET);
	lseek(out,0L,SEEK_SET);

	getnodedat(cfg.node_num,&thisnode,1);
	thisnode.misc&=~NODE_RPCHT; 		/* Clear "reset pchat flag" */
	putnodedat(cfg.node_num,&thisnode);

	if(!local) {
		getnodedat(n,&node,1);
		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)) {
			getnodedat(n,&node,0);
			if(!(node.misc&NODE_RPCHT))
				break;
			getnodedat(cfg.node_num,&thisnode,0);
			if(thisnode.misc&NODE_RPCHT)
				break;
			checkline();
			gettimeleft();
			SYNC;
			inkey(0);
			mswait(1); }
	}

	action=NODE_PCHT;
	SYNC;

	if(sys_status&SS_SPLITP) {
		lncntr=0;
		CLS;
		ANSI_SAVE();
#if 0
		if(local)
			bprintf(text[SysopIsHere],cfg.sys_op);
#endif
		GOTOXY(1,13);
		remote_y=1;
		bprintf(local ? local_sep : sep
			,thisnode.misc&NODE_MSGW ? 'T':SP
			,sectostr(timeleft,tmp)
			,thisnode.misc&NODE_NMSG ? 'M':SP);
		CRLF;
		local_y=14; }

	while(online && (local || !(sys_status&SS_ABORT))) {
		RIOSYNC(0);
		lncntr=0;
		if(sys_status&SS_SPLITP)
			lbuflen=0;
		action=NODE_PCHT;
		if(!localchar) {
			if(sys_status&SS_SPLITP) {
				getnodedat(cfg.node_num,&thisnode,0);
				if(thisnode.misc&NODE_INTR)
					break;
				if(thisnode.misc&NODE_UDAT && !(useron.rest&FLAG('G'))) {
					getuserdat(&cfg,&useron);
					getnodedat(cfg.node_num,&thisnode,1);
					thisnode.misc&=~NODE_UDAT;
					putnodedat(cfg.node_num,&thisnode); } }
			else
				nodesync(); }
		activity=0;
		remote_activity=0;
		if((ch=inkey(K_GETSTR))!=0) {
			activity=1;
			if(echo)
				attr(cfg.color[clr_chatlocal]);
			if(ch==BS || ch==DEL) {
				if(localchar) {
					if(echo)
						bputs("\b \b");
					localchar--;
					localbuf[localline][localchar]=0; } }
			else if(ch==TAB) {
				if(echo)
					outchar(SP);
				localbuf[localline][localchar]=SP;
				localchar++;
				while(localchar<78 && localchar%8) {
					if(echo)
						outchar(SP);
					localbuf[localline][localchar++]=SP; } }
			else if(ch==CHAT_REDRAW) {
				if(sys_status&SS_SPLITP) {
					CLS;
					attr(cfg.color[clr_chatremote]);
					remotebuf[remoteline][remotechar]=0;
					for(i=0;i<=remoteline;i++) {
						bputs(remotebuf[i]); 
						if(i!=remoteline) 
							bputs(crlf);
					}
					remote_y=1+remoteline;
					bputs("\1i_\1n");  /* Fake cursor */
					ANSI_SAVE();
					GOTOXY(1,13);
					bprintf(local ? local_sep : sep
						,thisnode.misc&NODE_MSGW ? 'T':SP
						,sectostr(timeleft,tmp)
						,thisnode.misc&NODE_NMSG ? 'M':SP);
					CRLF;
					attr(cfg.color[clr_chatlocal]);
					localbuf[localline][localchar]=0;
					for(i=0;i<=localline;i++) {
						bputs(localbuf[i]);
						if(i!=localline) 
							bputs(crlf);
					}
					local_y=15+localline;
				}
			else if(ch>=SP || ch==CR) {
				if(ch!=CR) {
					if(echo)
						outchar(ch);
					localbuf[localline][localchar]=ch; }

				if(ch==CR || (localchar>68 && ch==SP) || ++localchar>78) {

					localbuf[localline][localchar]=0;
					localchar=0;

					if(sys_status&SS_SPLITP && local_y==24) {
						GOTOXY(1,13);
						bprintf(local ? local_sep : sep
							,thisnode.misc&NODE_MSGW ? 'T':SP
							,sectostr(timeleft,tmp)
							,thisnode.misc&NODE_NMSG ? 'M':SP);
						attr(cfg.color[clr_chatlocal]);
						for(x=13,y=0;x<rows;x++,y++) {
							bprintf("\x1b[%d;1H\x1b[K",x+1);
							if(y<=localline)
								bprintf("%s\r\n",localbuf[y]); }
						GOTOXY(1,local_y=(15+localline));
						localline=0; }
					else {
						if(localline>=4)
							for(i=0;i<4;i++)
								memcpy(localbuf[i],localbuf[i+1],81);
						else
							localline++;
						if(echo) {
							CRLF;
		            		local_y++;
							if(sys_status&SS_SPLITP)
								bputs("\x1b[K"); } }
					// SYNC;
					} }

			read(out,&c,1);
			lseek(out,-1L,SEEK_CUR);
			if(!c)		/* hasn't wrapped */
				write(out,&ch,1);
			else {
				if(!tell(out))
					lseek(out,0L,SEEK_END);
				lseek(out,-1L,SEEK_CUR);
				ch=0;
				write(out,&ch,1);
				lseek(out,-1L,SEEK_CUR); }
			if(tell(out)>=PCHAT_LEN)
				lseek(out,0L,SEEK_SET);
			}
		else while(online) {
			if(!(sys_status&SS_SPLITP))
				remotechar=localchar;
			if(tell(in)>=PCHAT_LEN)
				lseek(in,0L,SEEK_SET);
			ch=0;
			read(in,&ch,1);
			lseek(in,-1L,SEEK_CUR);
			if(!ch) break;					  /* char from other node */
			activity=1;
			if(sys_status&SS_SPLITP && !remote_activity) {
				ansi_getxy(&x,&y);
				ANSI_RESTORE();
			}
			attr(cfg.color[clr_chatremote]);
			if(sys_status&SS_SPLITP && !remote_activity)
				bputs("\b \b");             /* Delete fake cursor */
			remote_activity=1;
			if(ch==BS || ch==DEL) {
				if(remotechar) {
					bputs("\b \b");
					remotechar--;
					remotebuf[remoteline][remotechar]=0; } }
			else if(ch==TAB) {
				outchar(SP);
				remotebuf[remoteline][remotechar]=SP;
				remotechar++;
				while(remotechar<78 && remotechar%8) {
					outchar(SP);
					remotebuf[remoteline][remotechar++]=SP; } }
			else if(ch>=SP || ch==CR) {
				if(ch!=CR) {
					outchar(ch);
					remotebuf[remoteline][remotechar]=ch; }

				if(ch==CR || (remotechar>68 && ch==SP) || ++remotechar>78) {

					remotebuf[remoteline][remotechar]=0;
					remotechar=0;

					if(sys_status&SS_SPLITP && remote_y==12) {
						CRLF;
						bprintf(local ? local_sep : sep
							,thisnode.misc&NODE_MSGW ? 'T':SP
							,sectostr(timeleft,tmp)
							,thisnode.misc&NODE_NMSG ? 'M':SP);
						attr(cfg.color[clr_chatremote]);
						for(i=0;i<12;i++) {
							bprintf("\x1b[%d;1H\x1b[K",i+1);
							if(i<=remoteline)
								bprintf("%s\r\n",remotebuf[i]); }
						remoteline=0;
						GOTOXY(1, remote_y=6); }
					else {
						if(remoteline>=4)
							for(i=0;i<4;i++)
								memcpy(remotebuf[i],remotebuf[i+1],81);
						else
							remoteline++;