Skip to content
Snippets Groups Projects
atcodes.cpp 55.5 KiB
Newer Older
/* Synchronet "@code" 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"
#include "cmdshell.h"
#include "utf8.h"
#include "unicode.h"
#if defined(_WINSOCKAPI_)
	extern WSADATA WSAData;
	#define SOCKLIB_DESC WSAData.szDescription
#else
	#define	SOCKLIB_DESC NULL
#endif

static char* separate_thousands(const char* src, char *dest, size_t maxlen, char sep)
{
	if(strlen(src) * 1.3 > maxlen)
		return (char*)src;
	const char* tail = src;
	while(*tail && IS_DIGIT(*tail))
		tail++;
	if(tail == src)
		return (char*)src;
	size_t digits = tail - src;
	char* d = dest;
	for(size_t i = 0; i < digits; d++, i++) {
		*d = src[i];
		if(i && i + 3 < digits && (digits - (i + 1)) % 3 == 0)
			*(++d) = sep;
	}
	*d = 0;
	strcpy(d, tail);
	return dest;
}

/****************************************************************************/
/* Returns 0 if invalid @ code. Returns length of @ code if valid.          */
/****************************************************************************/
int sbbs_t::show_atcode(const char *instr, JSObject* obj)
	char	str[128],str2[128],*tp,*sp,*p;
    int     len;
rswindell's avatar
rswindell committed
	bool	truncated = true;
	bool	doubled = false;
	bool	thousep = false;	// thousands-separated
	int		pmode = 0;
	SAFECOPY(str,instr);
	tp=strchr(str+1,'@');
	if(!tp)                 /* no terminating @ */
		return(0);
	sp=strchr(str+1,' ');
	if(sp && sp<tp)         /* space before terminating @ */
		return(0);
	len=(tp-str)+1;
	(*tp)=0;
	sp=(str+1);

	if(*sp == '~' && *(sp + 1)) {	// Mouse hot-spot (hungry)
		sp++;
		tp = strchr(sp + 1, '~');
		if(tp == NULL)
			tp = sp;
		else {
			*tp = 0;
			tp++;
		}
		c_unescape_str(tp);
		add_hotspot(tp, /* hungry: */true, column, column + strlen(sp) - 1, row);
		bputs(sp);
		return len;
	}

	if(*sp == '`' && *(sp + 1)) {	// Mouse hot-spot (strict)
		sp++;
		tp = strchr(sp + 1, '`');
		if(tp == NULL)
			tp = sp;
		else {
			*tp = 0;
			tp++;
		}
		c_unescape_str(tp);
		add_hotspot(tp, /* hungry: */false, column, column + strlen(sp) - 1, row);
		bputs(sp);
		return len;
	}

	// @!x@ for Ctrl-A x equivalent(s) */
	if(*sp == '!') {
		for(p = sp + 1; *p != '\0' && *p != '@'; p++)
			ctrl_a(*p);
		return len;
	}

	if((p = strchr(sp, '|')) != NULL) {
		if(strchr(p, 'T') != NULL)
			thousep = true;
		if(strchr(p, 'U') != NULL)
			uppercase = true;
		else if(strchr(p, 'W') != NULL)
			doubled = true;
		else if(strchr(p, 'Z') != NULL)
			zero_padded = true;
		else if(strchr(p, '>') != NULL)
			truncated = false;
	}
	else if(strchr(sp, ':') != NULL)
		p = NULL;
	else if((p=strstr(sp,"-L"))!=NULL)
	else if((p=strstr(sp,"-R"))!=NULL)
	else if((p=strstr(sp,"-C"))!=NULL)
rswindell's avatar
rswindell committed
	else if((p=strstr(sp,"-W"))!=NULL)	/* wide */
		doubled=true;
	else if((p=strstr(sp,"-Z"))!=NULL)
		zero_padded=true;
	else if((p=strstr(sp,"-T"))!=NULL)
		thousep=true;
	else if((p=strstr(sp,"-U"))!=NULL)
		uppercase=true;
rswindell's avatar
rswindell committed
	else if((p=strstr(sp,"->"))!=NULL)	/* wrap */
