atcodes.cpp 52.9 KB
Newer Older
1 2 3 4 5 6
/* 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)		*
 *																			*
7
 * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 *																			*
 * 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"
24 25
#include "utf8.h"
#include "unicode.h"
26
#include "cp437defs.h"
27
#include "ver.h"
28

29 30 31 32 33 34 35
#if defined(_WINSOCKAPI_)
	extern WSADATA WSAData;
	#define SOCKLIB_DESC WSAData.szDescription
#else
	#define	SOCKLIB_DESC NULL
#endif

36 37 38 39 40
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;
41
	while(*tail && IS_DIGIT(*tail))
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
		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;
}

57 58 59
/****************************************************************************/
/* Returns 0 if invalid @ code. Returns length of @ code if valid.          */
/****************************************************************************/
60
int sbbs_t::show_atcode(const char *instr, JSObject* obj)
61
{
62
	char	str[128],str2[128],*tp,*sp,*p;
63
    int     len;
64
	int		disp_len;
65 66 67 68 69 70
	enum {
		none,
		left,
		right,
		center
	} align = none;
71
	bool	zero_padded=false;
rswindell's avatar
rswindell committed
72
	bool	truncated = true;
rswindell's avatar
rswindell committed
73
	bool	doubled = false;
74
	bool	thousep = false;	// thousands-separated
75
	bool	uppercase = false;
76
	bool	width_specified = false;
77
	long	pmode = 0;
78
	const char *cp;
79

80 81
	if(*instr != '@')
		return 0;
82
	SAFECOPY(str,instr);
83 84 85
	tp=strchr(str+1,'@');
	if(!tp)                 /* no terminating @ */
		return(0);
86
	sp=strchr(str+1,' ');
87 88 89 90 91 92
	if(sp && sp<tp)         /* space before terminating @ */
		return(0);
	len=(tp-str)+1;
	(*tp)=0;
	sp=(str+1);

93
	if(*sp == '~' && *(sp + 1)) {	// Mouse hot-spot (hungry)
rswindell's avatar
rswindell committed
94 95 96 97 98 99 100 101 102
		sp++;
		tp = strchr(sp + 1, '~');
		if(tp == NULL)
			tp = sp;
		else {
			*tp = 0;
			tp++;
		}
		c_unescape_str(tp);
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
		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);
rswindell's avatar
rswindell committed
119 120 121 122
		bputs(sp);
		return len;
	}

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

130
	disp_len=len;
131 132 133
	if((p = strchr(sp, '|')) != NULL) {
		if(strchr(p, 'T') != NULL)
			thousep = true;
134 135
		if(strchr(p, 'U') != NULL)
			uppercase = true;
136
		if(strchr(p, 'L') != NULL)
137
			align = left;
138
		else if(strchr(p, 'R') != NULL)
139
			align = right;
140
		else if(strchr(p, 'C') != NULL)
141
			align = center;
142 143 144 145 146 147 148 149
		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)
150 151
		p = NULL;
	else if((p=strstr(sp,"-L"))!=NULL)
152
		align = left;
153
	else if((p=strstr(sp,"-R"))!=NULL)
154
		align = right;
155
	else if((p=strstr(sp,"-C"))!=NULL)
156
		align = center;
rswindell's avatar
rswindell committed
157
	else if((p=strstr(sp,"-W"))!=NULL)	/* wide */
rswindell's avatar
rswindell committed
158
		doubled=true;
159 160
	else if((p=strstr(sp,"-Z"))!=NULL)
		zero_padded=true;
161 162
	else if((p=strstr(sp,"-T"))!=NULL)
		thousep=true;
163 164
	else if((p=strstr(sp,"-U"))!=NULL)
		uppercase=true;
rswindell's avatar
rswindell committed
165
	else if((p=strstr(sp,"->"))!=NULL)	/* wrap */
rswindell's avatar
rswindell committed
166
		truncated = false;
