From c9c42c87a1f7b2e39705da95e4f20fb7830646a4 Mon Sep 17 00:00:00 2001
From: rswindell <>
Date: Thu, 30 Apr 2020 03:34:48 +0000
Subject: [PATCH] Support JavaScript property name expansion using JS:<name>
 @-code where name is the name of a scalar property in either the current
 scope (by default) or the scope of the object passed to: - bbs.menu() -
 console.putmsg() - console.printfile() - console.printtail()

The 'name' cannot be an array element (e.g. myprop[0]) or a nested object
reference (e.g. myobj.myprop): just a single property name that can be
converted to a string.

Also, bbs.menu() now accepts an optional print-mode argument (default: P_NONE).
---
 src/sbbs3/atcodes.cpp    | 13 +++++--
 src/sbbs3/js_bbs.cpp     | 28 +++++++++++---
 src/sbbs3/js_console.cpp | 80 ++++++++++++++++++++++++++--------------
 src/sbbs3/prntfile.cpp   | 14 +++----
 src/sbbs3/putmsg.cpp     |  4 +-
 src/sbbs3/sbbs.h         | 12 +++---
 6 files changed, 100 insertions(+), 51 deletions(-)

diff --git a/src/sbbs3/atcodes.cpp b/src/sbbs3/atcodes.cpp
index fde47dec29..e6c658966d 100644
--- a/src/sbbs3/atcodes.cpp
+++ b/src/sbbs3/atcodes.cpp
@@ -71,7 +71,7 @@ static char* separate_thousands(const char* src, char *dest, size_t maxlen, char
 /****************************************************************************/
 /* Returns 0 if invalid @ code. Returns length of @ code if valid.          */
 /****************************************************************************/
-int sbbs_t::show_atcode(const char *instr)
+int sbbs_t::show_atcode(const char *instr, JSObject* obj)
 {
 	char	str[128],str2[128],*tp,*sp,*p;
     int     len;
@@ -144,7 +144,7 @@ int sbbs_t::show_atcode(const char *instr)
 		*p=0;
 	}
 
-	cp = atcode(sp, str2, sizeof(str2), &pmode, centered);
+	cp = atcode(sp, str2, sizeof(str2), &pmode, centered, obj);
 	if(cp==NULL)
 		return(0);
 
@@ -210,7 +210,7 @@ static const char* getpath(scfg_t* cfg, const char* path)
 	return path;
 }
 