rswindell's avatar
rswindell committed
		truncated = false;
		while(*lp == '>'|| IS_ALPHA(*lp))
		while(*lp && !IS_DIGIT(*lp))
rswindell's avatar
rswindell committed
			disp_len=atoi(lp);
	cp = atcode(sp, str2, sizeof(str2), &pmode, align == center, obj);
	char separated[128];
	if(thousep)
		cp = separate_thousands(cp, separated, sizeof(separated), ',');

	char upper[128];
	if(uppercase) {
		SAFECOPY(upper, cp);
		strupr(upper);
		cp = upper;
	}

	if(p==NULL || truncated == false || (width_specified == false && align == none))
rswindell's avatar
rswindell committed
		disp_len = strlen(cp);

	if(truncated && strchr(cp, '\n') == NULL) {
rswindell's avatar
rswindell committed
		if(column + disp_len > cols - 1) {
			if(column >= cols - 1)
				disp_len = 0;
			else
				disp_len = (cols - 1) - column;
		}
	}
	if(pmode & P_UTF8) {
		if(term_supports(UTF8))
			disp_len += strlen(cp) - utf8_str_total_width(cp);
		else
			disp_len += strlen(cp) - utf8_str_count_width(cp, /* min: */1, /* max: */2);
	}
		bprintf(pmode, "%-*.*s",disp_len,disp_len,cp);
		bprintf(pmode, "%*.*s",disp_len,disp_len,cp);
