From 458243604c1801bc698b879b7b1f1dacccd66e31 Mon Sep 17 00:00:00 2001
From: echicken <>
Date: Thu, 23 Aug 2012 22:23:38 +0000
Subject: [PATCH] A lightbar message browser/reader/poster with threaded
 sub-board views. Includes a lightbar/hotkey message area selector.

---
 xtrn/ecreader/ecReader.js            | 298 +++++++++++++++++++++++++++
 xtrn/ecreader/messageAreaSelector.js |  28 +++
 xtrn/ecreader/msglib.js              |  75 +++++++
 xtrn/ecreader/readme.txt             |  39 ++++
 4 files changed, 440 insertions(+)
 create mode 100644 xtrn/ecreader/ecReader.js
 create mode 100644 xtrn/ecreader/messageAreaSelector.js
 create mode 100644 xtrn/ecreader/msglib.js
 create mode 100644 xtrn/ecreader/readme.txt

diff --git a/xtrn/ecreader/ecReader.js b/xtrn/ecreader/ecReader.js
new file mode 100644
index 0000000000..4282351529
--- /dev/null
+++ b/xtrn/ecreader/ecReader.js
@@ -0,0 +1,298 @@
+// ecReader
+// A lightbar, threaded message reader for Synchronet 3.16+
+// echicken -at- bbs.electronicchicken.com, 2012
+
+console.clear();
+console.putmsg("\1h\1wecReader by echicken, loading message threads...");
+
+load("sbbsdefs.js");
+load("msgutils.js");
+load(js.exec_dir + "msglib.js");
+load("frame.js");
+load("tree.js");
+
+var threaded = true;	// False to default to flat view
+var lbg = BG_CYAN;		// Lightbar background
+var lfg = WHITE;		// Foreground colour of highlighted text
+var nfg = LIGHTGRAY;	// Foreground colour of non-highlighted text
+var xfg = LIGHTCYAN;	// Subtree expansion indicator colour
+var tfg = LIGHTCYAN;	// Uh, that line beside subtree items
+var fbg = BG_BLUE;		// Title, Header, Help frame background colour
+var hfg = "\1h\1w"; 	// Heading text (CTRL-A format, for now)
+var sffg = "\1h\1c";	// Heading sub-field text (CTRL-A format, for now)
+var mfg = "\1n\1w";		// Message text colour (CTRL-A format, for now)
+
+var messages;
+var tree;
+var currentMessage = 0;
+
+if(argc > 0 && argv[0])
+	var msgBase = new MsgBase(argv[0]);
+else
+	var msgBase = new MsgBase(msg_area.grp_list[bbs.curgrp].sub_list[bbs.cursub].code);
+
+var frame = new Frame(1, 1, 80, 24, BG_BLACK|WHITE);
+var titleFrame = new Frame(1, 1, 80, 2, fbg|WHITE, frame);
+var columnFrame = new Frame(1, 3, 80, 1, fbg|WHITE, frame);
+var treeFrame = new Frame(1, 4, 80, 20, BG_BLACK|WHITE, frame);
+var helpFrame = new Frame(1, 24, 80, 1, fbg|WHITE, frame);
+var messageFrame = new Frame(1, 3, 80, 21, BG_BLACK|WHITE, frame);
+var headerFrame = new Frame(1, 3, 80, 4, fbg|WHITE, messageFrame);
+var bodyFrame = new Frame(1, 7, 80, 17, BG_BLACK|WHITE, messageFrame);
+var messageBar = new Frame(1, 24, 80, 1, fbg|WHITE, messageFrame);
+
+frame.open();
+messageFrame.bottom();
+headerFrame.bottom();
+
+columnFrame.putmsg(
+	format("%-9s", "Msg #")
+	+ format("%-13s", "From")
+	+ format("%-13s", "To")
+	+ format("%-28s", "Subject")
+	+ "Date"
+);
+helpFrame.putmsg(
+	hfg + "HOME" + sffg + ": first, "
+	+ hfg + "END" + sffg + ": last | "
+	+ sffg + "View: "
+	+ hfg + "T" + sffg + "hreaded, "
+	+ hfg + "F" + sffg + "lat | "
+	+ hfg + "C" + sffg + "hange area  "
+	+ hfg + "P" + sffg + "ost  "
+	+ hfg + "Q" + sffg + "uit"
+);
+messageBar.putmsg(
+	sffg + "Scroll: " + hfg + "UP" + sffg + "/" + hfg + "DOWN"
+	+ sffg + ", "
+	+ hfg + "HOME" + sffg + ": top, "
+	+ hfg + "END" + sffg + ": bottom"
+	+ sffg + " | "
+	+ hfg + "R" + sffg + "eply  "
+	+ hfg + "P" + sffg + "revious  "
+	+ hfg + "N" + sffg + "ext  "
+	+ hfg + "Q" + sffg + "uit"
+);
+
+function formatItem(messageNumber, from, to, subject, date) {
+	var retval = 
+			format("%-8s", messageNumber)
+			+ format("%-13s", from.substr(0, 12))
+			+ format("%-13s", to.substr(0, 12))
+			+ format("%-28s", subject.substr(0, 27))
+			+ strftime("%m-%d-%Y %H:%I", date);
+	return retval;
+}
+
+function getFlatList(oldestFirst) {
+	var header = null;
+	msgBase.open();
+	for(var m = msgBase.first_msg; m <= msgBase.last_msg; m++) {
+		header = msgBase.get_msg_header(m);
+		if(header === null || header.attr&MSG_DELETE)
+			continue;
+		messages.push(header);
+	}
+	msgBase.close();
+	if(oldestFirst === undefined || !oldestFirst)
+		messages.reverse();
+	for(var m = 0; m < messages.length; m++) {
+		tree.addItem(formatItem(messages[m].number, messages[m].from, messages[m].to, messages[m].subject, messages[m].when_written_time), showMessage, messages[m]);
+	}
+	tree.open();
+	tree.cycle();
+}
+
+function getThreadedList() {
+	var threads = getMessageThreads(msgBase.cfg.code);
+	for(var t in threads.order) {
+		if(threads.thread[threads.order[t]].messages.length < 2) {
+			messages.push(threads.thread[threads.order[t]].messages[0]);
+			tree.addItem(
+					formatItem(
+						threads.thread[threads.order[t]].messages[0].number,
+						threads.thread[threads.order[t]].messages[0].from,
+						threads.thread[threads.order[t]].messages[0].to,
+						threads.thread[threads.order[t]].messages[0].subject,
+						threads.thread[threads.order[t]].messages[0].when_written_time
+					),
+				showMessage, messages[messages.length - 1]
+			);
+			continue;
+		}
+		st = tree.addTree(
+				formatItem(
+					threads.thread[threads.order[t]].messages[0].number,
+					threads.thread[threads.order[t]].messages[0].from,
+					threads.thread[threads.order[t]].messages[0].to,
+					threads.thread[threads.order[t]].messages[0].subject,
+					threads.thread[threads.order[t]].newest
+				)
+		);
+		for(var m = 0; m < threads.thread[threads.order[t]].messages.length; m++) {
+			messages.push(threads.thread[threads.order[t]].messages[m]);
+			st.addItem(
+				formatItem(
+					threads.thread[threads.order[t]].messages[m].number,
+					threads.thread[threads.order[t]].messages[m].from,
+					threads.thread[threads.order[t]].messages[m].to,
+					threads.thread[threads.order[t]].messages[m].subject,
+					threads.thread[threads.order[t]].messages[m].when_written_time
+				),
+				showMessage, messages[messages.length - 1]
+			);
+		}
+	}
+	tree.open();
+	tree.cycle();
+}
+
+function getList() {
+	tree = new Tree(treeFrame);
+	tree.colors.lbg = lbg;
+	tree.colors.lfg = lfg;
+	tree.colors.fg = nfg;
+	tree.colors.xfg = xfg;
+	tree.colors.tfg = tfg;
+	messages = [];
+	titleFrame.clear();
+	titleFrame.putmsg(hfg + "Message Group: " + sffg + msgBase.cfg.grp_name);
+	titleFrame.crlf();
+	titleFrame.putmsg(hfg + "    Sub Board: " + sffg + msgBase.cfg.name);
+	if(threaded)
+		getThreadedList();
+	else
+		getFlatList(false);
+}
+
+function showMessage(header) {
+	currentMessage = messages.indexOf(header);
+	messageFrame.top();
+	msgBase.open();
+	var body = msgBase.get_msg_body(header.number);
+	msgBase.close();
+	headerFrame.putmsg(
+		hfg + format("%15s", "Subject: ") + sffg + header.subject
+		+ "\r\n" +
+		hfg + format("%15s", "From: ") + sffg + header.from
+		+ "\r\n" +
+		hfg + format("%15s", "To: ") + sffg + header.to
+		+ "\r\n" +
+		hfg + format("%15s", "Date: ") + sffg + system.timestr(header.when_written_time)
+	);
+	bodyFrame.scrollTo(0, 0);
+	bodyFrame.putmsg(mfg + word_wrap(body));
+	bodyFrame.scrollTo(0, 0);
+	var retval = true;
+	var userInput = "";
+	var h = null;
+	var n = header.number;
+	while(userInput != "Q") {
+		if(frame.cycle())
+			console.gotoxy(80, 24);
+		userInput = console.getkey().toUpperCase();
+		switch(userInput) {
+			case "R":
+				var f = new File(system.node_dir + "QUOTES.TXT");
+				f.open("w");
+				f.write(body);
+				f.close();
+				frame.invalidate();
+				console.clear();
+				bbs.post_msg(msgBase.cfg.code, WM_QUOTE, header);
+				frame.draw();
+				retval = header;
+				userInput = "Q";
+				getList();
+				break;
+			case "P":
+				if(!threaded && currentMessage < (messages.length - 1)) {
+					currentMessage++;
+					h = messages[currentMessage];
+					retval = h;
+					userInput = "Q";
+				} else if(threaded && currentMessage > 0) {
+					currentMessage = currentMessage - 1;
+					h = messages[currentMessage];
+					retval = h;
+					userInput = "Q";
+				}
+				break;
+			case "N":
+				if(!threaded && currentMessage > 0) {
+					currentMessage = currentMessage - 1;
+					h = messages[currentMessage];
+					retval = h;
+					userInput = "Q";
+				} else if(threaded && currentMessage < (messages.length - 1)) {
+					currentMessage++;
+					h = messages[currentMessage];
+					retval = h;
+					userInput = "Q";
+				}
+				break;
+			case KEY_UP:
+				bodyFrame.scroll(0, -1);
+				break;
+			case KEY_DOWN:
+				bodyFrame.scroll(0, 1);
+				break;
+			case KEY_HOME:
+				bodyFrame.scrollTo(0, 0);
+				break;
+			case KEY_END:
+				bodyFrame.end();
+				bodyFrame.scrollTo(0, bodyFrame.data_height - bodyFrame.height);
+				break;
+			default:
+				break;
+		}
+	}
+	messageFrame.clear();
+	headerFrame.clear();
+	bodyFrame.clear();
+	if(!retval.hasOwnProperty("number"))
+		messageFrame.bottom();
+	return retval;
+}
+
+getList();
+var userInput = "";
+var r = "";
+while(userInput != "Q") {
+	switch(userInput) {
+		case "T":
+			tree.close();
+			treeFrame.clear();
+			threaded = true;
+			getList();
+			break;
+		case "F":
+			tree.close();
+			treeFrame.clear();
+			threaded = false;
+			getList();
+			break;
+		case "C":
+			messageAreaSelector(4, 5, 70, 16, frame);
+			msgBase = new MsgBase(msg_area.grp_list[bbs.curgrp].sub_list[bbs.cursub].code);
+			getList();
+			break;
+		case "P":
+			frame.invalidate();
+			console.clear();
+			bbs.post_msg(msgBase.cfg.code);
+			getList();
+			frame.draw();
+		default:
+			r = tree.getcmd(userInput);
+			while(r.hasOwnProperty("number")) {
+				frame.cycle();
+				r = showMessage(r);
+			}
+			break;
+	}
+	userInput = console.inkey(5).toUpperCase();
+	if(frame.cycle())
+		console.gotoxy(80, 24);
+}
\ No newline at end of file
diff --git a/xtrn/ecreader/messageAreaSelector.js b/xtrn/ecreader/messageAreaSelector.js
new file mode 100644
index 0000000000..8f6fded1fd
--- /dev/null
+++ b/xtrn/ecreader/messageAreaSelector.js
@@ -0,0 +1,28 @@
+// Lightbar message area selector for Synchronet 3.16+
+// echicken -at- bbs.electronicchicken.com
+
+load(js.exec_dir + "msglib.js");
+
+var lbg = BG_CYAN;		// Lightbar background
+var lfg = WHITE;		// Foreground colour of highlighted text
+var nfg = LIGHTGRAY;	// Foreground colour of non-highlighted text
+var fbg = BG_BLUE;		// Title, Header, Help frame background colour
+var xfg = LIGHTCYAN;	// Subtree expansion indicator colour
+var tfg = LIGHTCYAN;	// Uh, that line beside subtree items
+var hfg = "\1h\1w"; 	// Heading text (CTRL-A format, for now)
+var sffg = "\1h\1c";	// Heading sub-field text (CTRL-A format, for now)
+var mfg = "\1n\1w";		// Message text colour (CTRL-A format, for now)
+
+if(argc < 4) {
+	x = 1;
+	y = 1;
+	width = console.screen_columns;
+	height = console.screen_rows;
+} else {
+	x = argv[0];
+	y = argv[1];
+	width = argv[2];
+	height = argv[3];
+}
+
+messageAreaSelector(x, y, width, height);
\ No newline at end of file
diff --git a/xtrn/ecreader/msglib.js b/xtrn/ecreader/msglib.js
new file mode 100644
index 0000000000..60410464f9
--- /dev/null
+++ b/xtrn/ecreader/msglib.js
@@ -0,0 +1,75 @@
+// Functions for ecReader and messageAreaSelector
+// echicken -at- bbs.electronicchicken.com
+
+load("sbbsdefs.js");
+load("frame.js");
+load("tree.js");
+
+function setSub(groupNumber, groupCode, subNumber, subCode) {
+	user.cursub = subCode;
+	bbs.curgrp = groupNumber;
+	bbs.cursub = subNumber;
+	bbs.cursub_code = subCode;
+	return "EXIT";
+}
+
+function messageAreaSelector(x, y, width, height, parentFrame) {
+	if(lbg === undefined)
+		var lbg = BG_CYAN;		// Lightbar background
+	if(lfg === undefined)
+		var lfg = WHITE;		// Foreground colour of highlighted text
+	if(nfg === undefined)
+		var nfg = LIGHTGRAY;	// Foreground colour of non-highlighted text
+	if(fbg === undefined)
+		var fbg = BG_BLUE;		// Title, Header, Help frame background colour
+	if(xfg === undefined)
+		var xfg = LIGHTCYAN;	// Subtree expansion indicator colour
+	if(tfg === undefined)
+		var tfg = LIGHTCYAN;	// Uh, that line beside subtree items
+	if(hfg === undefined)
+		var hfg = "\1h\1w"; 	// Heading text (CTRL-A format, for now)
+	if(sffg === undefined)
+		var sffg = "\1h\1c";	// Heading sub-field text (CTRL-A format, for now)
+	if(mfg === undefined)
+		var mfg = "\1n\1w";		// Message text colour (CTRL-A format, for now)
+
+	var selectorFrame = new Frame(x, y, width, height, fbg|WHITE, parentFrame);
+	var selectorSubFrame = new Frame(x + 2, y + 1, width - 4, height - 2, BG_BLACK|WHITE, selectorFrame);
+	var selectorTreeFrame = new Frame(x + 2, y + 2, width - 4, height - 3, BG_BLACK|WHITE, selectorSubFrame);
+	var selectorTree = new Tree(selectorTreeFrame);
+	selectorTree.colors.lbg = lbg;
+	selectorTree.colors.lfg = lfg;
+	selectorTree.colors.fg = nfg;
+	selectorTree.colors.xfg = xfg;
+	selectorTree.colors.tfg = tfg;
+	
+	for(var g = 0; g < msg_area.grp_list.length; g++) {
+		if(!user.compare_ars(msg_area.grp_list[g].ars))
+			continue;
+		var groupTree = selectorTree.addTree(g + ") " + msg_area.grp_list[g].name);
+		for(var s = 0; s < msg_area.grp_list[g].sub_list.length; s++) {
+			if(!user.compare_ars(msg_area.grp_list[g].sub_list[s].ars))
+				continue;
+			groupTree.addItem(s + ") " + msg_area.grp_list[g].sub_list[s].name, setSub, g, msg_area.grp_list[g].code, s, msg_area.grp_list[g].sub_list[s].code);
+		}
+	}
+	
+	selectorFrame.open();
+	selectorTree.open();
+	selectorSubFrame.putmsg("Select a message area: (Q to quit)");
+	selectorFrame.cycle();
+	selectorTree.cycle();
+	
+	var userInput;
+	var ret;
+	while(ret != "EXIT") {
+		userInput = console.getkey().toUpperCase();
+		if(userInput == "Q")
+			break;
+		ret = selectorTree.getcmd(userInput);
+		if(selectorFrame.cycle())
+			console.gotoxy(console.screen_columns, console.screen_rows);
+	}
+	selectorTree.close();
+	selectorFrame.close();
+}
\ No newline at end of file
diff --git a/xtrn/ecreader/readme.txt b/xtrn/ecreader/readme.txt
new file mode 100644
index 0000000000..f10272a33d
--- /dev/null
+++ b/xtrn/ecreader/readme.txt
@@ -0,0 +1,39 @@
+ecReader for Synchronet 3.16+
+echicken -at- bbs.electronicchicken.com, 2012
+
+A lightbar message browser/reader/poster for Synchronet BBS, with threaded
+sub-board views and many other features.
+
+Disclaimer:
+-----------
+This script works fine on my system, but it can consume a fair bit of memory.
+Always keep your BBS backed up just in case something happens, like some
+crazy message reader causing your system to slow down and crash, pooping on
+all of your files when it does.
+
+Requirements:
+-------------
+An up-to-date Synchronet installation, with recent copies of:
+exec/load/frame.js
+exec/load/tree.js
+exec/load/msgutils.js
+
+Installation:
+-------------
+Installation will depend on your command shell.  Some understanding of how to
+edit a command shell and add or modify a hotkey triggered event is required.
+
+To launch ecReader from a Javascript command shell:
+
+bbs.exec("?/sbbs/xtrn/ecreader/ecReader.js");
+
+Support:
+--------
+email: echicken -at- bbs.electronicchicken.com
+  irc: #synchronet on irc.synchro.net
+  
+To Do:
+------
+- Update scan pointers when new messages are read
+- New message scan
+- Allow browsing of the 'mail' sub-board
\ No newline at end of file
-- 
GitLab