diff --git a/exec/load/msgutils.js b/exec/load/msgutils.js
index d7f5702e6944de3ae289f8a11eaf3e666f221035..0e77f5cb03e461b16a60a0ef1e3bdfe20b8682b3 100644
--- a/exec/load/msgutils.js
+++ b/exec/load/msgutils.js
@@ -336,3 +336,101 @@ function expand_body(body, sys_misc, mode)
 
 	return(body);
 }
+
+/*	- echicken's getMessageThreads() function -
+
+	Usage: var threads = getMessageThreads(sub);
+	(Where 'sub' is a sub-board internal code)
+	threads.order	- Array of references to properties of threads.thread
+	threads.thread	- Object
+	threads.thread[x].newest	- Date of most recent message in this thread
+	threads.thread[x].messages	- Array of headers of messages in this thread
+	
+	Iterate over threads from oldest to newest:
+	for(var t in threads.thread) {
+		for(var m in threads.thread[t].messages) {
+			// Do stuff
+		}
+	}
+	
+	Iterate over threads from most recently to least recently updated:
+	for(var t in threads.order) {
+		for(var m in threads.thread[threads.order[t]].messages) {
+			// Do stuff
+		}
+	}
+*/
+function getMessageThreads(sub) {
+	var threads = { thread : {}, dates : [], order : [] };
+	var threadedMessages = [];
+	var subjects = {};
+	var header;
+	var tbHeader;
+	var md5subject;
+	var msgBase = new MsgBase(sub);
+	msgBase.open();
+	for(var m = msgBase.first_msg; m <= msgBase.last_msg; m++) {
+		if(threadedMessages.indexOf(m) >= 0)
+			continue;
+		header = msgBase.get_msg_header(m);
+		if(header === null || header.attr&MSG_DELETE)
+			continue;
+		md5subject = md5_calc(header.subject.toUpperCase().replace(/^\s*|\s*RE:|^\s*|\s*$/g, ''), hex=true);
+		if(header.thread_back !== null && threadedMessages.indexOf(header.thread_back) >= 0) {
+			if(threads.thread.hasOwnProperty(header.thread_back)) {
+				// This is a reply to the first message in a thread
+				threads.thread[header.thread_back].newest = header.when_written_time;
+				threads.dates[threads.thread[header.thread_back].dateIndex] = header.when_written_time;
+				threads.thread[header.thread_back].messages.push(header);
+				threadedMessages.push(m);
+			} else {
+				tbHeader = msgBase.get_msg_header(header.thread_back);
+				if(tbHeader !== null) {
+					// Heh - yeah, this part still sucks
+					outer:
+					for(var t in threads.thread) {
+						for(var mm in threads.thread[t].messages) {
+							if(threads.thread[t].messages[mm].number != tbHeader.number)
+								continue;
+							threads.thread[t].newest = header.when_written_time;
+							threads.dates[threads.thread[t].dateIndex] = header.when_written_time;
+							threads.thread[t].messages.push(header);
+							threadedMessages.push(m);
+							break outer;
+						}
+					}
+				}
+			}			
+		} else if(subjects.hasOwnProperty(md5subject)) {
+			// Attempt to match an existing thread based on the subject line
+			threads.thread[subjects[md5subject]].newest = header.when_written_time;
+			threads.dates[threads.thread[subjects[md5subject]].dateIndex] = header.when_written_time;
+			threads.thread[subjects[md5subject]].messages.push(header);
+			threadedMessages.push(m);
+		}
+		if(threadedMessages.indexOf(m) >= 0)
+			continue;
+		
+		// This is a new thread
+		threads.dates.push(header.when_written_time);
+		threads.thread[m] = {
+			newest : header.when_written_time,
+			dateIndex : threads.dates.length - 1,
+			messages : [header]
+		}
+		subjects[md5subject] = m;
+		threadedMessages.push(m);
+	}
+	msgBase.close();
+	
+	threads.dates.sort(function (a,b) {return b - a});
+	for(var d = 0; d < threads.dates.length; d++) {
+		for(var t in threads.thread) {
+			if(threads.thread[t].newest != threads.dates[d])
+				continue;
+			threads.order.push(t);
+			break;
+		}
+	}
+	return threads;
+}
\ No newline at end of file