rswindell's avatar
rswindell committed
		int vlen = strlen(cp);
		if(vlen < disp_len) {
			int left = (disp_len - vlen) / 2;
			bprintf(pmode, "%*s%-*s", left, "", disp_len - left, cp);
			bprintf(pmode, "%.*s", disp_len, cp);
	} else if(doubled) {
		wide(cp);
		int vlen = strlen(cp);
		if(vlen < disp_len)
			bprintf(pmode, "%-.*s%s", (int)(disp_len - strlen(cp)), "0000000000", cp);
			bprintf(pmode, "%.*s", disp_len, cp);
		bprintf(pmode, "%.*s", disp_len, cp);
static const char* getpath(scfg_t* cfg, const char* path)
{
	for(int i = 0; i < cfg->total_dirs; i++) {
		if(stricmp(cfg->dir[i]->code, path) == 0)
			return cfg->dir[i]->path;
	}
	return path;
}

const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, int* pmode, bool centered, JSObject* obj)
	long	l;
    stats_t stats;
    node_t  node;
	struct	tm tm;

	str[0]=0;

	if(strcmp(sp, "SHOW") == 0) {
		console &= ~CON_ECHO_OFF;
		return nulstr;
	}
	if(strncmp(sp, "SHOW:", 5) == 0) {
		uchar* ar = arstr(NULL, sp + 5, &cfg, NULL);
		if(ar != NULL) {
			if(!chk_ar(ar, &useron, &client))
				console |= CON_ECHO_OFF;
			else
				console &= ~CON_ECHO_OFF;
			free(ar);
		}
		return nulstr;
	}

	if(strcmp(sp, "HOT") == 0) { // Auto-mouse hot-spot attribute
		hot_attr = curatr;
		return nulstr;
	if(strncmp(sp, "HOT:", 4) == 0) {	// Auto-mouse hot-spot attribute
		sp += 4;
		if(stricmp(sp, "hungry") == 0) {
			hungry_hotspots = true;
			hot_attr = curatr;
		}
		else if(stricmp(sp, "strict") == 0) {
			hungry_hotspots = false;
			hot_attr = curatr;
		}
		else if(stricmp(sp, "off") == 0)
			hot_attr = 0;
		else
			hot_attr = attrstr(sp);
	if(strcmp(sp, "CLEAR_HOT") == 0) {
		clear_hotspots();
		return nulstr;
	}
	if(strncmp(sp, "U+", 2) == 0) {	// UNICODE
		enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
rswindell's avatar
rswindell committed
		if(tp == NULL || *tp == 0)
			outchar(codepoint, unicode_to_cp437(codepoint));
rswindell's avatar
rswindell committed
		else if(*tp == ':')
			outchar(codepoint, tp + 1);
		else {
			char fallback = (char)strtoul(tp + 1, NULL, 16);
rswindell's avatar
rswindell committed
			if(*tp == ',')
				outchar(codepoint, fallback);
			else if(*tp == '!') {
				char ch = unicode_to_cp437(codepoint);
				if(ch != 0)
					fallback = ch;
				outchar(codepoint, fallback);
			}
			else return NULL; // Invalid @-code
		}
		return nulstr;
	}

	if(strcmp(sp, "CHECKMARK") == 0) {
		outchar(UNICODE_CHECK_MARK, CP437_CHECK_MARK);
		return nulstr;
	}

rswindell's avatar
rswindell committed
	if(strcmp(sp, "ELLIPSIS") == 0) {
		outchar(UNICODE_HORIZONTAL_ELLIPSIS, "...");
		return nulstr;
	}
	if(strcmp(sp, "COPY") == 0) {
rswindell's avatar
rswindell committed
		outchar(UNICODE_COPYRIGHT_SIGN, "(C)");
		return nulstr;
	}
	if(strcmp(sp, "SOUNDCOPY") == 0) {
		outchar(UNICODE_SOUND_RECORDING_COPYRIGHT, "(P)");
		return nulstr;
	}
rswindell's avatar
rswindell committed
	if(strcmp(sp, "REGISTERED") == 0) {
		outchar(UNICODE_REGISTERED_SIGN, "(R)");
		return nulstr;
	}
rswindell's avatar
rswindell committed
	if(strcmp(sp, "TRADEMARK") == 0) {
		outchar(UNICODE_TRADE_MARK_SIGN, "(TM)");
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_C") == 0) {
rswindell's avatar
rswindell committed
		outchar(UNICODE_DEGREE_CELSIUS, "\xF8""C");
rswindell's avatar
rswindell committed
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_F") == 0) {
rswindell's avatar
rswindell committed
		outchar(UNICODE_DEGREE_FAHRENHEIT, "\xF8""F");
rswindell's avatar
rswindell committed
		return nulstr;
	}

rswindell's avatar
rswindell committed
	if(strncmp(sp, "WIDE:", 5) == 0) {
		wide(sp + 5);
		return(nulstr);
	}

	if(!strcmp(sp,"VER"))
		return(VERSION);
	if(!strcmp(sp,"REV")) {
		safe_snprintf(str,maxlen,"%c",REVISION);
		return(str);
	}
	if(!strcmp(sp,"FULL_VER")) {
		safe_snprintf(str,maxlen,"%s%c%s",VERSION,REVISION,beta_version);
		truncsp(str);
		strcat(str," Debug");
		return(str);
		return(VERSION_NOTICE);
	if(!strcmp(sp,"OS_VER"))
		return(os_version(str, maxlen));

	if(strcmp(sp,"OS_CPU") == 0)
		return(os_cpuarch(str, maxlen));
	if(!strcmp(sp,"JS_VER"))
		return((char *)JS_GetImplementationVersion());
	if(!strcmp(sp,"PLATFORM"))
		return(PLATFORM_DESC);
	if(!strcmp(sp,"COPYRIGHT"))
		return(COPYRIGHT_NOTICE);
	if(!strcmp(sp,"COMPILER")) {
		char compiler[32];
		DESCRIBE_COMPILER(compiler);
		strncpy(str, compiler, maxlen);
		return(str);
	if(strcmp(sp, "GIT_HASH") == 0)
		return git_hash;

	if(strcmp(sp, "GIT_BRANCH") == 0)
		return git_branch;

	if(strcmp(sp, "BUILD_DATE") == 0)
		return __DATE__;

	if(strcmp(sp, "BUILD_TIME") == 0)
		return __TIME__;

	if(!strcmp(sp,"UPTIME")) {
		time_t up=0;
		now = time(NULL);
		if (uptime != 0 && now >= uptime)
			up = now-uptime;
		char   days[64]="";
		if((up/(24*60*60))>=2) {
	        sprintf(days,"%u days ",(uint)(up/(24L*60L*60L)));
		safe_snprintf(str,maxlen,"%s%u:%02u"
			,(uint)(up/(60L*60L))
			,(uint)((up/60L)%60L)
		return(str);
		extern volatile uint served;
		safe_snprintf(str,maxlen,"%u",served);
		return(socklib_version(str,SOCKLIB_DESC));
	if(!strcmp(sp,"MSG_LIB")) {
		safe_snprintf(str,maxlen,"SMBLIB %s",smb_lib_ver());
		return(str);
	}
	if(!strcmp(sp,"BBS") || !strcmp(sp,"BOARDNAME"))
		return(cfg.sys_name);
	if(!strcmp(sp,"BAUD") || !strcmp(sp,"BPS")) {
		safe_snprintf(str,maxlen,"%u",cur_output_rate ? cur_output_rate : cur_rate);
		return(str);
	}
	if(!strcmp(sp,"CPS")) {
		safe_snprintf(str,maxlen,"%u",cur_cps);
		return(str);
	}

rswindell's avatar
rswindell committed
	if(!strcmp(sp,"COLS")) {
		safe_snprintf(str,maxlen,"%u",cols);
rswindell's avatar
rswindell committed
		return(str);
	}
	if(!strcmp(sp,"ROWS")) {
		safe_snprintf(str,maxlen,"%u",rows);
rswindell's avatar
rswindell committed
		return(str);
	}
	if(strcmp(sp,"TERM") == 0)
		return term_type();

	if(strcmp(sp,"CHARSET") == 0)
		return term_charset();
	if(!strcmp(sp,"CONN"))
		return(connection);
	if(!strcmp(sp,"SYSOP"))
		return(cfg.sys_op);
	if(strcmp(sp, "SYSAVAIL") == 0)
		return text[sysop_available(&cfg) ? LiSysopAvailable : LiSysopNotAvailable];

	if(strcmp(sp, "SYSAVAILYN") == 0)
		return text[sysop_available(&cfg) ? Yes : No];

	if(!strcmp(sp,"LOCATION"))
		return(cfg.sys_location);
	if(strcmp(sp,"NODE") == 0 || strcmp(sp,"NN") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.node_num);
		return(str);
	}
	if(strcmp(sp, "TNODES") == 0 || strcmp(sp, "TNODE") == 0 || strcmp(sp, "TN") == 0) {
		safe_snprintf(str,maxlen,"%u",cfg.sys_nodes);
		return(str);
	}
	if(strcmp(sp, "ANODES") == 0 || strcmp(sp, "ANODE") == 0 || strcmp(sp, "AN") == 0) {
		safe_snprintf(str, maxlen, "%u", count_nodes(/* self: */true));
		return str;
	}
	if(strcmp(sp, "ONODES") == 0 || strcmp(sp, "ONODE") == 0 || strcmp(sp, "ON") == 0) {
		safe_snprintf(str, maxlen, "%u", count_nodes(/* self: */false));
		return str;
	}
rswindell's avatar
rswindell committed
	if(strcmp(sp, "PWDAYS") == 0) {
		if(cfg.sys_pwdays) {
			safe_snprintf(str, maxlen, "%u", cfg.sys_pwdays);
			return str;
		}
		return text[Unlimited];
	}

	if(strcmp(sp, "AUTODEL") == 0) {
		if(cfg.sys_autodel) {
			safe_snprintf(str, maxlen, "%u", cfg.sys_autodel);
			return str;
		}
		return text[Unlimited];
	}

	if(strcmp(sp, "PAGER") == 0)
		return (thisnode.misc&NODE_POFF) ? text[Off] : text[On];

	if(strcmp(sp, "ALERTS") == 0)
		return (thisnode.misc&NODE_AOFF) ? text[Off] : text[On];

	if(strcmp(sp, "SPLITP") == 0)
		return (useron.chat&CHAT_SPLITP) ? text[On] : text[Off];

	if(!strcmp(sp,"INETADDR"))
		return(cfg.sys_inetaddr);
	if(!strcmp(sp,"HOSTNAME"))
	if(!strcmp(sp,"FIDOADDR")) {
		if(cfg.total_faddrs)
			return(smb_faddrtoa(&cfg.faddr[0],str));
		return(nulstr);
		return(usermailaddr(&cfg, str
			,(cfg.inetmail_misc&NMAIL_ALIAS) || (useron.rest&FLAG('O')) ? useron.alias : useron.name));
	if(strcmp(sp, "NETMAIL") == 0)
		return useron.netmail;

	if(strcmp(sp, "TERMTYPE") == 0)
		return term_type(&useron, term_supports(), str, maxlen);

	if(strcmp(sp, "TERMROWS") == 0)
		return term_rows(&useron, str, maxlen);

	if(strcmp(sp, "TERMCOLS") == 0)
		return term_cols(&useron, str, maxlen);

	if(strcmp(sp, "AUTOTERM") == 0)
		return (useron.misc & AUTOTERM) ? text[On] : text[Off];

	if(strcmp(sp, "ANSI") == 0)
		return (useron.misc & ANSI) ? text[On] : text[Off];

	if(strcmp(sp, "ASCII") == 0)
		return (useron.misc & NO_EXASCII) ? text[On] : text[Off];

	if(strcmp(sp, "COLOR") == 0)
		return (useron.misc & COLOR) ? text[On] : text[Off];

	if(strcmp(sp, "ICE") == 0)
		return (useron.misc & ICE_COLOR) ? text[On] : text[Off];

	if(strcmp(sp, "RIP") == 0)
		return (useron.misc & RIP) ? text[On] : text[Off];

	if(strcmp(sp, "PETSCII") == 0)
		return (useron.misc & PETSCII) ? text[On] : text[Off];

	if(strcmp(sp, "SWAPDEL") == 0)
		return (useron.misc & SWAP_DELETE) ? text[On] : text[Off];

	if(strcmp(sp, "UTF8") == 0)
		return (useron.misc & UTF8) ? text[On] : text[Off];

	if(strcmp(sp, "MOUSE") == 0)
		return (useron.misc & MOUSE) ? text[On] : text[Off];

	if(strcmp(sp, "UPAUSE") == 0)
		return (useron.misc & UPAUSE) ? text[On] : text[Off];

	if(strcmp(sp, "SPIN") == 0)
		return (useron.misc & SPIN) ? text[On] : text[Off];

	if(strcmp(sp, "PAUSESPIN") == 0)
		return (useron.misc & NOPAUSESPIN) ? text[Off] : text[On];

	if(strcmp(sp, "EXPERT") == 0)
		return (useron.misc & EXPERT) ? text[On] : text[Off];

	if(strcmp(sp, "HOTKEYS") == 0)
		return (useron.misc & COLDKEYS) ? text[Off] : text[On]; 

	if(strcmp(sp, "MSGCLS") == 0)
		return (useron.misc & CLRSCRN) ? text[On] : text[Off];

	if(strcmp(sp, "FWD") == 0)
		return (useron.misc & NETMAIL) ? text[On] : text[Off];

	if(strcmp(sp, "REMSUBS") == 0)
		return (useron.misc & CURSUB) ? text[On] : text[Off];

	if(strcmp(sp, "FILEDESC") == 0)
		return (useron.misc & EXTDESC) ? text[On] : text[Off];

	if(strcmp(sp, "FILEFLAG") == 0)
		return (useron.misc & BATCHFLAG) ? text[On] : text[Off];

	if(strcmp(sp, "AUTOHANG") == 0)
		return (useron.misc & AUTOHANG) ? text[On] : text[Off];

	if(strcmp(sp, "AUTOLOGON") == 0)
		return (useron.misc & AUTOLOGON) ? text[On] : text[Off];

	if(strcmp(sp, "QUIET") == 0)
		return (useron.misc & QUIET) ? text[On] : text[Off];

	if(strcmp(sp, "ASKNSCAN") == 0)
		return (useron.misc & ASK_NSCAN) ? text[On] : text[Off];

	if(strcmp(sp, "ASKSSCAN") == 0)
		return (useron.misc & ASK_SSCAN) ? text[On] : text[Off];

	if(strcmp(sp, "ANFSCAN") == 0)
		return (useron.misc & ANFSCAN) ? text[On] : text[Off];

	if(strcmp(sp, "EDITOR") == 0)
		return (useron.xedit < 1 || useron.xedit >= cfg.total_xedits) ? text[None] : cfg.xedit[useron.xedit - 1]->name;

	if(strcmp(sp, "SHELL") == 0)
		return cfg.shell[useron.shell]->name;

	if(strcmp(sp, "TMP") == 0)
		return useron.tmpext;

	if(strcmp(sp, "SEX") == 0) {
		safe_snprintf(str, maxlen, "%c", useron.sex);
		return str;
	}
	if(strcmp(sp, "GENDERS") == 0) {
		return cfg.new_genders;
	}

	if(!strcmp(sp,"QWKID"))
		return(cfg.sys_id);
	if(!strcmp(sp,"TIME") || !strcmp(sp,"SYSTIME")) {
		now=time(NULL);
		localtime_r(&now,&tm);
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
		        	,tm.tm_hour,tm.tm_min,tm.tm_sec);
			safe_snprintf(str,maxlen,"%02d:%02d %s"
				,tm.tm_hour==0 ? 12
				: tm.tm_hour>12 ? tm.tm_hour-12
				: tm.tm_hour, tm.tm_min, tm.tm_hour>11 ? "pm":"am");
		return(str);
rswindell's avatar
rswindell committed
	if(!strcmp(sp,"TIMEZONE"))
		return(smb_zonestr(sys_timezone(&cfg),str));

	if(!strcmp(sp,"DATE") || !strcmp(sp,"SYSDATE")) {
		return(unixtodstr(&cfg,time32(NULL),str));
	if(strncmp(sp, "DATE:", 5) == 0 || strncmp(sp, "TIME:", 5) == 0) {
		sp += 5;
		c_unescape_str(sp);
		now = time(NULL);
		memset(&tm, 0, sizeof(tm));
		localtime_r(&now, &tm);
		strftime(str, maxlen, sp, &tm);
		return str;
	}

rswindell's avatar
rswindell committed
	if(!strcmp(sp,"DATETIME"))
		return(timestr(time(NULL)));

rswindell's avatar
rswindell committed
	if(!strcmp(sp,"DATETIMEZONE")) {
		char zone[32];
		safe_snprintf(str, maxlen, "%s %s", timestr(time(NULL)), smb_zonestr(sys_timezone(&cfg),zone));
		return str;
	}
	
	if(strcmp(sp, "DATEFMT") == 0) {
		return cfg.sys_misc&SM_EURODATE ? "DD/MM/YY" : "MM/DD/YY";
	}
rswindell's avatar
rswindell committed

	if(strcmp(sp, "BDATEFMT") == 0 || strcmp(sp, "BIRTHFMT") == 0) {
		return birthdate_format(&cfg);
	}

	if(strcmp(sp, "GENDERS") == 0)
		return cfg.new_genders;

	if(!strcmp(sp,"TMSG")) {
		l=0;
		for(i=0;i<cfg.total_subs;i++)
			l+=getposts(&cfg,i); 		/* l=total posts */
		safe_snprintf(str,maxlen,"%lu",l);
		return(str);
	}
	if(!strcmp(sp,"TUSER")) {
		safe_snprintf(str,maxlen,"%u",total_users(&cfg));
		return(str);
	}
	if(!strcmp(sp,"TFILE")) {
		l=0;
		for(i=0;i<cfg.total_dirs;i++)
			l+=getfiles(&cfg,i);
		safe_snprintf(str,maxlen,"%lu",l);
		return(str);
	}
	if(strncmp(sp, "FILES:", 6) == 0) {	// Number of files in specified directory
		const char* path = getpath(&cfg, sp + 6);
		safe_snprintf(str, maxlen, "%u", getfilecount(path));
		return str;
	}

	if(strcmp(sp, "FILES") == 0) {	// Number of files in current directory
		safe_snprintf(str, maxlen, "%u", getfiles(&cfg, usrdir[curlib][curdir[curlib]]));
	if(strncmp(sp, "FILESIZE:", 9) == 0) {
		const char* path = getpath(&cfg, sp + 9);
		byte_estimate_to_str(getfilesizetotal(path), str, maxlen, /* unit: */1, /* precision: */1);
		return str;
	}

	if(strcmp(sp, "FILESIZE") == 0) {
		byte_estimate_to_str(getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path)
			,str, maxlen, /* unit: */1, /* precision: */1);
		return str;
	}

	if(strncmp(sp, "FILEBYTES:", 10) == 0) {	// Number of bytes in current file directory
		const char* path = getpath(&cfg, sp + 10);
		safe_snprintf(str, maxlen, "%" PRIu64, getfilesizetotal(path));
		return str;
	}

	if(strcmp(sp, "FILEBYTES") == 0) {	// Number of bytes in current file directory
		safe_snprintf(str, maxlen, "%" PRIu64
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path));
		return str;
	}

	if(strncmp(sp, "FILEKB:", 7) == 0) {	// Number of kibibytes in current file directory
		const char* path = getpath(&cfg, sp + 7);
		safe_snprintf(str, maxlen, "%1.1f", getfilesizetotal(path) / 1024.0);
		return str;
	}

	if(strcmp(sp, "FILEKB") == 0) {	// Number of kibibytes in current file directory
		safe_snprintf(str, maxlen, "%1.1f"
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path) / 1024.0);
		return str;
	}

	if(strncmp(sp, "FILEMB:", 7) == 0) {	// Number of mebibytes in current file directory
		const char* path = getpath(&cfg, sp + 7);
		safe_snprintf(str, maxlen, "%1.1f", getfilesizetotal(path) / (1024.0 * 1024.0));
		return str;
	}

	if(strcmp(sp, "FILEMB") == 0) {	// Number of mebibytes in current file directory
		safe_snprintf(str, maxlen, "%1.1f"
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path) / (1024.0 * 1024.0));
		return str;
	}

	if(strncmp(sp, "FILEGB:", 7) == 0) {	// Number of gibibytes in current file directory
		const char* path = getpath(&cfg, sp + 7);
		safe_snprintf(str, maxlen, "%1.1f", getfilesizetotal(path) / (1024.0 * 1024.0 * 1024.0));
		return str;
	}

	if(strcmp(sp, "FILEGB") == 0) {	// Number of gibibytes in current file directory
		safe_snprintf(str, maxlen, "%1.1f"
			,getfilesizetotal(cfg.dir[usrdir[curlib][curdir[curlib]]]->path) / (1024.0 * 1024.0 * 1024.0));
		return str;
	}

	if(!strcmp(sp,"TCALLS") || !strcmp(sp,"NUMCALLS")) {
		getstats(&cfg,0,&stats);
		safe_snprintf(str,maxlen,"%u", stats.logons);
		return(str);
	}
	if(!strcmp(sp,"PREVON") || !strcmp(sp,"LASTCALLERNODE")
		|| !strcmp(sp,"LASTCALLERSYSTEM"))
		return(lastuseron);
rswindell's avatar
rswindell committed
	if(!strcmp(sp,"CLS") || !strcmp(sp,"CLEAR")) {
		return(nulstr);
	}
	if(strcmp(sp, "GETDIM") == 0) {
		ansi_getlines();
		return nulstr;
	}

	if(strcmp(sp, "GETKEY") == 0) {
		getkey();
		return(nulstr);
	}

Rob Swindell's avatar
Rob Swindell committed
	if(strcmp(sp, "CONTINUE") == 0) {
		char ch = getkey(K_UPPER);
		if(ch == no_key() || ch == quit_key())
Rob Swindell's avatar
Rob Swindell committed
			sys_status|=SS_ABORT;
		return(nulstr);
	}

	if(strncmp(sp, "WAIT:", 5) == 0) {
Rob Swindell's avatar
Rob Swindell committed
		inkey(K_NONE, atoi(sp + 5) * 100);
	if(!strcmp(sp,"PAUSE") || !strcmp(sp,"MORE")) {
		return(nulstr);
	}
	if(!strcmp(sp,"RESETPAUSE")) {
		return(nulstr);
	}
	if(!strcmp(sp,"NOPAUSE") || !strcmp(sp,"POFF")) {
		sys_status^=SS_PAUSEOFF;
		return(nulstr);
	}
	if(!strcmp(sp,"PON") || !strcmp(sp,"AUTOMORE")) {
		sys_status^=SS_PAUSEON;
		return(nulstr);
	}
rswindell's avatar
rswindell committed
	if(strncmp(sp, "FILL:", 5) == 0) {
		sp += 5;
		int margin = centered ? column : 1;
		while(*sp && online && column < cols - margin)
rswindell's avatar
rswindell committed
			bputs(sp, P_TRUNCATE);
		return nulstr;
	}

	if(strncmp(sp, "POS:", 4) == 0) {	// PCBoard	(nn is 1 based)
		i = atoi(sp + 4);
		if(i >= 1)	// Convert to 0-based
			i--;
		for(l = i - column; l > 0; l--)
			outchar(' ');
		return nulstr;
	}

	if(strncmp(sp, "DELAY:", 6) == 0) {	// PCBoard
		mswait(atoi(sp + 6) * 100);
		return nulstr;
	}

	if(strcmp(sp, "YESCHAR") == 0) {	// PCBoard
		safe_snprintf(str, maxlen, "%c", yes_key());
		return str;
	}

	if(strcmp(sp, "NOCHAR") == 0) {		// PCBoard
		safe_snprintf(str, maxlen, "%c", no_key());
		return str;
	}

	if(strcmp(sp, "QUITCHAR") == 0) {
		safe_snprintf(str, maxlen, "%c", quit_key());
	if(strncmp(sp, "BPS:", 4) == 0) {
		set_output_rate((enum output_rate)atoi(sp + 4));
		return nulstr;
	}

	if(strncmp(sp, "TEXT:", 5) == 0) {
		i = atoi(sp + 5);
		if(i >= 1 && i <= TOTAL_TEXT)
			return text[i - 1];
		return nulstr;
	}

	if(!strcmp(sp,"BELL") || !strcmp(sp,"BEEP"))
		return("\a");
	if(!strcmp(sp,"EVENT")) {
		if(event_time==0)
			return("<none>");
		return(timestr(event_time));
	if(!strncmp(sp,"NODE",4)) {
		i=atoi(sp+4);
		if(i && i<=cfg.sys_nodes) {
			getnodedat(i,&node,0);
		return(nulstr);
	}
	if(!strcmp(sp,"WHO")) {
		whos_online(true);
		return(nulstr);
	}
	if(!strcmp(sp,"USER") || !strcmp(sp,"ALIAS") || !strcmp(sp,"NAME"))
		return(useron.alias);
	if(!strcmp(sp,"FIRST")) {
		safe_snprintf(str,maxlen,"%s",useron.alias);
		tp=strchr(str,' ');
	if(!strcmp(sp,"USERNUM")) {
		safe_snprintf(str,maxlen,"%u",useron.number);
		return(str);
	}
	if(!strcmp(sp,"PHONE") || !strcmp(sp,"HOMEPHONE")
		|| !strcmp(sp,"DATAPHONE") || !strcmp(sp,"DATA"))
		return(useron.phone);
	if(!strcmp(sp,"ADDR1"))
		return(useron.address);
	if(!strcmp(sp,"FROM"))
		return(useron.location);
	if(!strcmp(sp,"CITY")) {
		safe_snprintf(str,maxlen,"%s",useron.location);
		char* p=strchr(str,',');
		return(nulstr);
	}
	if(!strcmp(sp,"STATE")) {
		char* p=strchr(useron.location,',');
		return(nulstr);
	}
	if(!strcmp(sp,"CPU"))
		return(useron.comp);
	if(!strcmp(sp,"HOST"))
		return(client_name);
	if(!strcmp(sp,"BDATE"))