167
	if(p!=NULL) {
168
		char* lp = p;
169
		lp++;	// skip the '|' or '-'
170
		while(*lp == '>'|| IS_ALPHA(*lp))
171 172 173
			lp++;
		if(*lp)
			width_specified = true;
174
		while(*lp && !IS_DIGIT(*lp))
175
			lp++;
176
		if(*lp && IS_DIGIT(*lp)) {
rswindell's avatar
rswindell committed
177
			disp_len=atoi(lp);
178 179
			width_specified = true;
		}
180
		*p=0;
181
	}
182

183
	cp = atcode(sp, str2, sizeof(str2), &pmode, align == center, obj);
184
	if(cp==NULL)
185 186
		return(0);

187 188 189 190
	char separated[128];
	if(thousep)
		cp = separate_thousands(cp, separated, sizeof(separated), ',');

191
	char upper[128];
192 193 194 195 196 197
	if(uppercase) {
		SAFECOPY(upper, cp);
		strupr(upper);
		cp = upper;
	}

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

201 202 203
	if(uppercase && align == none)
		align = left;

204
	if(truncated && strchr(cp, '\n') == NULL) {
rswindell's avatar
rswindell committed
205 206 207 208 209 210 211
		if(column + disp_len > cols - 1) {
			if(column >= cols - 1)
				disp_len = 0;
			else
				disp_len = (cols - 1) - column;
		}
	}
212 213 214 215 216 217
	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);
	}
218
	if(align == left)
219
		bprintf(pmode, "%-*.*s",disp_len,disp_len,cp);
220
	else if(align == right)
221
		bprintf(pmode, "%*.*s",disp_len,disp_len,cp);
222
	else if(align == center) {
rswindell's avatar
rswindell committed
223
		int vlen = strlen(cp);
224 225
		if(vlen < disp_len) {
			int left = (disp_len - vlen) / 2;
226
			bprintf(pmode, "%*s%-*s", left, "", disp_len - left, cp);
227
		} else
228
			bprintf(pmode, "%.*s", disp_len, cp);
rswindell's avatar
rswindell committed
229 230
	} else if(doubled) {
		wide(cp);
231
	} else if(zero_padded) {
232 233
		int vlen = strlen(cp);
		if(vlen < disp_len)
234
			bprintf(pmode, "%-.*s%s", (int)(disp_len - strlen(cp)), "0000000000", cp);
235
		else
236
			bprintf(pmode, "%.*s", disp_len, cp);
237
	} else
238
		bprintf(pmode, "%.*s", disp_len, cp);
239 240 241 242

	return(len);
}

rswindell's avatar
rswindell committed
243 244 245 246 247 248 249 250 251
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;
}

252
const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered, JSObject* obj)
253
{
254
	char*	tp = NULL;
255
	uint	i;
256 257
	uint	ugrp;
	uint	usub;
258 259 260 261 262 263 264
	long	l;
    stats_t stats;
    node_t  node;
	struct	tm tm;

	str[0]=0;

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
	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;
	}

rswindell's avatar
rswindell committed
281 282 283
	if(strcmp(sp, "HOT") == 0) { // Auto-mouse hot-spot attribute
		hot_attr = curatr;
		return nulstr;
rswindell's avatar
rswindell committed
284
	}
285
	if(strncmp(sp, "HOT:", 4) == 0) {	// Auto-mouse hot-spot attribute
rswindell's avatar
rswindell committed
286
		sp += 4;
287 288 289 290 291 292 293 294 295
		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)
rswindell's avatar
rswindell committed
296 297 298
			hot_attr = 0;
		else
			hot_attr = attrstr(sp);
299 300
		return nulstr;
	}
301 302 303 304
	if(strcmp(sp, "CLEAR_HOT") == 0) {
		clear_hotspots();
		return nulstr;
	}
305