-const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered)
+const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool centered, JSObject* obj)
 {
 	char*	tp = NULL;
 	uint	i;
@@ -1092,6 +1092,13 @@ const char* sbbs_t::atcode(char* sp, char* str, size_t maxlen, long* pmode, bool
 		return nulstr;
 	}
 
+	if(strncmp(sp, "JS:", 3) == 0) {
+		jsval val;
+		if(JS_GetProperty(js_cx, obj == NULL ? js_glob : obj, sp + 3, &val))
+			JSVALUE_TO_STRBUF(js_cx, val, str, maxlen, NULL);
+		return str;
+	}
+
 	if(!strncmp(sp,"EXEC:",5)) {
 		exec_bin(sp+5,&main_csi);
 		return(nulstr);
diff --git a/src/sbbs3/js_bbs.cpp b/src/sbbs3/js_bbs.cpp
index 6e2fc26c85..ce0c075695 100644
--- a/src/sbbs3/js_bbs.cpp
+++ b/src/sbbs3/js_bbs.cpp
@@ -1170,6 +1170,8 @@ js_menu(JSContext *cx, uintN argc, jsval *arglist)
  	sbbs_t*		sbbs;
 	jsrefcount	rc;
 	char		*menu;
+	int32		mode = P_NONE;
+	JSObject*	obj = JS_GetScopeChain(cx);
 
  	if(!js_argc(cx, argc, 1))
 		return(JS_FALSE);
@@ -1181,11 +1183,23 @@ js_menu(JSContext *cx, uintN argc, jsval *arglist)
  	if (!str)
  		return(JS_FALSE);
 
+	uintN argn = 1;
+	if(argc > argn && JSVAL_IS_NUMBER(argv[argn])) {
+		if(!JS_ValueToInt32(cx,argv[argn], &mode))
+			return JS_FALSE;
+		argn++;
+	}
+	if(argc > argn && JSVAL_IS_OBJECT(argv[argn])) {
+		if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL)
+			return JS_FALSE;
+		argn++;
+	}
+
 	JSSTRING_TO_MSTRING(cx, str, menu, NULL);
 	if(!menu)
 		return JS_FALSE;
 	rc=JS_SUSPENDREQUEST(cx);
-	bool result = sbbs->menu(menu);
+	bool result = sbbs->menu(menu, mode, obj);
 	free(menu);
 	JS_RESUMEREQUEST(cx, rc);
 
@@ -4425,8 +4439,10 @@ static jsSyncMethodSpec js_bbs_functions[] = {
 	,314
 	},
 	/* menuing */
-	{"menu",			js_menu,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("base_filename")
-	,JSDOCSTR("display a menu file from the text/menu directory")
+	{"menu",			js_menu,			1,	JSTYPE_BOOLEAN,	JSDOCSTR("base_filename [,mode=<tt>P_NONE</tt>] [,object scope]")
+	,JSDOCSTR("display a menu file from the text/menu directory.<br>"
+	"See <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>"
+	"When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.")
 	,310
 	},
 	{"menu_exists",		js_menu_exists,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("base_filename")
@@ -4453,7 +4469,7 @@ static jsSyncMethodSpec js_bbs_functions[] = {
 	/* xtrn programs/modules */
 	{"exec",			js_exec,			2,	JSTYPE_NUMBER,	JSDOCSTR("cmdline [,mode=<tt>EX_NONE</tt>] [,startup_dir]")
 	,JSDOCSTR("execute a program, optionally changing current directory to <i>startup_dir</i> "
-	"(see <tt>EX_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> bits)")
+	"(see <tt>EX_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> flags.)")
 	,310
 	},
 	{"exec_xtrn",		js_exec_xtrn,		1,	JSTYPE_BOOLEAN,	JSDOCSTR("xtrn_number_or_code")
@@ -4466,11 +4482,11 @@ static jsSyncMethodSpec js_bbs_functions[] = {
 	,310
 	},
 	{"telnet_gate",		js_telnet_gate,		1,	JSTYPE_VOID,	JSDOCSTR("address [,mode=<tt>TG_NONE</tt>]")
-	,JSDOCSTR("external Telnet gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> bits)")
+	,JSDOCSTR("external Telnet gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> flags.)")
 	,310
 	},
 	{"rlogin_gate",		js_rlogin_gate,		1,	JSTYPE_VOID,	JSDOCSTR("address [,client-user-name=<tt>user.alias</tt>, server-user-name=<tt>user.name</tt>, terminal=<tt>console.terminal</tt>] [,mode=<tt>TG_NONE</tt>]")
-	,JSDOCSTR("external RLogin gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> bits)")
+	,JSDOCSTR("external RLogin gateway (see <tt>TG_*</tt> in <tt>sbbsdefs.js</tt> for valid <i>mode</i> flags.)")
 	,316
 	},
 	/* security */
diff --git a/src/sbbs3/js_console.cpp b/src/sbbs3/js_console.cpp
index 6f5343daf6..bede632e9b 100644
--- a/src/sbbs3/js_console.cpp
+++ b/src/sbbs3/js_console.cpp
@@ -1216,6 +1216,7 @@ js_writeln(JSContext *cx, uintN argc, jsval *arglist)
 static JSBool
 js_putmsg(JSContext *cx, uintN argc, jsval *arglist)
 {
+	uintN		argn;
 	jsval *argv=JS_ARGV(cx, arglist);
 	int32		mode=0;
 	int32		columns=0;
@@ -1223,6 +1224,7 @@ js_putmsg(JSContext *cx, uintN argc, jsval *arglist)
 	sbbs_t*		sbbs;
 	char*		cstr;
 	jsrefcount	rc;
+	JSObject*	obj = JS_GetScopeChain(cx);
 
 	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
 		return(JS_FALSE);
@@ -1233,20 +1235,28 @@ js_putmsg(JSContext *cx, uintN argc, jsval *arglist)
 	if (!str)
 		return(JS_FALSE);
 
-	if(argc>1 && JSVAL_IS_NUMBER(argv[1])) {
-		if(!JS_ValueToInt32(cx,argv[1],&mode))
+	argn = 1;
+	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
+		if(!JS_ValueToInt32(cx,argv[argn],&mode))
+			return JS_FALSE;
+		argn++;
+	}
+	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
+		if(!JS_ValueToInt32(cx,argv[argn],&columns))
 			return JS_FALSE;
+		argn++;
 	}
-	if(argc>2 && JSVAL_IS_NUMBER(argv[2])) {
-		if(!JS_ValueToInt32(cx,argv[2],&columns))
+	if(argc>argn && JSVAL_IS_OBJECT(argv[argn])) {
+		if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL)
 			return JS_FALSE;
+		argn++;
 	}
 
 	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
 	if(cstr==NULL)
 		return JS_FALSE;
 	rc=JS_SUSPENDREQUEST(cx);
-	sbbs->putmsg(cstr, mode, columns);
+	sbbs->putmsg(cstr, mode, columns, obj);
 	free(cstr);
 	JS_RESUMEREQUEST(cx, rc);
     return(JS_TRUE);
@@ -1262,6 +1272,7 @@ js_printfile(JSContext *cx, uintN argc, jsval *arglist)
 	sbbs_t*		sbbs;
 	char*		cstr;
 	jsrefcount	rc;
+	JSObject*	obj = JS_GetScopeChain(cx);
 
 	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
 		return(JS_FALSE);
@@ -1270,20 +1281,28 @@ js_printfile(JSContext *cx, uintN argc, jsval *arglist)
 	if (!str)
 		return(JS_FALSE);
 
-	if(argc>1 && JSVAL_IS_NUMBER(argv[1])) {
-		if(!JS_ValueToInt32(cx,argv[1],&mode))
+	uintN argn = 1;
+	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
+		if(!JS_ValueToInt32(cx,argv[argn],&mode))
 			return JS_FALSE;
+		argn++;
 	}
-	if(argc>2 && JSVAL_IS_NUMBER(argv[2])) {
-		if(!JS_ValueToInt32(cx,argv[2],&columns))
+	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
+		if(!JS_ValueToInt32(cx,argv[argn],&columns))
 			return JS_FALSE;
+		argn++;
+	}
+	if(argc>argn && JSVAL_IS_OBJECT(argv[argn])) {
+		if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL)
+			return JS_FALSE;
+		argn++;
 	}
 
 	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
 	if(cstr==NULL)
 		return JS_FALSE;
 	rc=JS_SUSPENDREQUEST(cx);
-	bool result = sbbs->printfile(cstr,mode,columns);
+	bool result = sbbs->printfile(cstr,mode,columns, obj);
 	free(cstr);
 	JS_RESUMEREQUEST(cx, rc);
 
@@ -1304,6 +1323,7 @@ js_printtail(JSContext *cx, uintN argc, jsval *arglist)
     JSString*	js_str=NULL;
 	char*		cstr;
 	jsrefcount	rc;
+	JSObject*	obj = JS_GetScopeChain(cx);
 
 	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
 		return(JS_FALSE);
@@ -1322,8 +1342,12 @@ js_printtail(JSContext *cx, uintN argc, jsval *arglist)
 				if(!JS_ValueToInt32(cx,argv[i],&columns))
 					return JS_FALSE;
 			}
-		} else if(JSVAL_IS_STRING(argv[i]))
+		} else if(JSVAL_IS_STRING(argv[i])) {
 			js_str = JS_ValueToString(cx, argv[i]);
+		} else if(JSVAL_IS_OBJECT(argv[i])) {
+			if((obj = JSVAL_TO_OBJECT(argv[i])) == NULL)
+				return JS_FALSE;
+		}
 	}
 
 	if(js_str==NULL)
