diff --git a/ctrl/text.dat b/ctrl/text.dat
index e5eb7febe50d7688297624beea92d26022867f31..c81126f9776462f04c8bd1b964be6d80cafb3518 100644
--- a/ctrl/text.dat
+++ b/ctrl/text.dat
@@ -851,31 +851,31 @@
 "Directory %u"                                          693 NoAccessDir
 "\1n\1hNode Status\r\n\1c"\                                694 NodeLstHdr
 	"\xc4\xc4\xc4\xc4 \xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\r\n"
-""                                                      695 NodeActionMain
-""                                                      696 NodeActionReadMsgs
-""                                                      697 NodeActionReadMail
-""                                                      698 NodeActionSendMail
-""                                                      699 NodeActionReadTxt
-""                                                      700 NodeActionReadSentMail
-""                                                      701 NodeActionPostMsg
+""                                                      695 NodeActionMainMenu
+""                                                      696 NodeActionReadingMsgs
+""                                                      697 NodeActionReadingMail
+""                                                      698 NodeActionSendingMail
+""                                                      699 NodeActionReadingTextFiles
+""                                                      700 NodeActionReadingSentMail
+""                                                      701 NodeActionPostingMsg
 ""                                                      702 NodeActionAutoMsg
-""                                                      703 NodeActionXtrn
-""                                                      704 NodeActionDefaults
-""                                                      705 NodeActionXfer
-""                                                      706 NodeActionDLing
-""                                                      707 NodeActionULing
-""                                                      708 NodeActionBiXfer
-""                                                      709 NodeActionListFiles
+""                                                      703 NodeActionXtrnMenu
+""                                                      704 NodeActionSettings
+""                                                      705 NodeActionFileMenu
+""                                                      706 NodeActionDownloadingFile
+""                                                      707 NodeActionUploadingFile
+""                                                      708 NodeActionBiXferFile
+""                                                      709 NodeActionListingFiles
 ""                                                      710 NodeActionLoggingOn
 ""                                                      711 NodeActionLocalChat
-""				       	                712 NodeActionMultiChat
+""				       	                                712 NodeActionMultiChat
 ""                                                      713 NodeActionGuruChat
-""                                                      714 NodeActionChatSec
-""                                                      715 NodeActionSysopAct
-""                                                      716 NodeActionQWK
+""                                                      714 NodeActionChatMenu
+""                                                      715 NodeActionSysop
+""                                                      716 NodeActionQWKmenu
 ""                                                      717 NodeActionPrivateChat
-""                                                      718 NodeActionPaging
-""                                                      719 NodeActionRetrieving
+""                                                      718 NodeActionPagingNode
+""                                                      719 NodeActionRetrievingFile
 ""                                                      720 NodeActionCustom
 "View signature"                                        721 ViewSignatureQ
 "Delete signature"                                      722 DeleteSignatureQ
@@ -1082,3 +1082,30 @@
 "Nov"                                                      895 Nov
 "Dec"                                                      896 Dec
 "\7@ALIAS@ paged you to chat from node @NODE@\7"           897 SysopPageNotification
+"at main menu"                                             898 NodeActivityMainMenu
+"reading messages"                                         899 NodeActivityReadingMsgs
+"reading mail"                                             900 NodeActivityReadingMail
+"sending mail"                                             901 NodeActivitySendingMail
+"reading text files"                                       902 NodeActivityReadingTextFiles
+"reading sent mail"                                        903 NodeActivityReadingSentMail
+"posting message"                                          904 NodeActivityPostingMsg
+"posting auto-message"                                     905 NodeActivityAutoMsg
+"at external program menu"                                 906 NodeActivityXtrnMenu
+"running"                                                  907 NodeActivityRunningXtrn
+"changing settings"                                        908 NodeActivitySettings
+"at file menu"                                             909 NodeActivityFileMenu
+"downloading files"                                        910 NodeActivityDownloadingFile
+"uploading files"                                          911 NodeActivityUploadingFile
+"transferring files bidirectional"                         912 NodeActivityBiXferFile
+"listing files"                                            913 NodeActivityListingFiles
+"logging on"                                               914 NodeActivityLoggingOn
+"chatting with %s"                                         915 NodeActivityLocalChat
+"in multinode chat channel %d"                             916 NodeActivityChatChannel
+"in multinode global chat channel"                         917 NodeActivityGlobalChat
+"in chat section"                                          918 NodeActivityChatMenu
+"performing sysop activity"                                919 NodeActivitySysop
+"transferring QWK packet"                                  920 NodeActivityQWKmenu
+"chatting with node %u"                                    921 NodeActivityNodeChat
+"paging node %u for private chat"                          922 NodeActivityPagingNode
+"retrieving file from device #%d"                          923 NodeActivityRetrievingFile
+"performing custom activity"                               924 NodeActivityCustom
diff --git a/exec/load/presence_lib.js b/exec/load/presence_lib.js
index 1217a58ce37a6a3db937fa3eba423bac9aacd246..d0dde2904c0f0b4d65a99cc3f8e32feee4597769 100644
--- a/exec/load/presence_lib.js
+++ b/exec/load/presence_lib.js
@@ -133,17 +133,6 @@ function user_age_and_gender(user, options)
 	return output;
 }
 
-function extended_status(num)
-{
-	var f = new File(system.ctrl_dir + "node.exb");
-	if(!f.open("rb"))
-		return "!error " + f.error + " opening " + f.name;
-	f.position = num * 128;
-	var str = f.read(128);
-	f.close();
-	return truncsp(str);
-}
-
 // Returns a string describing the node status, suitable for printing on a single line
 //
 // num is zero-based