306 307
	if(strncmp(sp, "U+", 2) == 0) {	// UNICODE
		enum unicode_codepoint codepoint = (enum unicode_codepoint)strtoul(sp + 2, &tp, 16);
rswindell's avatar
rswindell committed
308
		if(tp == NULL || *tp == 0)
309
			outchar(codepoint, unicode_to_cp437(codepoint));
rswindell's avatar
rswindell committed
310 311
		else if(*tp == ':')
			outchar(codepoint, tp + 1);
312 313
		else {
			char fallback = (char)strtoul(tp + 1, NULL, 16);
rswindell's avatar
rswindell committed
314
			if(*tp == ',')
315 316 317 318 319 320 321 322 323 324 325 326
				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;
	}

327 328 329 330 331
	if(strcmp(sp, "CHECKMARK") == 0) {
		outchar(UNICODE_CHECK_MARK, CP437_CHECK_MARK);
		return nulstr;
	}

rswindell's avatar
rswindell committed
332 333 334 335
	if(strcmp(sp, "ELLIPSIS") == 0) {
		outchar(UNICODE_HORIZONTAL_ELLIPSIS, "...");
		return nulstr;
	}
336
	if(strcmp(sp, "COPY") == 0) {
rswindell's avatar
rswindell committed
337 338 339 340 341 342 343
		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
344 345 346 347
	if(strcmp(sp, "REGISTERED") == 0) {
		outchar(UNICODE_REGISTERED_SIGN, "(R)");
		return nulstr;
	}
rswindell's avatar
rswindell committed
348 349 350 351 352
	if(strcmp(sp, "TRADEMARK") == 0) {
		outchar(UNICODE_TRADE_MARK_SIGN, "(TM)");
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_C") == 0) {
rswindell's avatar
rswindell committed
353
		outchar(UNICODE_DEGREE_CELSIUS, "\xF8""C");
rswindell's avatar
rswindell committed
354 355 356
		return nulstr;
	}
	if(strcmp(sp, "DEGREE_F") == 0) {
rswindell's avatar
rswindell committed
357
		outchar(UNICODE_DEGREE_FAHRENHEIT, "\xF8""F");
rswindell's avatar
rswindell committed
358 359 360
		return nulstr;
	}

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

366
	if(!strcmp(sp,"VER"))
367
		return(VERSION);
368

369
	if(!strcmp(sp,"REV")) {
370
		safe_snprintf(str,maxlen,"%c",REVISION);
371 372
		return(str);
	}
373

374
	if(!strcmp(sp,"FULL_VER")) {
375
		safe_snprintf(str,maxlen,"%s%c%s",VERSION,REVISION,beta_version);
376
		truncsp(str);
377
#if defined(_DEBUG)
378
		strcat(str," Debug");
379
#endif
380
		return(str);
381 382
	}

383
	if(!strcmp(sp,"VER_NOTICE"))
384
		return(VERSION_NOTICE);
385

386 387
	if(!strcmp(sp,"OS_VER"))
		return(os_version(str));
388 389

#ifdef JAVASCRIPT
390 391
	if(!strcmp(sp,"JS_VER"))
		return((char *)JS_GetImplementationVersion());
392 393
#endif

394 395
	if(!strcmp(sp,"PLATFORM"))
		return(PLATFORM_DESC);
396

397 398
	if(!strcmp(sp,"COPYRIGHT"))
		return(COPYRIGHT_NOTICE);
399

400
	if(!strcmp(sp,"COMPILER")) {
401 402 403
		char compiler[32];
		DESCRIBE_COMPILER(compiler);
		strncpy(str, compiler, maxlen);
404
		return(str);
405 406
	}

407 408 409 410 411 412
	if(strcmp(sp, "GIT_HASH") == 0)
		return git_hash;

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

413 414 415 416 417 418
	if(strcmp(sp, "BUILD_DATE") == 0)
		return __DATE__;

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

419
	if(!strcmp(sp,"UPTIME")) {
420
		extern volatile time_t uptime;
421 422 423 424
		time_t up=0;
		now = time(NULL);
		if (uptime != 0 && now >= uptime)
			up = now-uptime;
425 426
		char   days[64]="";
		if((up/(24*60*60))>=2) {
427
	        sprintf(days,"%lu days ",(ulong)(up/(24L*60L*60L)));
428 429
			up%=(24*60*60);
		}
430
		safe_snprintf(str,maxlen,"%s%lu:%02lu"
431
	        ,days
432 433
			,(ulong)(up/(60L*60L))
			,(ulong)((up/60L)%60L)
434
			);
435
		return(str);
436 437
	}

438
	if(!strcmp(sp,"SERVED")) {
439
		extern volatile ulong served;
440
		safe_snprintf(str,maxlen,"%lu",served);
441 442 443
		return(str);
	}

444
	if(!strcmp(sp,"SOCKET_LIB"))
445
		return(socklib_version(str,SOCKLIB_DESC));
446

447
	if(!strcmp(sp,"MSG_LIB")) {
448
		safe_snprintf(str,maxlen,"SMBLIB %s",smb_lib_ver());
449 450
		return(str);
	}
451

452 453
	if(!strcmp(sp,"BBS") || !strcmp(sp,"BOARDNAME"))
		return(cfg.sys_name);
454

455
	if(!strcmp(sp,"BAUD") || !strcmp(sp,"BPS")) {
456
		safe_snprintf(str,maxlen,"%lu",cur_output_rate ? cur_output_rate : cur_rate);
457 458
		return(str);
	}
459

rswindell's avatar
rswindell committed
460 461 462 463 464 465 466 467
	if(!strcmp(sp,"COLS")) {
		safe_snprintf(str,maxlen,"%lu",cols);
		return(str);
	}
	if(!strcmp(sp,"ROWS")) {
		safe_snprintf(str,maxlen,"%lu",rows);
		return(str);
	}
468 469 470 471 472
	if(strcmp(sp,"TERM") == 0)
		return term_type();

	if(strcmp(sp,"CHARSET") == 0)
		return term_charset();
rswindell's avatar
rswindell committed
473

474 475
	if(!strcmp(sp,"CONN"))
		return(connection);
476

477 478
	if(!strcmp(sp,"SYSOP"))
		return(cfg.sys_op);
479

480 481 482
	if(strcmp(sp, "SYSAVAIL") == 0)
		return text[sysop_available(&cfg) ? LiSysopAvailable : LiSysopNotAvailable];

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

486 487
	if(!strcmp(sp,"LOCATION"))
		return(cfg.sys_location);
488

489
	if(strcmp(sp,"NODE") == 0 || strcmp(sp,"NN") == 0) {
490
		safe_snprintf(str,maxlen,"%u",cfg.node_num);
491 492
		return(str);
	}
493
	if(strcmp(sp, "TNODES") == 0 || strcmp(sp, "TNODE") == 0 || strcmp(sp, "TN") == 0) {
494
		safe_snprintf(str,maxlen,"%u",cfg.sys_nodes);
495 496
		return(str);
	}
497 498 499 500 501 502 503 504
	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;
	}
505

rswindell's avatar
rswindell committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521
	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];
	}