@@ -1336,7 +1360,7 @@ js_printtail(JSContext *cx, uintN argc, jsval *arglist)
 	if(cstr==NULL)
 		return JS_FALSE;
 	rc=JS_SUSPENDREQUEST(cx);
-	bool result = sbbs->printtail(cstr,lines,mode,columns);
+	bool result = sbbs->printtail(cstr,lines,mode,columns,obj);
 	free(cstr);
 	JS_RESUMEREQUEST(cx, rc);
 
@@ -1951,19 +1975,19 @@ js_term_supports(JSContext *cx, uintN argc, jsval *arglist)
 
 static jsSyncMethodSpec js_console_functions[] = {
 	{"inkey",			js_inkey,			0, JSTYPE_STRING,	JSDOCSTR("[mode=<tt>K_NONE</tt>] [,timeout=<tt>0</tt>]")
-	,JSDOCSTR("get a single key with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait), "
-		"returns an empty string if there is no input (e.g. timeout occurs), "
-		"see <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits")
+	,JSDOCSTR("get a single key with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait).<br>"
+		"Returns an empty string if there is no input (e.g. timeout occurs).<br>"
+		"See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.")
 	,311
 	},
 	{"getkey",			js_getkey,			0, JSTYPE_STRING,	JSDOCSTR("[mode=<tt>K_NONE</tt>]")
-	,JSDOCSTR("get a single key, with wait, "
-		"see <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits")
+	,JSDOCSTR("get a single key, with wait.<br>"
+		"See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.")
 	,310
 	},
 	{"getstr",			js_getstr,			0, JSTYPE_STRING,	JSDOCSTR("[string] [,maxlen=<tt>128</tt>] [,mode=<tt>K_NONE</tt>] [,history[]]")
-	,JSDOCSTR("get a text string from the user, "
-		"see <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits.<br>"
+	,JSDOCSTR("get a text string from the user.<br>"
+		"See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>"
 		"<i>history[]</i>, added in v3.17, allows a command history (string array) to be recalled using the up/down arrow keys."
 		)
 	,310
@@ -2042,10 +2066,11 @@ static jsSyncMethodSpec js_console_functions[] = {
 	,JSDOCSTR("display one or more values as raw strings followed by a single carriage-return/line-feed pair (new-line)")
 	,315
 	},
-	{"putmsg",			js_putmsg,			1, JSTYPE_VOID,		JSDOCSTR("text [,mode=<tt>P_NONE</tt>] [,orig_columns=0]")
-	,JSDOCSTR("display message text (Ctrl-A codes, @-codes, pipe codes, etc), "
-		"see <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> bits.<br>"
-		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known")
+	{"putmsg",			js_putmsg,			1, JSTYPE_VOID,		JSDOCSTR("text [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]")
+	,JSDOCSTR("display message text (Ctrl-A codes, @-codes, pipe codes, etc.).<br> "
+		"See <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>"
+		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known.<br>"
+		"When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.")
 	,310
 	},
 	{"center",			js_center,			1, JSTYPE_VOID,		JSDOCSTR("text [,width]")
@@ -2060,13 +2085,14 @@ static jsSyncMethodSpec js_console_functions[] = {
 	,JSDOCSTR("returns the printed-length (number of columns) of the specified <i>text</i>, accounting for Ctrl-A codes")
 	,310
 	},
-	{"printfile",		js_printfile,		1, JSTYPE_BOOLEAN,		JSDOCSTR("filename [,mode=<tt>P_NONE</tt>] [,orig_columns=0")
+	{"printfile",		js_printfile,		1, JSTYPE_BOOLEAN,		JSDOCSTR("filename [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]")
 	,JSDOCSTR("print a message text file with optional mode.<br>"
-		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known")
+		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known.<br>"
+		"When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.")
 	,310
 	},
-	{"printtail",		js_printtail,		2, JSTYPE_BOOLEAN,		JSDOCSTR("filename, lines [,mode=<tt>P_NONE</tt>] [,orig_columns=0]")
-	,JSDOCSTR("print last x lines of file with optional mode")
+	{"printtail",		js_printtail,		2, JSTYPE_BOOLEAN,		JSDOCSTR("filename, lines [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]")
+	,JSDOCSTR("print the last <i>n</i> lines of file with optional mode, original column width, and scope.")
 	,310
 	},
 	{"editfile",		js_editfile,		1, JSTYPE_BOOLEAN,		JSDOCSTR("filename")
diff --git a/src/sbbs3/prntfile.cpp b/src/sbbs3/prntfile.cpp
index cea34a9da9..5cba2ab3e3 100644
--- a/src/sbbs3/prntfile.cpp
+++ b/src/sbbs3/prntfile.cpp
@@ -48,7 +48,7 @@
 /* for pauses, aborts and ANSI. 'str' is the path of the file to print      */
 /* Called from functions menu and text_sec                                  */
 /****************************************************************************/
-bool sbbs_t::printfile(const char* fname, long mode, long org_cols)
+bool sbbs_t::printfile(const char* fname, long mode, long org_cols, JSObject* obj)
 {
 	char* buf;
 	char fpath[MAX_PATH+1];
@@ -119,7 +119,7 @@ bool sbbs_t::printfile(const char* fname, long mode, long org_cols)
 			buf[l]=0;
 			if((mode&P_UTF8) && !term_supports(UTF8))
 				utf8_normalize_str(buf);
-			putmsg(buf,mode,org_cols);
+			putmsg(buf,mode,org_cols, obj);
 		}
 		free(buf);
 	} else {	// Line-at-a-time mode
@@ -138,7 +138,7 @@ bool sbbs_t::printfile(const char* fname, long mode, long org_cols)
 				break;
 			if((mode&P_UTF8) && !term_supports(UTF8))
 				utf8_normalize_str(buf);
-			if(putmsg(buf, mode|P_SAVEATR, org_cols) != '\0') // early-EOF?
+			if(putmsg(buf, mode|P_SAVEATR, org_cols, obj) != '\0') // early-EOF?
 				break;
 		}
 		free(buf);
@@ -157,7 +157,7 @@ bool sbbs_t::printfile(const char* fname, long mode, long org_cols)
 	return true;
 }
 
-bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols)
+bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols, JSObject* obj)
 {
 	char*	buf;
 	char	fpath[MAX_PATH+1];
@@ -215,7 +215,7 @@ bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols)
 			}
 			p--; 
 		}
-		putmsg(p,mode,org_cols);
+		putmsg(p,mode,org_cols, obj);
 	}
 	if(mode&P_NOABORT && online==ON_REMOTE) {
 		SYNC;
@@ -228,7 +228,7 @@ bool sbbs_t::printtail(const char* fname, int lines, long mode, long org_cols)
 /****************************************************************************/
 /* Displays a menu file (e.g. from the text/menu directory)                 */
 /****************************************************************************/
-bool sbbs_t::menu(const char *code, long mode)
+bool sbbs_t::menu(const char *code, long mode, JSObject* obj)
 {
     char path[MAX_PATH+1];
 	const char *next= "msg";
@@ -261,7 +261,7 @@ bool sbbs_t::menu(const char *code, long mode)
 	mode |= P_OPENCLOSE | P_CPM_EOF;
 	if(column == 0)
 		mode |= P_NOCRLF;
-	return printfile(path, mode);
+	return printfile(path, mode, /* org_cols: */0, obj);
 }
 
 bool sbbs_t::menu_exists(const char *code, const char* ext, char* path)
diff --git a/src/sbbs3/putmsg.cpp b/src/sbbs3/putmsg.cpp
index af494fe933..067ffc8753 100644
--- a/src/sbbs3/putmsg.cpp
+++ b/src/sbbs3/putmsg.cpp
@@ -49,7 +49,7 @@
 /* the attributes prior to displaying the message are always restored.      */
 /* Stops parsing/displaying upon CTRL-Z (only in P_CPM_EOF mode).           */
 /****************************************************************************/
-char sbbs_t::putmsg(const char *buf, long mode, long org_cols)
+char sbbs_t::putmsg(const char *buf, long mode, long org_cols, JSObject* obj)
 {
 	uint 	tmpatr;
 	char 	tmp2[256],tmp3[128];
@@ -366,7 +366,7 @@ char sbbs_t::putmsg(const char *buf, long mode, long org_cols)
 					continue;
 				}
 				bool was_tos = tos;
- 				i=show_atcode((char *)str+l);	/* returns 0 if not valid @ code */
+ 				i=show_atcode((char *)str+l, obj);	/* returns 0 if not valid @ code */
 				l+=i;					/* i is length of code string */
 				if(tos && !was_tos && (sys_status&SS_ABORT) && !lines_printed)	/* Aborted at (auto) pause prompt (e.g. due to CLS)? */
 					sys_status &= ~SS_ABORT;				/* Clear the abort flag (keep displaying the msg/file) */
diff --git a/src/sbbs3/sbbs.h b/src/sbbs3/sbbs.h
index bc3f108a97..771223efaa 100644
--- a/src/sbbs3/sbbs.h
+++ b/src/sbbs3/sbbs.h
@@ -656,7 +656,7 @@ public:
 				,const char *to, const char* from, const char** editor=NULL, const char** charset=NULL);
 	char*	quotes_fname(int xedit, char* buf, size_t len);
 	char*	msg_tmp_fname(int xedit, char* fname, size_t len);
-	char	putmsg(const char *str, long mode, long org_cols = 0);
+	char	putmsg(const char *str, long mode, long org_cols = 0, JSObject* obj = NULL);
 	bool	msgabort(void);
 	bool	email(int usernumber, const char *top = NULL, const char *title = NULL
 				, long mode = WM_NONE, smb_t* resmb = NULL, smbmsg_t* remsg = NULL);
@@ -801,9 +801,9 @@ public:
 	char	handle_ctrlkey(char ch, long mode=0);
 
 	/* prntfile.cpp */
-	bool	printfile(const char* fname, long mode, long org_cols = 0);
-	bool	printtail(const char* fname, int lines, long mode, long org_cols = 0);
-	bool	menu(const char *code, long mode = 0);
+	bool	printfile(const char* fname, long mode, long org_cols = 0, JSObject* obj = NULL);
+	bool	printtail(const char* fname, int lines, long mode, long org_cols = 0, JSObject* obj = NULL);
+	bool	menu(const char *code, long mode = 0, JSObject* obj = NULL);
 	bool	menu_exists(const char *code, const char* ext=NULL, char* realpath=NULL);
 
 	int		uselect(int add, uint n, const char *title, const char *item, const uchar *ar);
@@ -814,8 +814,8 @@ public:
 	void	redrwstr(char *strin, int i, int l, long mode);
 
 	/* atcodes.cpp */
-	int		show_atcode(const char *code);
-	const char*	atcode(char* sp, char* str, size_t maxlen, long* pmode = NULL, bool centered = false);
+	int		show_atcode(const char *code, JSObject* obj = NULL);
+	const char*	atcode(char* sp, char* str, size_t maxlen, long* pmode = NULL, bool centered = false, JSObject* obj = NULL);
 
 	/* getnode.cpp */
 	int		getsmsg(int usernumber, bool clearline = false);
-- 
GitLab