@@ -169,14 +158,10 @@ function node_status(node, is_sysop, options, num)
 	switch(node_status) {
 		case NODE_QUIET:
 			if(!is_sysop)
-				return format(NodeStatus[NODE_WFC],node.aux);
+				return node.vstatus || format(NodeStatus[NODE_WFC],node.aux);
 			/* Fall-through */
 		case NODE_INUSE:
 		{
-			if(misc&NODE_EXT) {
-				output += extended_status(num);
-				break;
-			}
 			var user = new User(node.useron);
 
 			if (!options.exclude_username) {
@@ -191,22 +176,26 @@ function node_status(node, is_sysop, options, num)
 				output += options.status_prefix;
 			output += user_age_and_gender(user, options);
 			output += " ";
-			switch(node.action) {
-				case NODE_PCHT:
-					if(node.aux == 0)
-						output += NodeAction[NODE_LCHT];
-					else
+			if(node.activity)
+				output += node.activity;
+			else {
+				switch(node.action) {
+					case NODE_PCHT:
+						if(node.aux == 0)
+							output += NodeAction[NODE_LCHT];
+						else
+							output += format(NodeAction[node.action], node.aux);
+						break;
+					case NODE_XTRN:
+						if(node.aux)
+							output += "running " + xtrn_name(node.aux);
+						else
+							output += NodeAction[node.action];
+						break;
+					default:
 						output += format(NodeAction[node.action], node.aux);
-					break;
-				case NODE_XTRN:
-					if(node.aux)
-						output += "running " + xtrn_name(node.aux);
-					else
-						output += NodeAction[node.action];
-					break;
-				default:
-					output += format(NodeAction[node.action], node.aux);
-					break;
+						break;
+				}
 			}
 			if (!options.exclude_connection) {
 				if(options.connection_prefix)
@@ -217,11 +206,11 @@ function node_status(node, is_sysop, options, num)
 		}
 		case NODE_LOGON:
 		case NODE_NEWUSER:
-			output += format(NodeStatus[node_status], node.aux);
+			output += node.vstatus || format(NodeStatus[node_status], node.aux);
 			output += node_connection_desc(node);
 			break;
 		case NODE_LOGOUT:
-			output += NodeStatus[node_status];
+			output += node.vstatus || NodeStatus[node_status];
 
 			if(options.username_prefix)
 				output += options.username_prefix;
@@ -231,7 +220,7 @@ function node_status(node, is_sysop, options, num)
 				output += system.username(node.useron);
 			break;
 		default:
-			output += format(NodeStatus[node_status], node.aux);
+			output += node.vstatus || format(NodeStatus[node_status], node.aux);
 			break;
 	}
 
diff --git a/exec/load/text.js b/exec/load/text.js
index 52051620fa630c0e584906b1c8179b09c8eaf767..6ad0d720e4add2e27bebc4aee6d5586238924621 100644
--- a/exec/load/text.js
+++ b/exec/load/text.js
@@ -701,31 +701,31 @@ var NoAccessSub=691;
 var NoAccessLib=692;
 var NoAccessDir=693;
 var NodeLstHdr=694;
-var NodeActionMain=695;
-var NodeActionReadMsgs=696;
-var NodeActionReadMail=697;
-var NodeActionSendMail=698;
-var NodeActionReadTxt=699;
-var NodeActionReadSentMail=700;
-var NodeActionPostMsg=701;
+var NodeActionMainMenu=695;
+var NodeActionReadingMsgs=696;
+var NodeActionReadingMail=697;
+var NodeActionSendingMail=698;
+var NodeActionReadingTextFiles=699;
+var NodeActionReadingSentMail=700;
+var NodeActionPostingMsg=701;
 var NodeActionAutoMsg=702;
-var NodeActionXtrn=703;
-var NodeActionDefaults=704;
-var NodeActionXfer=705;
-var NodeActionDLing=706;
-var NodeActionULing=707;
-var NodeActionBiXfer=708;
-var NodeActionListFiles=709;
+var NodeActionXtrnMenu=703;
+var NodeActionSettings=704;
+var NodeActionFileMenu=705;
+var NodeActionDownloadingFile=706;
+var NodeActionUploadingFile=707;
+var NodeActionBiXferFile=708;
+var NodeActionListingFiles=709;
 var NodeActionLoggingOn=710;
 var NodeActionLocalChat=711;
 var NodeActionMultiChat=712;
 var NodeActionGuruChat=713;
-var NodeActionChatSec=714;
-var NodeActionSysopAct=715;
-var NodeActionQWK=716;
+var NodeActionChatMenu=714;
+var NodeActionSysop=715;
+var NodeActionQWKmenu=716;
 var NodeActionPrivateChat=717;
-var NodeActionPaging=718;
-var NodeActionRetrieving=719;
+var NodeActionPagingNode=718;
+var NodeActionRetrievingFile=719;
 var NodeActionCustom=720;
 var ViewSignatureQ=721;
 var DeleteSignatureQ=722;
@@ -904,7 +904,34 @@ var Oct=894;
 var Nov=895;
 var Dec=896;
 var SysopPageNotification=897;
+var NodeActivityMainMenu=898;
+var NodeActivityReadingMsgs=899;
+var NodeActivityReadingMail=900;
+var NodeActivitySendingMail=901;
+var NodeActivityReadingTextFiles=902;
+var NodeActivityReadingSentMail=903;
+var NodeActivityPostingMsg=904;
+var NodeActivityAutoMsg=905;
+var NodeActivityXtrnMenu=906;
+var NodeActivityRunningXtrn=907;
+var NodeActivitySettings=908;
+var NodeActivityFileMenu=909;
+var NodeActivityDownloadingFile=910;
+var NodeActivityUploadingFile=911;
+var NodeActivityBiXferFile=912;
+var NodeActivityListingFiles=913;
+var NodeActivityLoggingOn=914;
+var NodeActivityLocalChat=915;
+var NodeActivityChatChannel=916;
+var NodeActivityGlobalChat=917;
+var NodeActivityChatMenu=918;
+var NodeActivitySysop=919;
+var NodeActivityQWKmenu=920;
+var NodeActivityNodeChat=921;
+var NodeActivityPagingNode=922;
+var NodeActivityRetrievingFile=923;
+var NodeActivityCustom=924;
 
-var TOTAL_TEXT=898;
+var TOTAL_TEXT=925;
 
 this;
diff --git a/src/sbbs3/ctrl/MainFormUnit.cpp b/src/sbbs3/ctrl/MainFormUnit.cpp
index db5dc55dd4126548fd849a82584603d50cc7d617..f4a9241bc135b0892e2150d4cce20f4534176896 100644
--- a/src/sbbs3/ctrl/MainFormUnit.cpp
+++ b/src/sbbs3/ctrl/MainFormUnit.cpp
@@ -1905,7 +1905,7 @@ void __fastcall TMainForm::StartupTimerTick(TObject *Sender)
 	SAFECOPY(error,UNKNOWN_LOAD_ERROR);
 
    	StatusBar->Panels->Items[STATUSBAR_LAST_PANEL]->Text="Loading configuration...";
-	if(!load_cfg(&cfg, /* text: */NULL, /* prep: */TRUE, /* node: */FALSE, error, sizeof(error))) {
+	if(!load_cfg(&cfg, text, /* prep: */TRUE, /* node: */FALSE, error, sizeof(error))) {
     	Application->MessageBox(error,"ERROR Loading Configuration"
 	        ,MB_OK|MB_ICONEXCLAMATION);
         Application->Terminate();
@@ -3060,7 +3060,7 @@ void __fastcall TMainForm::reload_config(void)
 	char error[256];
 	SAFECOPY(error,UNKNOWN_LOAD_ERROR);
    	StatusBar->Panels->Items[STATUSBAR_LAST_PANEL]->Text="Reloading configuration...";
-	if(!load_cfg(&cfg, /* text: */NULL, /* prep: */TRUE, /* node: */FALSE, error, sizeof(error))) {
+	if(!load_cfg(&cfg, text, /* prep: */TRUE, /* node: */FALSE, error, sizeof(error))) {
     	Application->MessageBox(error,"ERROR Re-loading Configuration"
 	        ,MB_OK|MB_ICONEXCLAMATION);
         Application->Terminate();
diff --git a/src/sbbs3/ctrl/MainFormUnit.h b/src/sbbs3/ctrl/MainFormUnit.h
index aa8d97700a9777a4d8380d106ef0d3d137dfd4d1..922d63c26d4b4220581ae61ab3c4f4c3a47a38ca 100644
--- a/src/sbbs3/ctrl/MainFormUnit.h
+++ b/src/sbbs3/ctrl/MainFormUnit.h
@@ -1,7 +1,5 @@
 /* Synchronet Control Panel (GUI Borland C++ Builder Project for Win32) */
 
-/* $Id: MainFormUnit.h,v 1.94 2020/04/17 20:38:56 rswindell Exp $ */
-
 /****************************************************************************
  * @format.tab-size 4		(Plain Text/Source Code File Header)			*
  * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
@@ -15,21 +13,9 @@
  * 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.	*
  ****************************************************************************/
 
@@ -53,6 +39,7 @@
 #include "websrvr.h"	// web_startup_t
 #include "mailsrvr.h"	// mail_startup_t
 #include "services.h"   // services_startup_t
+#include "text.h"		// TOTAL_TEXT
 #include <ImgList.hpp>
 #include <Buttons.hpp>
 #include <Graphics.hpp>
@@ -471,6 +458,7 @@ public:		// User declarations
     bool            UndockableForms;
     bool            UseFileAssociations;
     scfg_t		    cfg;
+	char*			text[TOTAL_TEXT];
     char		    ini_file[MAX_PATH+1];
     bbs_startup_t 	bbs_startup;
     ftp_startup_t	ftp_startup;
diff --git a/src/sbbs3/getnode.cpp b/src/sbbs3/getnode.cpp
index 3e4b1f89ea65dab7fb1de384f19cd53cea317273..6dfc94d94e0adb2b8987a6b819d08a77b9ca3787 100644
--- a/src/sbbs3/getnode.cpp
+++ b/src/sbbs3/getnode.cpp
@@ -284,8 +284,7 @@ bool sbbs_t::getnodeext(uint number, char *ext)
 		return false;
 	}
 
-	SAFEPRINTF(str,"%snode.exb",cfg.ctrl_dir);
-	if((node_ext=nopen(str,O_RDONLY|O_DENYNONE))==-1) {
+	if((node_ext=opennodeext(&cfg))==-1) {
 		memset(ext,0,128);
 		errormsg(WHERE,ERR_OPEN,str,O_RDONLY|O_DENYNONE);
 		return false;
@@ -480,11 +479,6 @@ void sbbs_t::printnodedat(uint number, node_t* node)
 				break;
 			}
 		case NODE_INUSE:
-			if(node->misc&NODE_EXT) {
-				getnodeext(number,tmp);
-				bputs(tmp);
-				break;
-			}
 			attr(cfg.color[clr_nodeuser]);
 			if(node->misc&NODE_ANON && !SYSOP)
 				bputs(text[UNKNOWN_USER]);
@@ -492,113 +486,7 @@ void sbbs_t::printnodedat(uint number, node_t* node)
 				bputs(username(&cfg,node->useron,tmp));
 			attr(cfg.color[clr_nodestatus]);
 			bputs(" ");
-			switch(node->action) {
-				case NODE_MAIN:
-					bputs("at main menu");
-					break;
-				case NODE_RMSG:
-					bputs("reading messages");
-					break;
-				case NODE_RMAL:
-					bputs("reading mail");
-					break;
-				case NODE_RSML:
-					bputs("reading sent mail");
-					break;
-				case NODE_RTXT:
-					bputs("reading text files");
-					break;
-				case NODE_PMSG:
-					bputs("posting message");
-					break;
-				case NODE_SMAL:
-					bputs("sending mail");
-					break;
-				case NODE_AMSG:
-					bputs("posting auto-message");
-					break;
-				case NODE_XTRN:
-					if(node->aux<1 || node->aux>cfg.total_xtrns)
-						bputs("at external program menu");
-					else {
-						bputs("running ");
-						i=node->aux-1;
-						if(SYSOP || chk_ar(cfg.xtrn[i]->ar,&useron,&client))
-							bputs(cfg.xtrn[node->aux-1]->name);
-						else
-							bputs("external program");
-					}
-					break;
-				case NODE_DFLT:
-					bputs("changing defaults");
-					break;
-				case NODE_XFER:
-					bputs("at transfer menu");
-					break;
-				case NODE_RFSD:
-					bprintf("retrieving from device #%d",node->aux);
-					break;
-				case NODE_DLNG:
-					bprintf("downloading");
-					break;
-				case NODE_ULNG:
-					bputs("uploading");
-					break;
-				case NODE_BXFR:
-					bputs("transferring bidirectional");
-					break;
-				case NODE_LFIL:
-					bputs("listing files");
-					break;
-				case NODE_LOGN:
-					bputs("logging on");
-					break;
-				case NODE_LCHT:
-					bprintf("in local chat with %s",cfg.sys_op);
-					break;
-				case NODE_MCHT:
-					if(node->aux) {
-						bprintf("in multinode chat channel %d",node->aux&0xff);
-						if(node->aux&0x1f00) { /* password */
-							outchar('*');
-							if(SYSOP)
-								bprintf(" %s",unpackchatpass(tmp,node));
-						}
-					}
-					else
-						bputs("in multinode global chat channel");
-					break;
-				case NODE_PAGE:
-					bprintf("paging node %u for private chat",node->aux);
-					break;
-				case NODE_PCHT:
-					if(node->aux)
-						bprintf("in private chat with node %u",node->aux);
-					else
-						bprintf("in local chat with %s",cfg.sys_op);
-					break;
-				case NODE_GCHT:
-					i=node->aux;
-					if(i>=cfg.total_gurus)
-						i=0;
-					bprintf("chatting with %s",cfg.guru[i]->name);
-					break;
-				case NODE_CHAT:
-					bputs("in chat section");
-					break;
-				case NODE_TQWK:
-					bputs("transferring QWK packet");
-					break;
-				case NODE_SYSP:
-					bputs("performing sysop activities");
-					break;
-				case NODE_CUSTOM:
-					bputs("performing custom action");
-					break;
-				default:
-					bputs(ultoa(node->action,tmp,10));
-					break;
-			}
+			bputs(node_activity(&cfg, node, tmp, sizeof tmp, number));
 			bputs(node_connection_desc(this, node->connection, tmp));
 			if(node->action==NODE_DLNG) {
 				if(cfg.sys_misc&SM_MILITARY) {
diff --git a/src/sbbs3/js_system.c b/src/sbbs3/js_system.c
index 8e2cca81f676052e3370b4459bc4004b7c07b953..17a2a956e2026fb445e9e4542ca53ad8ebd6e223 100644
--- a/src/sbbs3/js_system.c
+++ b/src/sbbs3/js_system.c
@@ -1529,6 +1529,7 @@ js_filter_ip(JSContext *cx, uintN argc, jsval *arglist)
 static JSBool
 js_get_node(JSContext *cx, uintN argc, jsval *arglist)
 {
+	char		str[128];
 	JSObject*	obj=JS_THIS_OBJECT(cx, arglist);
 	JSObject*	nodeobj;
 	jsval*		argv=JS_ARGV(cx, arglist);
@@ -1565,8 +1566,10 @@ js_get_node(JSContext *cx, uintN argc, jsval *arglist)
 		return JS_TRUE;
 	}
 	JS_DefineProperty(cx, nodeobj, "status", INT_TO_JSVAL((int)node.status), NULL, NULL, JSPROP_ENUMERATE);
+	JS_DefineProperty(cx, nodeobj, "vstatus", STRING_TO_JSVAL(JS_NewStringCopyZ(cx, node_vstatus(sys->cfg, &node, str, sizeof str))), NULL, NULL, JSPROP_ENUMERATE);
 	JS_DefineProperty(cx, nodeobj, "errors", INT_TO_JSVAL((int)node.errors), NULL, NULL, JSPROP_ENUMERATE);
 	JS_DefineProperty(cx, nodeobj, "action", INT_TO_JSVAL((int)node.action), NULL, NULL, JSPROP_ENUMERATE);
+	JS_DefineProperty(cx, nodeobj, "activity", STRING_TO_JSVAL(JS_NewStringCopyZ(cx, node_activity(sys->cfg, &node, str, sizeof str, node_num))), NULL, NULL, JSPROP_ENUMERATE);
 	JS_DefineProperty(cx, nodeobj, "useron", INT_TO_JSVAL((int)node.useron), NULL, NULL, JSPROP_ENUMERATE);
 	JS_DefineProperty(cx, nodeobj, "connection", INT_TO_JSVAL((int)node.connection), NULL, NULL, JSPROP_ENUMERATE);
 	JS_DefineProperty(cx, nodeobj, "misc", INT_TO_JSVAL((int)node.misc), NULL, NULL, JSPROP_ENUMERATE);
@@ -2384,8 +2387,10 @@ static jsSyncMethodSpec js_system_functions[] = {
 enum {
 	/* raw node_t fields */
 	 NODE_PROP_STATUS
+	,NODE_PROP_VSTATUS
 	,NODE_PROP_ERRORS
 	,NODE_PROP_ACTION
+	,NODE_PROP_ACTIVITY
 	,NODE_PROP_USERON
 	,NODE_PROP_CONNECTION
 	,NODE_PROP_MISC
@@ -2397,21 +2402,23 @@ enum {
 #ifdef BUILD_JSDOCS
 static const char* node_prop_desc[] = {
 	 "Status (see <tt>nodedefs.js</tt> for valid values)"
+	,"Verbal status - <small>READ ONLY</small>"
 	,"Error counter"
 	,"Current user action (see <tt>nodedefs.js</tt>)"
+	,"Current user activity - <small>READ ONLY</small>y"
 	,"Current user number"
 	,"Connection speed (<tt>0xffff</tt> = Telnet or RLogin)"
 	,"Miscellaneous bit-flags (see <tt>nodedefs.js</tt>)"
 	,"Auxiliary value"
 	,"Extended auxiliary value"
-	,"Node directory"
+	,"Node directory - <small>READ ONLY</small>"
 	,NULL
 };
 #endif
 
-
 static JSBool js_node_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 {
+	char tmp[128];
 	jsval idval;
 	uint		node_num;
     jsint       tiny;
@@ -2438,7 +2445,7 @@ static JSBool js_node_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 
 	rc=JS_SUSPENDREQUEST(cx);
 	memset(&node,0,sizeof(node));
-	if(getnodedat(sys->cfg, node_num, &node, /* lockit: */FALSE, &sys->nodefile)) {
+	if(getnodedat(sys->cfg, node_num, &node, /* lockit: */FALSE, &sys->nodefile) != 0) {
 		JS_RESUMEREQUEST(cx, rc);
 		return(JS_TRUE);
 	}
@@ -2449,12 +2456,22 @@ static JSBool js_node_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
 		case NODE_PROP_STATUS:
 			*vp = INT_TO_JSVAL((int)node.status);
 			break;
+		case NODE_PROP_VSTATUS:
+			if((js_str=JS_NewStringCopyZ(cx, node_vstatus(sys->cfg, &node, tmp, sizeof tmp)))==NULL)
+				return(JS_FALSE);
+			*vp = STRING_TO_JSVAL(js_str);
+			break;
 		case NODE_PROP_ERRORS:
 			*vp = INT_TO_JSVAL((int)node.errors);
 			break;
 		case NODE_PROP_ACTION:
 			*vp = INT_TO_JSVAL((int)node.action);
 			break;
+		case NODE_PROP_ACTIVITY:
+			if((js_str=JS_NewStringCopyZ(cx, node_activity(sys->cfg, &node, tmp, sizeof tmp, node_num)))==NULL)
+				return(JS_FALSE);
+			*vp = STRING_TO_JSVAL(js_str);
+			break;
 		case NODE_PROP_USERON:
 			*vp = INT_TO_JSVAL((int)node.useron);
 			break;
@@ -2551,17 +2568,19 @@ static JSBool js_node_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict,
 }
 
 static jsSyncPropertySpec js_node_properties[] = {
-/*		 name,						tinyid,					flags,				ver	*/
+/*		 name,						tinyid,					flags,								ver	*/
 
 /* raw node_t fields */
-	{	"status",					NODE_PROP_STATUS,		JSPROP_ENUMERATE,	310 },
-	{	"errors",					NODE_PROP_ERRORS,		JSPROP_ENUMERATE,	310 },
-	{	"action",					NODE_PROP_ACTION,		JSPROP_ENUMERATE,	310 },
-	{	"useron",					NODE_PROP_USERON,		JSPROP_ENUMERATE,	310 },
-	{	"connection",				NODE_PROP_CONNECTION,	JSPROP_ENUMERATE,	310 },
-	{	"misc",						NODE_PROP_MISC,			JSPROP_ENUMERATE,	310 },
-	{	"aux",						NODE_PROP_AUX,			JSPROP_ENUMERATE,	310 },
-	{	"extaux",					NODE_PROP_EXTAUX,		JSPROP_ENUMERATE,	310 },
+	{	"status",					NODE_PROP_STATUS,		JSPROP_ENUMERATE,					310 },
+	{	"vstatus",					NODE_PROP_VSTATUS,		JSPROP_ENUMERATE|JSPROP_READONLY,	320 },
+	{	"errors",					NODE_PROP_ERRORS,		JSPROP_ENUMERATE,					310 },
+	{	"action",					NODE_PROP_ACTION,		JSPROP_ENUMERATE,					310 },
+	{	"activity",					NODE_PROP_ACTIVITY,		JSPROP_ENUMERATE|JSPROP_READONLY,	320 },
+	{	"useron",					NODE_PROP_USERON,		JSPROP_ENUMERATE,					310 },
+	{	"connection",				NODE_PROP_CONNECTION,	JSPROP_ENUMERATE,					310 },
+	{	"misc",						NODE_PROP_MISC,			JSPROP_ENUMERATE,					310 },
+	{	"aux",						NODE_PROP_AUX,			JSPROP_ENUMERATE,					310 },
+	{	"extaux",					NODE_PROP_EXTAUX,		JSPROP_ENUMERATE,					310 },
 	{	"dir",						NODE_PROP_DIR,			JSPROP_ENUMERATE|JSPROP_READONLY,	315 },
 	{0}
 };
diff --git a/src/sbbs3/putnode.cpp b/src/sbbs3/putnode.cpp
index 7dfcd88b33dc6e79189c3efd4456e82df3e382f5..5659bc02ac252210587f12a394242fd4428ab856 100644
--- a/src/sbbs3/putnode.cpp
+++ b/src/sbbs3/putnode.cpp
@@ -28,7 +28,6 @@
 /****************************************************************************/
 bool sbbs_t::putnodedat(uint number, node_t* node)
 {
-	char	str[256];
 	char	tmp[128];
 	char	path[MAX_PATH+1];
 	int		wr=0;
@@ -42,21 +41,9 @@ bool sbbs_t::putnodedat(uint number, node_t* node)
 	if(number==cfg.node_num) {
 		if((node->status==NODE_INUSE || node->status==NODE_QUIET)
 			&& node->action<NODE_LAST_ACTION
-			&& text[NodeActionMain+node->action][0]) {
-			node->misc|=NODE_EXT;
-			memset(str,0,128);
-			snprintf(str, sizeof str, text[NodeActionMain+node->action]
-				,useron.alias
-				,useron.level
-				,getage(&cfg,useron.birth)
-				,useron.sex
-				,useron.comp
-				,useron.ipaddr
-				,datestr(useron.firston)
-				,node->aux&0xff
-				,node->connection
-				);
-			putnodeext(number, expand_atcodes(str, tmp, sizeof tmp));
+			&& text[NodeActionMainMenu+node->action][0]) {
+			node->misc |= NODE_EXT;
+			putnodeext(number, expand_atcodes(text[NodeActionMainMenu+node->action], tmp, sizeof tmp));
 		}
 		else
 			node->misc&=~NODE_EXT;
@@ -124,8 +111,7 @@ bool sbbs_t::putnodeext(uint number, char *ext)
 	}
 	number--;   /* make zero based */
 
-	snprintf(str, sizeof str, "%snode.exb",cfg.ctrl_dir);
-	if((node_ext=nopen(str,O_CREAT|O_RDWR|O_DENYNONE))==-1) {
+	if((node_ext=opennodeext(&cfg))==-1) {
 		errormsg(WHERE,ERR_OPEN,str,O_CREAT|O_RDWR|O_DENYNONE);
 		return false;
 	}
diff --git a/src/sbbs3/text.h b/src/sbbs3/text.h
index fdea9d3d5aaf5f73fa78d2d2f6224967241fc249..9694c66aede953dda102d1a399e6d4f0d09fbccf 100644
--- a/src/sbbs3/text.h
+++ b/src/sbbs3/text.h
@@ -711,31 +711,31 @@ enum text {
 	,NoAccessLib
 	,NoAccessDir
 	,NodeLstHdr
-	,NodeActionMain
-	,NodeActionReadMsgs
-	,NodeActionReadMail
-	,NodeActionSendMail
-	,NodeActionReadTxt
-	,NodeActionReadSentMail
-	,NodeActionPostMsg
+	,NodeActionMainMenu
+	,NodeActionReadingMsgs
+	,NodeActionReadingMail
+	,NodeActionSendingMail
+	,NodeActionReadingTextFiles
+	,NodeActionReadingSentMail
+	,NodeActionPostingMsg
 	,NodeActionAutoMsg
-	,NodeActionXtrn
-	,NodeActionDefaults
-	,NodeActionXfer
-	,NodeActionDLing
-	,NodeActionULing
-	,NodeActionBiXfer
-	,NodeActionListFiles
+	,NodeActionXtrnMenu
+	,NodeActionSettings
+	,NodeActionFileMenu
+	,NodeActionDownloadingFile
+	,NodeActionUploadingFile
+	,NodeActionBiXferFile
+	,NodeActionListingFiles
 	,NodeActionLoggingOn
 	,NodeActionLocalChat
 	,NodeActionMultiChat
 	,NodeActionGuruChat
-	,NodeActionChatSec
-	,NodeActionSysopAct
-	,NodeActionQWK
+	,NodeActionChatMenu
+	,NodeActionSysop
+	,NodeActionQWKmenu
 	,NodeActionPrivateChat
-	,NodeActionPaging
-	,NodeActionRetrieving
+	,NodeActionPagingNode
+	,NodeActionRetrievingFile
 	,NodeActionCustom
 	,ViewSignatureQ
 	,DeleteSignatureQ
@@ -914,6 +914,33 @@ enum text {
 	,Nov
 	,Dec
 	,SysopPageNotification
+	,NodeActivityMainMenu
+	,NodeActivityReadingMsgs
+	,NodeActivityReadingMail
+	,NodeActivitySendingMail
+	,NodeActivityReadingTextFiles
+	,NodeActivityReadingSentMail
+	,NodeActivityPostingMsg
+	,NodeActivityAutoMsg
+	,NodeActivityXtrnMenu
+	,NodeActivityRunningXtrn
+	,NodeActivitySettings
+	,NodeActivityFileMenu
+	,NodeActivityDownloadingFile
+	,NodeActivityUploadingFile
+	,NodeActivityBiXferFile
+	,NodeActivityListingFiles
+	,NodeActivityLoggingOn
+	,NodeActivityLocalChat
+	,NodeActivityChatChannel
+	,NodeActivityGlobalChat
+	,NodeActivityChatMenu
+	,NodeActivitySysop
+	,NodeActivityQWKmenu
+	,NodeActivityNodeChat
+	,NodeActivityPagingNode
+	,NodeActivityRetrievingFile
+	,NodeActivityCustom
 
 	,TOTAL_TEXT
 };
diff --git a/src/sbbs3/text_defaults.c b/src/sbbs3/text_defaults.c
index 92a9cae98db5c2c61cf687c7f1df5c3ff71685af..beebd2103898f752efc8f5d01d7977f99fd8cb30 100644
--- a/src/sbbs3/text_defaults.c
+++ b/src/sbbs3/text_defaults.c
@@ -1159,31 +1159,31 @@ const char * const text_defaults[TOTAL_TEXT]={
 	,"\x44\x69\x72\x65\x63\x74\x6f\x72\x79\x20\x25\x75" // 693 NoAccessDir
 	,"\x01\x6e\x01\x68\x4e\x6f\x64\x65\x20\x53\x74\x61\x74\x75\x73\x0d\x0a\x01\x63\xc4\xc4\xc4\xc4\x20\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4"
 		"\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\xc4\x0d\x0a" // 694 NodeLstHdr
-	,"" // 695 NodeActionMain
-	,"" // 696 NodeActionReadMsgs
-	,"" // 697 NodeActionReadMail
-	,"" // 698 NodeActionSendMail
-	,"" // 699 NodeActionReadTxt
-	,"" // 700 NodeActionReadSentMail
-	,"" // 701 NodeActionPostMsg
+	,"" // 695 NodeActionMainMenu
+	,"" // 696 NodeActionReadingMsgs
+	,"" // 697 NodeActionReadingMail
+	,"" // 698 NodeActionSendingMail
+	,"" // 699 NodeActionReadingTextFiles
+	,"" // 700 NodeActionReadingSentMail
+	,"" // 701 NodeActionPostingMsg
 	,"" // 702 NodeActionAutoMsg
-	,"" // 703 NodeActionXtrn
-	,"" // 704 NodeActionDefaults
-	,"" // 705 NodeActionXfer
-	,"" // 706 NodeActionDLing
-	,"" // 707 NodeActionULing
-	,"" // 708 NodeActionBiXfer
-	,"" // 709 NodeActionListFiles
+	,"" // 703 NodeActionXtrnMenu
+	,"" // 704 NodeActionSettings
+	,"" // 705 NodeActionFileMenu
+	,"" // 706 NodeActionDownloadingFile
+	,"" // 707 NodeActionUploadingFile
+	,"" // 708 NodeActionBiXferFile
+	,"" // 709 NodeActionListingFiles
 	,"" // 710 NodeActionLoggingOn
 	,"" // 711 NodeActionLocalChat
 	,"" // 712 NodeActionMultiChat
 	,"" // 713 NodeActionGuruChat
-	,"" // 714 NodeActionChatSec
-	,"" // 715 NodeActionSysopAct
-	,"" // 716 NodeActionQWK
+	,"" // 714 NodeActionChatMenu
+	,"" // 715 NodeActionSysop
+	,"" // 716 NodeActionQWKmenu
 	,"" // 717 NodeActionPrivateChat
-	,"" // 718 NodeActionPaging
-	,"" // 719 NodeActionRetrieving
+	,"" // 718 NodeActionPagingNode
+	,"" // 719 NodeActionRetrievingFile
 	,"" // 720 NodeActionCustom
 	,"\x56\x69\x65\x77\x20\x73\x69\x67\x6e\x61\x74\x75\x72\x65" // 721 ViewSignatureQ
 	,"\x44\x65\x6c\x65\x74\x65\x20\x73\x69\x67\x6e\x61\x74\x75\x72\x65" // 722 DeleteSignatureQ
@@ -1458,4 +1458,33 @@ const char * const text_defaults[TOTAL_TEXT]={
 	,"\x44\x65\x63" // 896 Dec
 	,"\x07\x40\x41\x4c\x49\x41\x53\x40\x20\x70\x61\x67\x65\x64\x20\x79\x6f\x75\x20\x74\x6f\x20\x63\x68\x61\x74\x20\x66\x72\x6f\x6d\x20"
 		"\x6e\x6f\x64\x65\x20\x40\x4e\x4f\x44\x45\x40\x07" // 897 SysopPageNotification
+	,"\x61\x74\x20\x6d\x61\x69\x6e\x20\x6d\x65\x6e\x75" // 898 NodeActivityMainMenu
+	,"\x72\x65\x61\x64\x69\x6e\x67\x20\x6d\x65\x73\x73\x61\x67\x65\x73" // 899 NodeActivityReadingMsgs
+	,"\x72\x65\x61\x64\x69\x6e\x67\x20\x6d\x61\x69\x6c" // 900 NodeActivityReadingMail
+	,"\x73\x65\x6e\x64\x69\x6e\x67\x20\x6d\x61\x69\x6c" // 904 NodeActivitySendingMail
+	,"\x72\x65\x61\x64\x69\x6e\x67\x20\x74\x65\x78\x74\x20\x66\x69\x6c\x65\x73" // 902 NodeActivityReadingTextFiles
+	,"\x72\x65\x61\x64\x69\x6e\x67\x20\x73\x65\x6e\x74\x20\x6d\x61\x69\x6c" // 901 NodeActivityReadingSentMail
+	,"\x70\x6f\x73\x74\x69\x6e\x67\x20\x6d\x65\x73\x73\x61\x67\x65" // 903 NodeActivityPostingMsg
+	,"\x70\x6f\x73\x74\x69\x6e\x67\x20\x61\x75\x74\x6f\x2d\x6d\x65\x73\x73\x61\x67\x65" // 905 NodeActivityAutoMsg
+	,"\x61\x74\x20\x65\x78\x74\x65\x72\x6e\x61\x6c\x20\x70\x72\x6f\x67\x72\x61\x6d\x20\x6d\x65\x6e\x75" // 906 NodeActivityXtrnMenu
+	,"\x72\x75\x6e\x6e\x69\x6e\x67" // 907 NodeActivityRunningXtrn
+	,"\x63\x68\x61\x6e\x67\x69\x6e\x67\x20\x73\x65\x74\x74\x69\x6e\x67\x73" // 908 NodeActivitySettings
+	,"\x61\x74\x20\x66\x69\x6c\x65\x20\x6d\x65\x6e\x75" // 909 NodeActivityFileMenu
+	,"\x64\x6f\x77\x6e\x6c\x6f\x61\x64\x69\x6e\x67\x20\x66\x69\x6c\x65\x73" // 911 NodeActivityDownloadingFile
+	,"\x75\x70\x6c\x6f\x61\x64\x69\x6e\x67\x20\x66\x69\x6c\x65\x73" // 912 NodeActivityUploadingFile
+	,"\x74\x72\x61\x6e\x73\x66\x65\x72\x72\x69\x6e\x67\x20\x66\x69\x6c\x65\x73\x20\x62\x69\x64\x69\x72\x65\x63\x74\x69\x6f\x6e\x61\x6c"
+		"" // 913 NodeActivityBiXferFile
+	,"\x6c\x69\x73\x74\x69\x6e\x67\x20\x66\x69\x6c\x65\x73" // 914 NodeActivityListingFiles
+	,"\x6c\x6f\x67\x67\x69\x6e\x67\x20\x6f\x6e" // 915 NodeActivityLoggingOn
+	,"\x63\x68\x61\x74\x74\x69\x6e\x67\x20\x77\x69\x74\x68\x20\x25\x73" // 916 NodeActivityLocalChat
+	,"\x69\x6e\x20\x6d\x75\x6c\x74\x69\x6e\x6f\x64\x65\x20\x63\x68\x61\x74\x20\x63\x68\x61\x6e\x6e\x65\x6c\x20\x25\x64" // 917 NodeActivityChatChannel
+	,"\x69\x6e\x20\x6d\x75\x6c\x74\x69\x6e\x6f\x64\x65\x20\x67\x6c\x6f\x62\x61\x6c\x20\x63\x68\x61\x74\x20\x63\x68\x61\x6e\x6e\x65\x6c"
+		"" // 918 NodeActivityGlobalChat
+	,"\x69\x6e\x20\x63\x68\x61\x74\x20\x73\x65\x63\x74\x69\x6f\x6e" // 921 NodeActivityChatMenu
+	,"\x70\x65\x72\x66\x6f\x72\x6d\x69\x6e\x67\x20\x73\x79\x73\x6f\x70\x20\x61\x63\x74\x69\x76\x69\x74\x79" // 923 NodeActivitySysop
+	,"\x74\x72\x61\x6e\x73\x66\x65\x72\x72\x69\x6e\x67\x20\x51\x57\x4b\x20\x70\x61\x63\x6b\x65\x74" // 922 NodeActivityQWKmenu
+	,"\x63\x68\x61\x74\x74\x69\x6e\x67\x20\x77\x69\x74\x68\x20\x6e\x6f\x64\x65\x20\x25\x75" // 920 NodeActivityNodeChat
+	,"\x70\x61\x67\x69\x6e\x67\x20\x6e\x6f\x64\x65\x20\x25\x75\x20\x66\x6f\x72\x20\x70\x72\x69\x76\x61\x74\x65\x20\x63\x68\x61\x74" // 919 NodeActivityPagingNode
+	,"\x72\x65\x74\x72\x69\x65\x76\x69\x6e\x67\x20\x66\x69\x6c\x65\x20\x66\x72\x6f\x6d\x20\x64\x65\x76\x69\x63\x65\x20\x23\x25\x64" // 910 NodeActivityRetrievingFile
+	,"\x70\x65\x72\x66\x6f\x72\x6d\x69\x6e\x67\x20\x63\x75\x73\x74\x6f\x6d\x20\x61\x63\x74\x69\x76\x69\x74\x79" // 924 NodeActivityCustom
 };
diff --git a/src/sbbs3/text_id.c b/src/sbbs3/text_id.c
index d449c0ff9fc058e3c8724193a835c973632b739a..3a80ae6ac8272c0d89a164106e9318cdc281bfaf 100644
--- a/src/sbbs3/text_id.c
+++ b/src/sbbs3/text_id.c
@@ -695,31 +695,31 @@ const char* const text_id[]={
 	,"NoAccessLib"
 	,"NoAccessDir"
 	,"NodeLstHdr"
-	,"NodeActionMain"
-	,"NodeActionReadMsgs"
-	,"NodeActionReadMail"
-	,"NodeActionSendMail"
-	,"NodeActionReadTxt"
-	,"NodeActionReadSentMail"
-	,"NodeActionPostMsg"
+	,"NodeActionMainMenu"
+	,"NodeActionReadingMsgs"
+	,"NodeActionReadingMail"
+	,"NodeActionSendingMail"
+	,"NodeActionReadingTextFiles"
+	,"NodeActionReadingSentMail"
+	,"NodeActionPostingMsg"
 	,"NodeActionAutoMsg"
-	,"NodeActionXtrn"
-	,"NodeActionDefaults"
-	,"NodeActionXfer"
-	,"NodeActionDLing"
-	,"NodeActionULing"
-	,"NodeActionBiXfer"
-	,"NodeActionListFiles"
+	,"NodeActionXtrnMenu"
+	,"NodeActionSettings"
+	,"NodeActionFileMenu"
+	,"NodeActionDownloadingFile"
+	,"NodeActionUploadingFile"
+	,"NodeActionBiXferFile"
+	,"NodeActionListingFiles"
 	,"NodeActionLoggingOn"
 	,"NodeActionLocalChat"
 	,"NodeActionMultiChat"
 	,"NodeActionGuruChat"
-	,"NodeActionChatSec"
-	,"NodeActionSysopAct"
-	,"NodeActionQWK"
+	,"NodeActionChatMenu"
+	,"NodeActionSysop"
+	,"NodeActionQWKmenu"
 	,"NodeActionPrivateChat"
-	,"NodeActionPaging"
-	,"NodeActionRetrieving"
+	,"NodeActionPagingNode"
+	,"NodeActionRetrievingFile"
 	,"NodeActionCustom"
 	,"ViewSignatureQ"
 	,"DeleteSignatureQ"
@@ -898,4 +898,31 @@ const char* const text_id[]={
 	,"Nov"
 	,"Dec"
 	,"SysopPageNotification"
+	,"NodeActivityMainMenu"
+	,"NodeActivityReadingMsgs"
+	,"NodeActivityReadingMail"
+	,"NodeActivitySendingMail"
+	,"NodeActivityReadingTextFiles"
+	,"NodeActivityReadingSentMail"
+	,"NodeActivityPostingMsg"
+	,"NodeActivityAutoMsg"
+	,"NodeActivityXtrnMenu"
+	,"NodeActivityRunningXtrn"
+	,"NodeActivitySettings"
+	,"NodeActivityFileMenu"
+	,"NodeActivityDownloadingFile"
+	,"NodeActivityUploadingFile"
+	,"NodeActivityBiXferFile"
+	,"NodeActivityListingFiles"
+	,"NodeActivityLoggingOn"
+	,"NodeActivityLocalChat"
+	,"NodeActivityChatChannel"
+	,"NodeActivityGlobalChat"
+	,"NodeActivityChatMenu"
+	,"NodeActivitySysop"
+	,"NodeActivityQWKmenu"
+	,"NodeActivityNodeChat"
+	,"NodeActivityPagingNode"
+	,"NodeActivityRetrievingFile"
+	,"NodeActivityCustom"
 };
diff --git a/src/sbbs3/userdat.c b/src/sbbs3/userdat.c
index 732cf9ce47eb019dda41fa41ea5a073aa922bb5e..a4f12032e1d08dfc33017ada834f01fc4ecf027a 100644
--- a/src/sbbs3/userdat.c
+++ b/src/sbbs3/userdat.c
@@ -1403,14 +1403,158 @@ char* getnodeext(scfg_t* cfg, int num, char* buf)
 	return buf;
 }
 
+char* node_vstatus(scfg_t* cfg, node_t* node, char* str, size_t size)
+{
+	char	tmp[128];
+
+    switch(node->status) {
+        case NODE_WFC:
+			return cfg->text != NULL ? cfg->text[NodeStatusWaitingForCall] : "Waiting for connection";
+        case NODE_OFFLINE:
+			return cfg->text != NULL ? cfg->text[NodeStatusOffline] : "Offline";
+        case NODE_NETTING:	/* Obsolete */
+            return "Networking";
+        case NODE_LOGON:
+			return cfg->text != NULL ? cfg->text[NodeStatusLogon] : "At login prompt";
+		case NODE_LOGOUT:
+			snprintf(str, sizeof str, cfg->text != NULL ? cfg->text[NodeStatusLogout] : "Logging out %s", username(cfg,node->useron,tmp));
+			return str;
+        case NODE_EVENT_WAITING:
+            return cfg->text != NULL ? cfg->text[NodeStatusEventWaiting] : "Waiting for all nodes to become inactive";
+        case NODE_EVENT_LIMBO:
+            snprintf(str, size, cfg->text != NULL ? cfg->text[NodeStatusEventLimbo] : "Waiting for node %d to finish external event"
+                ,node->aux);
+			break;
+        case NODE_EVENT_RUNNING:
+            return cfg->text != NULL ? cfg->text[NodeStatusEventRunning] : "Running external event";
+        case NODE_NEWUSER:
+            return cfg->text != NULL ? cfg->text[NodeStatusNewUser] : "New user applying for access";
+		case NODE_QUIET:
+		case NODE_INUSE:
+			return "In use";
+			break;
+		default:
+			snprintf(str, size, "Unknown status %u", node->status);
+			break;
+	}
+	return str;
+}
+
+char* node_activity(scfg_t* cfg, node_t* node, char* str, size_t size, int num)
+{
+	int xtrnnum;
+	int gurunum;
+	user_t	user = {0};
+
+	if(node->misc & NODE_EXT) {
+		getnodeext(cfg, num, str); // note assuming sizeof str is >= 128
+		return str;
+	}
+
+	switch(node->action) {
+		case NODE_MAIN:
+			return cfg->text != NULL ? cfg->text[NodeActivityMainMenu] : "at main menu";
+		case NODE_RMSG:
+			return cfg->text != NULL ? cfg->text[NodeActivityReadingMsgs] : "reading messages";
+		case NODE_RMAL:
+			return cfg->text != NULL ? cfg->text[NodeActivityReadingMail] : "reading mail";
+		case NODE_RSML:
+			return cfg->text != NULL ? cfg->text[NodeActivityReadingSentMail]: "reading sent mail";
+		case NODE_RTXT:
+			return cfg->text != NULL ? cfg->text[NodeActivityReadingTextFiles] : "reading text files";
+		case NODE_PMSG:
+			return cfg->text != NULL ? cfg->text[NodeActivityPostingMsg] : "posting message";
+		case NODE_SMAL:
+			return cfg->text != NULL ? cfg->text[NodeActivitySendingMail] : "sending mail";
+		case NODE_AMSG:
+			return cfg->text != NULL ? cfg->text[NodeActivityAutoMsg] : "posting auto-message";
+		case NODE_XTRN:
+			if(node->aux == 0)
+				return cfg->text != NULL ? cfg->text[NodeActivityXtrnMenu] : "at external program menu";
+			user.number = node->useron;
+			getuserdat(cfg, &user);
+			xtrnnum = getxtrnnum(cfg, user.curxtrn);
+			if(is_valid_xtrnnum(cfg, xtrnnum))
+				snprintf(str, size, "%s %s"
+					,cfg->text != NULL ? cfg->text[NodeActivityRunningXtrn] : "running"
+					,cfg->xtrn[xtrnnum]->name);
+			else if(*user.curxtrn != '\0')
+				snprintf(str, size, "%s external program %s"
+					,cfg->text != NULL ? cfg->text[NodeActivityRunningXtrn] : "running"
+					,user.curxtrn);
+			else
+				snprintf(str, size, "%s external program #%d"
+					,cfg->text != NULL ? cfg->text[NodeActivityRunningXtrn] : "running"
+					,node->aux);
+			break;
+		case NODE_DFLT:
+			return cfg->text != NULL ? cfg->text[NodeActivitySettings] : "changing defaults";
+		case NODE_XFER:
+			return cfg->text != NULL ? cfg->text[NodeActivityFileMenu] : "at transfer menu";
+		case NODE_RFSD:
+			snprintf(str, size,cfg->text != NULL ? cfg->text[NodeActivityRetrievingFile] : "retrieving from device #%d", node->aux);
+			break;
+		case NODE_DLNG:
+			return cfg->text != NULL ? cfg->text[NodeActivityDownloadingFile] : "downloading";
+		case NODE_ULNG:
+			return cfg->text != NULL ? cfg->text[NodeActivityUploadingFile] : "uploading";
+		case NODE_BXFR:
+			return cfg->text != NULL ? cfg->text[NodeActivityBiXferFile] : "transferring bidirectional";
+		case NODE_LFIL:
+			return cfg->text != NULL ? cfg->text[NodeActivityListingFiles] : "listing files";
+		case NODE_LOGN:
+			return cfg->text != NULL ? cfg->text[NodeActivityLoggingOn] : "logging on";
+		case NODE_LCHT:
+			snprintf(str, size, cfg->text != NULL ? cfg->text[NodeActivityLocalChat] : "chatting with %s", cfg->sys_op);
+			break;
+		case NODE_MCHT:
+			if(node->aux != 0)
+				snprintf(str, sizeof str
+					,cfg->text != NULL ? cfg->text[NodeActivityChatChannel] : "in multinode chat channel %d"
+					,node->aux & 0xff);
+			else
+				return cfg->text != NULL ? cfg->text[NodeActivityGlobalChat] : "in multinode global chat channel";
+			break;
+		case NODE_PAGE:
+			snprintf(str, size, cfg->text != NULL ? cfg->text[NodeActivityPagingNode] : "paging node %u for private chat", node->aux);
+			break;
+		case NODE_PCHT:
+			if(node->aux == 0)
+				snprintf(str, size
+					,cfg->text != NULL ? cfg->text[NodeActivityLocalChat] : "in local chat with %s"
+					,cfg->sys_op);
+			else
+				snprintf(str, size
+					,cfg->text != NULL ? cfg->text[NodeActivityNodeChat] : "in private chat with node %u"
+					,node->aux);
+			break;
+		case NODE_GCHT:
+			gurunum = node->aux;
+			if(gurunum >= cfg->total_gurus)
+				gurunum = 0;
+			snprintf(str, size, cfg->text != NULL ? cfg->text[NodeActivityLocalChat] : "chatting with %s", cfg->guru[gurunum]->name);
+			break;
+		case NODE_CHAT:
+			return cfg->text != NULL ? cfg->text[NodeActivityChatMenu] : "in chat section";
+		case NODE_TQWK:
+			return cfg->text != NULL ? cfg->text[NodeActivityQWKmenu] : "transferring QWK packet";
+		case NODE_SYSP:
+			return cfg->text != NULL ? cfg->text[NodeActivitySysop] : "performing sysop activities";
+		case NODE_CUSTOM:
+			return cfg->text != NULL ? cfg->text[NodeActivityCustom] : "performing custom action";
+		default:
+			snprintf(str, size, "unknown user action %d", node->action);
+			break;
+	}
+	return str;
+}
+
 char* nodestatus(scfg_t* cfg, node_t* node, char* buf, size_t buflen, int num)
 {
 	char	str[256];
 	char	tmp[128];
 	char*	mer;
 	int		hour;
-	int		xtrnnum;
-	user_t	user = {0};
 
 	if(node==NULL) {
 		strncpy(buf,"(null)",buflen);
@@ -1420,158 +1564,25 @@ char* nodestatus(scfg_t* cfg, node_t* node, char* buf, size_t buflen, int num)
 	str[0]=0;
     switch(node->status) {
         case NODE_WFC:
-            SAFECOPY(str,"Waiting for connection");
-            break;
         case NODE_OFFLINE:
-            strcpy(str,"Offline");
-            break;
-        case NODE_NETTING:	/* Obsolete */
-            SAFECOPY(str,"Networking");
-            break;
-        case NODE_LOGON:
-            SAFEPRINTF(str,"At login prompt %s"
-				,node_connection_desc(node->connection, tmp));
-            break;
+        case NODE_NETTING:
 		case NODE_LOGOUT:
-			SAFEPRINTF(str,"Logging out %s", username(cfg,node->useron,tmp));
-			break;
         case NODE_EVENT_WAITING:
-            SAFECOPY(str,"Waiting for all nodes to become inactive");
-            break;
         case NODE_EVENT_LIMBO:
-            SAFEPRINTF(str,"Waiting for node %d to finish external event"
-                ,node->aux);
-            break;
         case NODE_EVENT_RUNNING:
-            SAFECOPY(str,"Running external event");
-            break;
+			SAFECOPY(str, node_vstatus(cfg, node, tmp, sizeof tmp));
+			break;
+        case NODE_LOGON:
         case NODE_NEWUSER:
-            SAFEPRINTF(str,"New user applying for access %s"
-				,node_connection_desc(node->connection, tmp));
+			SAFECOPY(str, node_vstatus(cfg, node, tmp, sizeof tmp));
+			SAFECAT(str, " ");
+			SAFECAT(str, node_connection_desc(node->connection, tmp));
             break;
         case NODE_QUIET:
         case NODE_INUSE:
-			if(node->misc & NODE_EXT) {
-				getnodeext(cfg, num, str);
-				break;
-			}
             username(cfg,node->useron,str);
-            strcat(str," ");
-            switch(node->action) {
-                case NODE_MAIN:
-                    strcat(str,"at main menu");
-                    break;
-                case NODE_RMSG:
-                    strcat(str,"reading messages");
-                    break;
-                case NODE_RMAL:
-                    strcat(str,"reading mail");
-                    break;
-                case NODE_RSML:
-                    strcat(str,"reading sent mail");
-                    break;
-                case NODE_RTXT:
-                    strcat(str,"reading text files");
-                    break;
-                case NODE_PMSG:
-                    strcat(str,"posting message");
-                    break;
-                case NODE_SMAL:
-                    strcat(str,"sending mail");
-                    break;
-                case NODE_AMSG:
-                    strcat(str,"posting auto-message");
-                    break;
-                case NODE_XTRN:
-                    if(node->aux == 0) {
-                        strcat(str,"at external program menu");
-						break;
-					}
-					user.number = node->useron;
-					getuserdat(cfg, &user);
-					xtrnnum = getxtrnnum(cfg, user.curxtrn);
-					if(is_valid_xtrnnum(cfg, xtrnnum))
-						sprintf(str+strlen(str),"running %s"
-							,cfg->xtrn[xtrnnum]->name);
-					else if(*user.curxtrn != '\0')
-						sprintf(str+strlen(str),"running external program %s"
-							,user.curxtrn);
-					else
-						sprintf(str+strlen(str),"running external program #%d"
-							,node->aux);
-                    break;
-                case NODE_DFLT:
-                    strcat(str,"changing defaults");
-                    break;
-                case NODE_XFER:
-                    strcat(str,"at transfer menu");
-                    break;
-                case NODE_RFSD:
-                    sprintf(str+strlen(str),"retrieving from device #%d",node->aux);
-                    break;
-                case NODE_DLNG:
-                    strcat(str,"downloading");
-                    break;
-                case NODE_ULNG:
-                    strcat(str,"uploading");
-                    break;
-                case NODE_BXFR:
-                    strcat(str,"transferring bidirectional");
-                    break;
-                case NODE_LFIL:
-                    strcat(str,"listing files");
-                    break;
-                case NODE_LOGN:
-                    strcat(str,"logging on");
-                    break;
-                case NODE_LCHT:
-                    strcat(str,"in local chat with sysop");
-                    break;
-                case NODE_MCHT:
-                    if(node->aux) {
-                        sprintf(str+strlen(str),"in multinode chat channel %d"
-                            ,node->aux&0xff);
-                        if(node->aux&0x1f00) { /* password */
-                            strcat(str,"* ");
-                            unpackchatpass(str+strlen(str),node);
-                        }
-                    }
-                    else
-                        strcat(str,"in multinode global chat channel");
-                    break;
-                case NODE_PAGE:
-                    sprintf(str+strlen(str)
-						,"paging node %u for private chat",node->aux);
-                    break;
-                case NODE_PCHT:
-                    if(node->aux==0)
-                        sprintf(str+strlen(str)
-							,"in local chat with %s"
-							,cfg->sys_op);
-                    else
-                        sprintf(str+strlen(str)
-							,"in private chat with node %u"
-                            ,node->aux);
-                    break;
-                case NODE_GCHT:
-                    strcat(str,"chatting with The Guru");
-                    break;
-                case NODE_CHAT:
-                    strcat(str,"in chat section");
-                    break;
-                case NODE_TQWK:
-                    strcat(str,"transferring QWK packet");
-                    break;
-                case NODE_SYSP:
-                    strcat(str,"performing sysop activities");
-                    break;
-				case NODE_CUSTOM:
-					SAFECAT(str, "performing custom action");
-					break;
-                default:
-                    sprintf(str+strlen(str),"%d",node->action);
-                    break;
-			}
+            SAFECAT(str," ");
+			SAFECAT(str, node_activity(cfg, node, tmp, sizeof tmp, num));
 			sprintf(str+strlen(str)," %s",node_connection_desc(node->connection, tmp));
             if(node->action==NODE_DLNG) {
                 if((node->aux/60)>=12) {
diff --git a/src/sbbs3/userdat.h b/src/sbbs3/userdat.h
index fe987610859ed88f4f0ccfc4ed0fb03246853fbe..0d60262daabb0151f4d1f4d5644c75b3cf21288c 100644
--- a/src/sbbs3/userdat.h
+++ b/src/sbbs3/userdat.h
@@ -93,6 +93,8 @@ DLLEXPORT int	opennodedat(scfg_t*);
 DLLEXPORT int	opennodeext(scfg_t*);
 DLLEXPORT int	getnodedat(scfg_t*, uint number, node_t *node, bool lockit, int* file);
 DLLEXPORT int	putnodedat(scfg_t*, uint number, node_t *node, bool closeit, int file);
+DLLEXPORT char* node_activity(scfg_t*, node_t* node, char* str, size_t size, int num);
+DLLEXPORT char* node_vstatus(scfg_t*, node_t* node, char* str, size_t size);
 DLLEXPORT char* nodestatus(scfg_t*, node_t* node, char* buf, size_t buflen, int num);
 DLLEXPORT void	printnodedat(scfg_t*, uint number, node_t* node);
 DLLEXPORT int	is_user_online(scfg_t*, uint usernumber);