522 523 524 525 526 527
	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];

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

531 532
	if(!strcmp(sp,"INETADDR"))
		return(cfg.sys_inetaddr);
533

534
	if(!strcmp(sp,"HOSTNAME"))
535
		return server_host_name();
536

537
	if(!strcmp(sp,"FIDOADDR")) {
538
		if(cfg.total_faddrs)
539
			return(smb_faddrtoa(&cfg.faddr[0],str));
540
		return(nulstr);
541 542
	}

543
	if(!strcmp(sp,"EMAILADDR"))
544
		return(usermailaddr(&cfg, str
545
			,(cfg.inetmail_misc&NMAIL_ALIAS) || (useron.rest&FLAG('O')) ? useron.alias : useron.name));
546

547 548 549 550 551 552 553 554 555
	if(strcmp(sp, "NETMAIL") == 0)
		return useron.netmail;

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

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

Rob Swindell's avatar
Rob Swindell committed
556 557 558 559 560 561 562 563
	if(strcmp(sp, "SEX") == 0) {
		safe_snprintf(str, maxlen, "%c", useron.sex);
		return str;
	}
	if(strcmp(sp, "GENDERS") == 0) {
		return cfg.new_genders;
	}

564 565
	if(!strcmp(sp,"QWKID"))
		return(cfg.sys_id);
566

567
	if(!strcmp(sp,"TIME") || !strcmp(sp,"SYSTIME")) {
568
		now=time(NULL);
569
		memset(&tm,0,sizeof(tm));
570
		localtime_r(&now,&tm);
571
		if(cfg.sys_misc&SM_MILITARY)
572 573
			safe_snprintf(str,maxlen,"%02d:%02d:%02d"
		        	,tm.tm_hour,tm.tm_min,tm.tm_sec);
574
		else
575
			safe_snprintf(str,maxlen,"%02d:%02d %s"
576 577 578
				,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");
579
		return(str);
580 581
	}

rswindell's avatar
rswindell committed
582 583 584
	if(!strcmp(sp,"TIMEZONE"))
		return(smb_zonestr(sys_timezone(&cfg),str));

585
	if(!strcmp(sp,"DATE") || !strcmp(sp,"SYSDATE")) {
586
		return(unixtodstr(&cfg,time32(NULL),str));
587
	}
588

589 590 591 592 593 594 595 596 597 598
	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
599 600 601
	if(!strcmp(sp,"DATETIME"))
		return(timestr(time(NULL)));

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

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

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

619
	if(!strcmp(sp,"TMSG")) {
620 621
		l=0;
		for(i=0;i<cfg.total_subs;i++)
622
			l+=getposts(&cfg,i); 		/* l=total posts */
623
		safe_snprintf(str,maxlen,"%lu",l);
624 625
		return(str);
	}
626

627
	if(!strcmp(sp,"TUSER")) {
628
		safe_snprintf(str,maxlen,"%u",total_users(&cfg));
629 630
		return(str);
	}
631

632
	if(!strcmp(sp,"TFILE")) {
633 634
		l=0;
		for(i=0;i<cfg.total_dirs;i++)
635
			l+=getfiles(&cfg,i);
636
		safe_snprintf(str,maxlen,"%lu",l);
637 638
		return(str);
	}
639

640
	if(strncmp(sp, "FILES:", 6) == 0) {	// Number of files in specified directory
rswindell's avatar
rswindell committed
641
		const char* path = getpath(&cfg, sp + 6);
642
		safe_snprintf(str, maxlen, "%lu", getfilecount(path));
rswindell's avatar
rswindell committed
643
		return str;
644 645 646 647 648 649 650
	}

	if(strcmp(sp, "FILES") == 0) {	// Number of files in current directory
		safe_snprintf(str, maxlen, "%lu", (ulong)getfiles(&cfg, usrdir[curlib][curdir[curlib]]));
		return str;
	}

rswindell's avatar
rswindell committed
651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710
	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;
	}

711
	if(!strcmp(sp,"TCALLS") || !strcmp(sp,"NUMCALLS")) {
712
		getstats(&cfg,0,&stats);
713
		safe_snprintf(str,maxlen,"%lu", (ulong)stats.logons);
714 715
		return(str);
	}
716

717
	if(!strcmp(sp,"PREVON") || !strcmp(sp,"LASTCALLERNODE")
718
		|| !strcmp(sp,"LASTCALLERSYSTEM"))
719
		return(lastuseron);
720

rswindell's avatar
rswindell committed
721
	if(!strcmp(sp,"CLS") || !strcmp(sp,"CLEAR")) {
722
		CLS;
723 724
		return(nulstr);
	}
725

726 727 728 729 730
	if(strcmp(sp, "GETKEY") == 0) {
		getkey();
		return(nulstr);
	}

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

738
	if(strncmp(sp, "WAIT:", 5) == 0) {
Rob Swindell's avatar
Rob Swindell committed
739
		inkey(K_NONE, atoi(sp + 5) * 100);
740 741 742
		return(nulstr);
	}

743
	if(!strcmp(sp,"PAUSE") || !strcmp(sp,"MORE")) {
744
		pause();
745 746
		return(nulstr);
	}
747

748
	if(!strcmp(sp,"RESETPAUSE")) {
749
		lncntr=0;
750 751
		return(nulstr);
	}
752

753
	if(!strcmp(sp,"NOPAUSE") || !strcmp(sp,"POFF")) {
754
		sys_status^=SS_PAUSEOFF;
755 756
		return(nulstr);
	}
757

758
	if(!strcmp(sp,"PON") || !strcmp(sp,"AUTOMORE")) {
759
		sys_status^=SS_PAUSEON;
760 761
		return(nulstr);
	}
762

rswindell's avatar
rswindell committed
763 764
	if(strncmp(sp, "FILL:", 5) == 0) {
		sp += 5;
765 766
		long margin = centered ? column : 1;
		if(margin < 1) margin = 1;
767
		c_unescape_str(sp);
768
		while(*sp && online && column < cols - margin)
rswindell's avatar
rswindell committed
769 770 771 772
			bputs(sp, P_TRUNCATE);
		return nulstr;
	}

773 774 775 776 777
	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--)
778 779 780 781 782 783 784 785 786 787
			outchar(' ');
		return nulstr;
	}

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

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

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

	if(strcmp(sp, "QUITCHAR") == 0) {
798
		safe_snprintf(str, maxlen, "%c", quit_key());
799 800 801
		return str;
	}

802 803 804 805 806
	if(strncmp(sp, "BPS:", 4) == 0) {
		set_output_rate((enum output_rate)atoi(sp + 4));
		return nulstr;
	}

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

814 815 816 817
	/* NOSTOP */

	/* STOP */

818 819
	if(!strcmp(sp,"BELL") || !strcmp(sp,"BEEP"))
		return("\a");
820

821 822 823
	if(!strcmp(sp,"EVENT")) {
		if(event_time==0)
			return("<none>");
824
		return(timestr(event_time));
825
	}
826 827 828

	/* LASTCALL */

829
	if(!strncmp(sp,"NODE",4)) {
830 831 832
		i=atoi(sp+4);
		if(i && i<=cfg.sys_nodes) {
			getnodedat(i,&node,0);
833 834
			printnodedat(i,&node);
		}
835 836
		return(nulstr);
	}
837

838
	if(!strcmp(sp,"WHO")) {
839
		whos_online(true);
840 841
		return(nulstr);
	}
842 843 844

	/* User Codes */

845 846
	if(!strcmp(sp,"USER") || !strcmp(sp,"ALIAS") || !strcmp(sp,"NAME"))
		return(useron.alias);
847

848
	if(!strcmp(sp,"FIRST")) {
849
		safe_snprintf(str,maxlen,"%s",useron.alias);
850
		tp=strchr(str,' ');
851
		if(tp) *tp=0;
852
		return(str);
853
	}
854

855
	if(!strcmp(sp,"USERNUM")) {
856
		safe_snprintf(str,maxlen,"%u",useron.number);
857 858
		return(str);
	}
859

860
	if(!strcmp(sp,"PHONE") || !strcmp(sp,"HOMEPHONE")
861
		|| !strcmp(sp,"DATAPHONE") || !strcmp(sp,"DATA"))
862
		return(useron.phone);
863

864 865
	if(!strcmp(sp,"ADDR1"))
		return(useron.address);
866

867 868
	if(!strcmp(sp,"FROM"))
		return(useron.location);
869

870
	if(!strcmp(sp,"CITY")) {
871
		safe_snprintf(str,maxlen,"%s",useron.location);