diff --git a/exec/msglist.js b/exec/msglist.js new file mode 100644 index 0000000000000000000000000000000000000000..6fb802d68ade2007df380934ac8ed44d578c84f8 --- /dev/null +++ b/exec/msglist.js @@ -0,0 +1,1450 @@ +// $Id$ +// vi: tabstop=4 + +// Message Listing Module + +/* To install manually into a Baja command shell (*.src) file, + e.g. at the main or message menu, add a command key handler, like so: + + cmdkey L + exec_bin "msglist" + end_cmd + + ... and then recompile with baja (e.g. "baja default.src"). + */ + +/* To install as mail reading module and message list module, run: + jsexec msglist -install + */ + +"use strict"; + +function install() +{ + var cnflib = load({}, "cnflib.js"); + var main_cnf = cnflib.read("main.cnf"); + if(!main_cnf) + return "Failed to read main.cnf"; + main_cnf.readmail_mod = "msglist mail -preview"; + main_cnf.listmsgs_mod = "msglist"; + if(!cnflib.write("main.cnf", undefined, main_cnf)) + return "Failed to write main.cnf"; + file_touch(system.ctrl_dir + "recycle"); + return true; +} + +if(argv.indexOf('-install') >= 0) +{ + var result = install(); + if(result !== true) + alert(result); + exit(result === true ? 0 : 1); +} + +require('sbbsdefs.js', 'LEN_ALIAS'); +require("utf8_cp437.js", 'utf8_cp437'); +require("file_size.js", 'file_size_str'); +require("html2asc.js", 'html2asc'); +load('822header.js'); +var hexdump = load('hexdump_lib.js'); +var mimehdr = load('mimehdr.js'); + +const age = load('age.js'); + +var color_cfg = { + selection: BG_BLUE, + column: [ WHITE ], + sorted: BG_RED, + preview_separator_active: LIGHTGRAY, + preview_separator_inactive: LIGHTCYAN, + preview_active: LIGHTCYAN, + preview_inactive: LIGHTGRAY, +}; + +String.prototype.capitalize = function(){ + return this.replace( /(^|\s)([a-z])/g , function(m,p1,p2){ return p1+p2.toUpperCase(); } ); +}; + +function columnHeading(name) +{ + var heading_option = 'heading_' + name; + if(options[heading_option]) + return options[heading_option]; + switch(name) { + case 'date_time': return "Date Time"; + case 'from_net_addr': return "Net Address"; + case 'id': return "Message-ID"; + case 'ftn_pid': return "Program-ID"; + case 'ftn_msgid': return "FidoNet Message-ID"; + case 'ftn_reply': return "FidoNet Reply-ID"; + case 'ftn_tid': return "FidoNet Tosser-ID"; + case 'priority': return "Prio"; + } + return name.replace(/_/g, ' ').capitalize(); +} + +/* Supported list formats */ +var list_format = 0; +var list_formats = []; + +const sub_list_formats = [ + [ "to", "subject" ], +]; + +const local_sub_list_formats = [ + [ "attr", "score", "age", "date_time"], +]; + +const net_sub_list_formats = [ + [ "from_net_addr", "date_time", "zone"], + [ "attr", "score", "subject" ], + [ "id" ], + [ "ftn_pid" ], + [ "received", "age" ], + [ "tags" ], +]; + +const fido_sub_list_formats = [ + [ "ftn_msgid" ], + [ "ftn_reply" ], + [ "ftn_tid" ], +]; + +const mail_sent_list_formats = [ + [ "attributes", "to_list" ] +]; + +const mail_list_formats = [ + [ "attributes", "subject" ], + [ "priority", "date_time", "zone" ], + [ "to_list" , "to_ext"], + [ "cc_list", "to_net_addr" ], + [ "from_net_addr" ], + [ "received" ], + [ "replyto_net_addr" ], +]; + +const extra_list_formats = [ + [ "size", "text_length", "lines" ], +]; + +function max_len(name) +{ + switch(name /*.toLowerCase().replace(/\s+/g, '') */) { + case 'spam': + case 'read': + return 5; + case 'age': + case 'attr': + return 8; + case 'attributes': + return 16; + case 'date': + return 10; + case 'data_length': + case 'text_length': + return 11; + case 'from': + case 'to': + return 25; + case 'date_time': + return 24; // Date and time + case 'zone': + return 10; + case 'received': + return 34; // Date, time and zone + case 'score': + return 5; + case 'from_net_addr': + return 15; // 333:6404/1384.1253 + case 'priority': + return 4; + case 'size': + return 8; + } + return 39; +} + +var cmd_prompt_fmt = "\x01n\x01c\xfe \x01h%s \x01n\x01c\xfe "; +if(js.global.console==undefined || !console.term_supports(USER_ANSI)) + cmd_prompt_fmt = "%s: "; + +function cp437(msg, prop) +{ + if(!msg[prop]) + return null; + var xlatprop = prop + '_cp437'; + if(msg[xlatprop]) + return msg[xlatprop]; + if((msg.auxattr&MSG_HFIELDS_UTF8) /* && !console.term_supports(USER_UTF8) */) + msg[xlatprop] = utf8_decode(msg[prop]); +// log('decoding ' + prop + ' : ' + msg[prop]); + if(msg[xlatprop] == undefined) + msg[xlatprop] = mimehdr.to_cp437(msg[prop]); + return msg[xlatprop]; +} + +function property_value(msg, prop) +{ + var val; + + switch(prop) { + case 'to_list': + return cp437(msg, prop) || msg.to; + case 'attr': + return msg_attributes(msg, msgbase, /* short: */true); + case 'spam': + if(msg.attr & MSG_SPAM) + val = "SPAM"; + break; + case 'age': + return age.string(msg.when_written_time - (msg.when_written_zone_offset * 60) + , /* adjust_for_zone: */true); + case 'date': + return options.date_fmt ? strftime(options.date_fmt, msg.when_written_time) + : system.datestr(msg.when_written_time); + case 'date_time': + return options.date_time_fmt ? strftime(options.date_time_fmt, msg.when_written_time) + : system.timestr(msg.when_written_time); + case 'zone': + return system.zonestr(msg.when_written_zone); + case 'received': + val = options.date_time_fmt ? strftime(options.date_time_fmt, msg.when_imported_time) + : system.timestr(msg.when_imported_time); + return format("%s %s", val, system.zonestr(msg.when_imported_zone)); + case 'id': + if(msg.id) { + val = msg.id.match(/[^<>]+/); + } + break; + case 'size': + return file_size_str(msg.data_length); + case 'from': + case 'subject': + return cp437(msg, prop); + default: + val = msg[prop]; + break; + } + if(val == undefined) + return ''; + return val; +} + +function property_sort_value(msg, prop) +{ + prop = prop; //.toLowerCase().replace(/\s+/g, ''); + switch(prop) { + case 'attr': + return msg.attr; + case 'age': + return age.seconds(msg.when_written_time - (msg.when_written_zone_offset * 60) + , /* adjust_for_zone: */true); + break; + case 'date': + case 'date_time': + return msg.when_written_time; + case 'zone': + return msg.when_written_zone_offset; + case 'received': + return msg.when_imported_time; + case 'to_ext': + return parseInt(msg.to_ext, 10); + case 'priority': + return msg.priority ? msg.priority : SMB_PRIORITY_NORMAL; + case 'size': + return msg.data_length; + } + return property_value(msg, prop); +} + +var sort_property = 'num'; +var sort_reversed = false; +function sort_compare(a, b) +{ + var val1=""; + var val2=""; + + if(sort_reversed) { + var tmp = a; + a = b; + b = tmp; + } + + val1 = property_sort_value(a, sort_property); + val2 = property_sort_value(b, sort_property); + + if(typeof(val1) == "string") + val1=val1.toLowerCase(); + if(typeof(val2) == "string") + val2=val2.toLowerCase(); + else { /* Sort numbers backwards on purpose */ + var tmp = val1; + val1 = val2; + val2 = tmp; + } + if(val1>val2) return 1; + if(val1<val2) return -1; + return 0; +} + +function console_color(arg, selected) +{ + if(selected) + arg |= BG_BLUE|BG_HIGH; + if(js.global.console != undefined) + console.attributes = arg; +} + +function console_beep() +{ + if(js.global.console != undefined && options.beep !== false) + console.beep(); +} + +function help() +{ + console.clear(); + bbs.menu("msglist"); + console.pause(); +} + +function list_msg(msg, digits, selected, sort, msg_ctrl, exclude) +{ + var color = color_cfg.column[0]; + var color_mask = msg_ctrl ? 7 : 0xff; + + console_color(color&color_mask, selected); + printf("%-*u%c", digits, msg.num, msg.flagged ? 251 : ' '); + color = LIGHTMAGENTA; + if(color_cfg.column[1] != undefined) + color = color_cfg.column[1]; + if(sort == "from") + color |= color_cfg.sorted; + color &= color_mask; + console_color(color++, selected); + printf("%-*.*s%c", LEN_ALIAS, LEN_ALIAS, property_value(msg, 'from'), selected ? '<' : ' '); + + if(!js.global.console || console.screen_columns >= 80) { + for(var i = 0; i < list_formats[list_format].length; i++) { + var prop = list_formats[list_format][i]; + if(exclude.indexOf(prop) >= 0) + continue; + var heading = columnHeading(prop); + var fmt = "%-*.*s"; + var len = Math.max(heading.length, max_len(prop)); + if(i > 0) + len++; + var last_column = (i == list_formats[list_format].length - 1); + if(js.global.console) { + if(i > 0) { + if(last_column) + fmt = "%*.*s"; + else + console.print(" "); + } + var cols_remain = console.screen_columns - console.current_column - 1; + if(last_column) + len = cols_remain; + else + len = Math.min(len, cols_remain); + } + if(color > WHITE) + color = DARKGRAY; + var custom_color = color_cfg.column[i+2]; + if(custom_color != undefined) + color = custom_color; + if(prop == sort) + color |= color_cfg.sorted; + color &= color_mask; + console_color(color++, selected); + if(typeof msg[prop] == 'number') // Right-justify numbers + fmt = "%*.*s"; + printf(fmt, len, len, property_value(msg, prop)); + } + } +} + +function view_msg(msg, lines, total_msgs, grp_name, sub_name) +{ + var show_hdr = true; + var line_num = 0; + var hdr_len; +// console.clear(); + msg.lines = lines.length; + + while(!js.terminated) { + if(show_hdr) { + console.home(); +// console.status |= CON_CR_CLREOL; + bbs.show_msg_header(msg + , property_value(msg, 'subject') + , property_value(msg, 'from') + , msg.forward_path || msg.to_list || msg.to); +// console.status &= ~CON_CR_CLREOL; + hdr_len = console.line_counter; + if(console.term_supports(USER_ANSI)) + show_hdr = false; + } else + console.gotoxy(1, hdr_len + 1); + var pagesize = console.screen_rows - (hdr_len + 2); + if(line_num + pagesize >= lines.length) + line_num = lines.length - pagesize; + else if(lines.length > pagesize && line_num >= lines.length - pagesize) + line_num = lines.length - (pagesize + 1); + if(line_num < 0) + line_num = 0; + var i = line_num; + var row = hdr_len; + while(row < (console.screen_rows - 2)) { + console.line_counter = 0; + if(i < lines.length) + console.putmsg(lines[i++].trimRight(), msg.is_utf8 ? P_UTF8 : 0); + console.cleartoeol(); + console.crlf(); + row++; + } +// if(line_num == 0) +// bbs.download_msg_attachments(msg); + var line_range = "no content"; + if(lines && lines.length) { + line_range = format(options.view_lines_fmt || "%slines %u-%u" + ,content_description(msg), line_num + 1, i); + line_range += format(options.view_total_lines_fmt || " of %u", lines.length); + } + right_justify(format(options.view_line_range_fmt || "\x01n\x01h\x01k[%s]", line_range)); + console.crlf(); + console.print(format("\x01n\x01c\%s \x01h\x01bReading \x01n\x01c\%s %s \x01h%s " + + "\x01n\x01c(\x01h?\x01n\x01c=Menu) (\x01h%u\x01n\x01c of \x01h%u\x01n\x01c): \x01n\x01>" + , line_num ? "\x18" : "\xfe" + , line_num + pagesize < lines.length ? "\x19" : "\xfe" + , grp_name + , sub_name + , msg.num + , total_msgs)); + // Only message text nav keys are handled here + var key = console.getkeys(total_msgs, K_UPPER|K_NOCRLF); + switch(key) { + case KEY_DOWN: + if(i < lines.length) + line_num++; + break; + case KEY_UP: + if(line_num) + line_num--; + break; + case KEY_HOME: + line_num = 0; + break; + case KEY_END: + line_num = lines.length; + break; + case KEY_PAGEUP: + line_num -= pagesize; + break; + case KEY_PAGEDN: + case ' ': + line_num += pagesize; + break; + /* TODO: support F5? */ + case ascii(ctrl('R')): /* refresh */ + show_hdr = true; + break; + default: + return key; + } + } +} + +function right_justify(text) +{ + console.print(format("%*s%s", console.screen_columns - console.current_column - (console.strlen(text) + 1), "", text)); +} + +function find(msgbase, search_list, text) +{ + var new_list=[]; + var text=text.toUpperCase(); + for(var i = 0; i < search_list.length; i++) { + var msg = search_list[i]; + var msgtxt = msg.text; + if(msgtxt === undefined) + msgtxt = msgbase.get_msg_body(msg); + if(msgtxt.toUpperCase().indexOf(text) >= 0) + new_list.push(msg); + else { + for(var p in msg) { + if(typeof msg[p] != 'string') + continue; + if(msg[p].toUpperCase().indexOf(text) >= 0) { + new_list.push(msg); + break; + } + } + } + } + return new_list; +} + +function line_split(text, chop) +{ + var maxlen = console.screen_columns - 1; + text = text.split('\n'); + for(var i = 0; i < text.length; i++) { + // truncate trailing white-space and replace tabs w/spaces + text[i] = text[i].trimRight().replace(/\x09/g, ' '); + if(console.strlen(text[i]) > maxlen) { + if(chop !== true) + text.splice(i + 1, 0, text[i].substring(maxlen)); + text[i] = text[i].substring(0, maxlen); + } + } + return text; +} + +// Return an array of lines of text from the message body/tails/header fields +function get_msg_lines(msgbase, msg, source, hex, wrap, chop) +{ + if(msg.text && msg.source === source && msg.hex == hex && msg.wrapped == wrap) { + msg.lines = msg.text.length; + return msg.text; + } + const preparing_fmt = options.preparing_preview_fmt || "%25s"; + msg.hex = hex; + msg.source = (source===true && !hex); + msg.wrapped = false; + msg.html = false; + console.print(format(preparing_fmt, options.reading_message_text || "\x01[Reading message text ...")); + var text = msgbase.get_msg_body(msg + ,/* strip ctrl-a */msg.source + ,/* dot-stuffing */false + ,/* tails */true + ,/* plain-text */!msg.source); + if(!text) { + console.clearline(); + return []; + } + if(text.length <= options.large_msg_threshold) + msg.wrapped = (wrap!==false && !hex); + if(hex) { + console.print(format(preparing_fmt, options.preparing_hex_dump || "\x01[Preparing hex-dump ...")); + text = text.slice(0, options.large_msg_threshold); + text = hexdump.generate(undefined, text, /* ASCII: */true, /* offsets: */true); + } else { + if(msg.source) + text = msg.get_rfc822_header(/* force_update: */false, /* unfold: */false) + text; + else { + if(msg.is_utf8 && !console.term_supports(USER_UTF8)) { + console.print(format(preparing_fmt, options.translating_charset || "\x01[Translating charset ...")); + text = utf8_cp437(text); + } + if((msg.text_subtype && msg.text_subtype.toLowerCase() == 'html') + || (msg.content_type && msg.content_type.toLowerCase().indexOf("text/html") == 0)) { + console.print(format(preparing_fmt, options.decoding_html || "\x01[Decoding HTML ...")); + text = html2asc(text); + msg.html = true; + // remove excessive blank lines after HTML-translation + text = text.replace(/\r\n\r\n\r\n/g, '\r\n\r\n'); + } + } + text = text.replace(/\xff/g, ' '); // Use a regular old space for nbsp + if(msg.wrapped) { + console.print(format(preparing_fmt, options.wrapping_lines || "\x01[Wrapping lines ...")); + text = word_wrap(text, console.screen_columns - 1, (msg.columns || 80) - 1).split('\n'); + } else { + console.print(format(preparing_fmt, options.splitting_lines || "\x01[Splitting lines ...")); + text = line_split(text, chop); + } + while(text.length && !text[0].trim().length) + text.shift(); // Remove initial blank lines + while(text.length && !text[text.length - 1].trim()) + text.pop(); // Remove trailing blank lines + } + msg.lines = text.length; + if(options.cache_msg_text && text.length < options.large_msg_threshold) + msg.text = text; + console.clearline(); + return text; +} + +function next_msg(list, current, prop) +{ + var next = current + 1; + while(next < list.length) { + if(list[next][prop].toLowerCase() == list[current][prop].toLowerCase()) + break; + next++; + } + if(next < list.length) + return next; + console_beep(); + return current; +} + +function prev_msg(list, current, prop) +{ + var prev = current - 1; + while(prev >= 0) { + if(list[prev][prop].toLowerCase() == list[current][prop].toLowerCase()) + break; + prev--; + } + if(prev >= 0) + return prev; + console_beep(); + return current; +} + +function mail_reply(msg) +{ + console.clear(); + var success = false; + if(msg.from_net_type || msg.replyto_net_type) { + var addr; + if(msg.replyto_net_type) + addr = msg.replyto_net_addr; + else if(msg.from_net_addr.indexOf('@') < 0) + addr = msg.from + '@' + msg.from_net_addr; + else + addr = msg.from_net_addr; + write(bbs.text(EnterNetMailAddress)); + addr = console.getstr(addr, 128, K_EDIT|K_AUTODEL|K_LINE); + if(!addr || console.aborted) + return; + success = bbs.netmail(addr, msg); + } else if(msg.from_ext) + success = bbs.email(parseInt(msg.from_ext, 10), msg); + if(!success) { + alert("Failed to send"); + console.pause(); + } +} + +function download_msg_source(msg) +{ + var fname = system.temp_dir + "msg_" + msg.number + ".txt"; + var f = new File(fname); + if(!f.open("w")) + return false; + var text = msgbase.get_msg_body(msg + ,/* strip ctrl-a */false + ,/* dot-stuffing */false + ,/* tails */true + ,/* plain-text */false); + f.write(msg.get_rfc822_header(/* force_update: */false, /* unfold: */false)); + f.writeln(text); + f.close(); + return bbs.send_file(fname); +} + +function content_description(msg) +{ + var desc = []; + if(msg.hex) + desc.push('hex-dumpped'); + else { + if(msg.wrapped) + desc.push('wrapped'); + if(msg.source) + desc.push('source'); + else { + if(msg.text_charset) + desc.push(msg.text_charset.toUpperCase()); + else if(msg.is_utf8) + desc.push("UTF-8"); + if(msg.html) + desc.push('HTML'); + } + } + var retval = desc.join(' '); + if(retval) retval += ' '; + return retval; +} + +function remove_extra_blank_lines(list) +{ + for(var i = 0; i < list.length; i++) { + if(truncsp(list[i]).length < 1 + && list[i + 1] !== undefined + && truncsp(list[i + 1]).length < 1) + list.splice(i + 1, 1); + } +} + +function list_msgs(msgbase, list, current, preview, grp_name, sub_name) +{ + var mail = (msgbase.attributes & SMB_EMAIL); + if(!current) + current = 0; + else { + for(var i = 0; i <= list.length; i++) { + if(list[i] && list[i].number == current) { + current = i; + break; + } + } + } + var top = current; + var sort; + var reversed = false; + var orglist = list.slice(); + var digits = format("%u", list.length).length; + if(!preview) + preview = false; + var msg_line = 0; + var msg_ctrl = false; + var spam_visible = true; + var view_hex = false; + var view_wrapped = true; + var view_source = false; + var area_name = format(options.area_name_fmt || "\x01n\x01k\x017%s / %s\x01h" + ,grp_name, sub_name); + + console.line_counter = 0; + while(!js.terminated) { + var pagesize = console.screen_rows - 3; + if(preview) + pagesize = Math.floor(pagesize / 2); + console.home(); + console.current_column = 0; + console.print(area_name); + + /* Bounds checking: */ + if(current < 0) { + console_beep(); + current = 0; + } else if(current >= list.length) { + console_beep(); + current = list.length-1; + } + + if(console.screen_columns >= 80) + right_justify(format(options.area_nums_fmt || "[message %u of %u]" + , current + 1, list.length)); + console.cleartoeol(); + console.crlf(); + if(list_format >= list_formats.length) + list_format = 0; + else if(list_format < 0) + list_format = list_formats.length-1; + + var exclude_heading = []; + if(!spam_visible) + exclude_heading.push("spam"); + + /* Column headings */ + printf("%-*s %-*s ", digits, "#", LEN_ALIAS, sort=="from" ? "FROM" : "From"); + for(var i in list_formats[list_format]) { + var prop = list_formats[list_format][i]; + if(exclude_heading.indexOf(prop) >= 0) + continue; + var fmt = "%-*.*s"; + var heading = columnHeading(prop); + var last_column = (i == (list_formats[list_format].length - 1)); + if(last_column) { + if(i > 0) + fmt = "%*.*s"; + } else { + if(i > 0 && max_len(prop) >= heading.length) + console.print(" "); + } + var cols_remain = console.screen_columns - console.current_column - 1; + var len = cols_remain; + if(!last_column) { + len = Math.min(max_len(prop), cols_remain); + len = Math.max(heading.length, len); + } + printf(fmt + ,len + ,len + ,sort==prop ? heading.toUpperCase() : heading); + } + console.cleartoeol(); + console.crlf(); + + if(top > current) + top = current; + if(top && (top + pagesize) > list.length) + top = list.length-pagesize; + if(top + pagesize <= current) + top = (current+1) - pagesize; + if(top < 0) + top = 0; + + for(var i = top; i - top < pagesize; i++) { + console.line_counter = 0; + console.attributes = LIGHTGRAY; + if(list[i] !== undefined && list[i] !== null) { + list_msg(list[i], digits, i == current, sort, msg_ctrl, exclude_heading); + } + console.cleartoeol(); + console.crlf(); + } + if(preview) { + var msg = list[current]; + var text = get_msg_lines(msgbase, msg); + remove_extra_blank_lines(text); + var default_separator = "\xc4"; + console.attributes = msg_ctrl ? color_cfg.preview_separator_active : color_cfg.preview_separator_inactive; + while(console.current_column < digits - 1) + write(options.preview_separator || default_separator); + write(options.preview_label || "\xd9 Preview"); + var offset = pagesize + 4; + if(text) { + if(text.length) { + if(msg_line < 0 || !msg_ctrl) + msg_line = 0; + else { + var max = Math.max(text.length - 1, text.length - offset); + if(msg_line > max) + msg_line = max; + } + // log(format("text lines(%s): %d\n", typeof text, text.length)); + var max = Math.min(console.screen_rows - offset, text.length); + if(msg_line + max > text.length) + msg_line = text.length - max; + write(format(options.preview_lines_fmt || " lines %u-%u" + , msg_line + 1, msg_line + max)); + if(max < text.length) + write(format(options.preview_total_lines_fmt || " of %u", text.length)); + } + write(options.preview_label_terminator || " \xc0"); + if(options.preview_properties) { + var array = options.preview_properties.split(','); + var propval = []; + for(var i in array) { + if(options.hide_redundant_properties !== false + && list_formats[list_format].indexOf(array[i]) >= 0) + continue; + var val = property_value(msg, array[i]); + if(val) + propval.push(format("%.*s", options.preivew_properties_maxlen || 10, val)); + } + propval = propval.join(options.preview_properties_separator || ', ').trim(); + if(propval.length) { + while(console.current_column < console.screen_columns - (4 + propval.length + digits)) + write(options.preview_separator || default_separator); + write(format(options.preview_properties_fmt || "\xd9 %s \xc0", propval)); + } + } + while(console.current_column < console.screen_columns - 1) + write(options.preview_separator || default_separator); + } else { + write("!Error getting msg text: " + msgbase.error); + msg_line = 0; + } + console.cleartoeol(); + console.crlf(); + console.attributes = msg_ctrl ? color_cfg.preview_active : color_cfg.preview_inactive; + for(var i = offset; i < console.screen_rows; i++) { + if(text !== null && text[msg_line + (i - offset)]) + console.write(strip_ctrl(text[msg_line + (i - offset)])); + console.cleartoeol(); + console.crlf(); + } + } + + var cmds = []; + if(!preview) + cmds.push("~Preview"); + if(!(list[current].attr&MSG_NOREPLY)) { + if(mail) + cmds.push("~Reply"); + else + cmds.push("~Reply/~Mail"); + } + if(list[current].auxattr&(MSG_FILEATTACH|MSG_MIMEATTACH)) + cmds.push("~Dload"); + cmds.push("~Goto"); + cmds.push("~Find"); + var fmt = ", fmt:0-%u" + if(console.term_supports(USER_ANSI)) + fmt += ", Hm/End/\x18\x19/PgUpDn"; + fmt += ", ~Quit or [View] ~?"; + console.mnemonics(cmds.join(", ") + format(fmt, list_formats.length-1)); + console.cleartoeol(); + bbs.nodesync(/* clearline: */false); + var key = console.getkey(); + switch(key.toUpperCase()) { + case ctrl('A'): + var flagged = true; + if(list[0] && list[0].flagged) + flagged = false; + for(var i in list) + list[i].flagged = flagged; + break; + case KEY_DEL: + if(msgbase.cfg && !msg_area.sub[msgbase.cfg.code].is_operator) + break; + flagged = 0; + for(var i in list) + if(list[i].flagged) { + list[i].attr ^= MSG_DELETE; + list[i].attributes = msg_attributes(list[i], msgbase); + flagged++; + } + if(!flagged) { + list[current].attr ^= MSG_DELETE; + list[current].attributes = msg_attributes(list[current], msgbase); + current++; + } + break; + case 'V': + case '\r': + console.clear(); + var viewed_msg = current; + while(!js.terminated && list[current] + && (key = view_msg(list[current] + ,get_msg_lines(msgbase, list[current], view_source, view_hex, view_wrapped) + ,list.length + ,grp_name, sub_name + )) != 'Q') { + switch(key) { + case '?': + console.clear(); + bbs.menu("msgview"); + console.pause(); + break; + case '\r': + case '\n': + current++; + break; + case '\b': + case '-': + current--; + break; + case KEY_DEL: + if(msgbase.cfg && !msg_area.sub[msgbase.cfg.code].is_operator) + break; + list[current].attr ^= MSG_DELETE; + list[current].attributes = msg_attributes(list[current], msgbase); + if(!update_msg_attr(msgbase, list[current])) + alert("Delete failed"); + break; + case KEY_LEFT: + if(!list[current].columns) + list[current].columns = 79; + else + list[current].columns--; + break; + case KEY_RIGHT: + if(!list[current].columns) + list[current].columns = 81; + else + list[current].columns++; + break; + case '>': + current = next_msg(list, current, 'subject'); + break; + case '<': + current = prev_msg(list, current, 'subject'); + break; + case '}': + current = next_msg(list, current, 'from'); + break; + case '{': + current = prev_msg(list, current, 'from'); + break; + case ']': + current = next_msg(list, current, 'to'); + break; + case '[': + current = prev_msg(list, current, 'to'); + break; + case 'A': + case 'R': + if(mail) + mail_reply(list[current]); + else { + console.clear(); + bbs.post_msg(msgbase.subnum, list[current]); + } + break; + case 'M': + mail_reply(list[current]); + break; + case 'S': + view_source = !view_source; + view_hex = false; + list[current].text = null; + break; + case 'H': + view_hex = !view_hex; + list[current].text = null; + break; + case 'W': + view_wrapped = !view_wrapped; + list[current].text = null; + break; + default: + if(typeof key == "number") { + current = key -1; + break; + } + break; + } + if(viewed_msg != current) { + console.clear(); + viewed_msg = current; + } + } + + if(list[current]) { + var msg = list[current]; + if(mail && msg.to_ext == user.number) { + msg.attr |= MSG_READ; + if(update_msg_attr(msgbase, msg)) + msg.attributes = msg_attributes(msg, msgbase); + } + } + break; + case 'D': + console.clearline(); + if(!console.noyes("Download message source", P_NOCRLF)) { + if(!download_msg_source(list[current], msgbase)) + alert("failed"); + continue; + } + console.creturn(); + bbs.download_msg_attachments(list[current]); + continue; + case 'Q': + console.clear(); + return false; + case KEY_HOME: + if(msg_ctrl) + msg_line = 0; + else + current=top=0; + continue; + case KEY_END: + if(msg_ctrl) + msg_line = 9999999; + else + current=list.length-1; + continue; + case KEY_UP: + if(msg_ctrl) + msg_line--; + else + current--; + break; + case KEY_DOWN: + if(msg_ctrl) + msg_line++; + else + current++; + break; + case ' ': + if(list[current].flagged) + list[current].flagged = false; + else + list[current].flagged = true; + if(!msg_ctrl) + current++; + break; + case KEY_PAGEDN: + case 'N': + current += pagesize; + top += pagesize; + break; + case KEY_PAGEUP: + current -= pagesize; + top -= pagesize; + break; + case 'P': + preview = !preview; + break; + case '\t': + preview = true; + msg_ctrl = !msg_ctrl; + break; + case '/': + case 'F': + { + console.clearline(); + console.print("\x01n\x01y\x01hFind: "); + var search = console.getstr(60,K_LINE|K_UPPER|K_NOCRLF); + console.clearline(LIGHTGRAY); + if(search && search.length) { + console.print("Searching \x01i...\x01n"); + var found = find(msgbase, orglist, search); + if(found.length) { + list = found; + current = 0; + } else { + console.print("\x01[Text not found: " + search); + sleep(1500); + } + } + break; + } + case 'G': + console.clearline(); + console.print("\x01n\x01y\x01hGo to Message: "); + var num = console.getstr(digits,K_LINE|K_NUMBER|K_NOCRLF); + if(num) + top = current = num - 1; + console.clearline(LIGHTGRAY); + break; + case 'A': + case 'R': + if(mail) + mail_reply(list[current]); + else { + console.clear(); + bbs.post_msg(msgbase.subnum, list[current]); + } + break; + case 'M': + mail_reply(list[current]); + break; + case 'S': + if(sort == undefined) + sort = (key == 'S') ? list_formats[list_format][list_formats[list_format].length - 1] : "from"; + else { + var sort_field = list_formats[list_format].indexOf(sort); + if(sort_field >= list_formats[list_format].length) + sort = undefined; + else if(key == 'S') { /* capital 'S', move backwards through sort fields */ + if(sort == "from") + sort = undefined; + else if(sort_field) + sort = list_formats[list_format][sort_field-1]; + else + sort = "from"; + } else { + if(sort_field < 0) + sort = list_formats[list_format][0]; + else + sort = list_formats[list_format][sort_field+1]; + } + } + if(sort == undefined) + list = orglist.slice(); + else { + sort_reversed = reversed; + sort_property = sort; + write("\x01[\x01iSorting...\x01n\x01>"); + list.sort(sort_compare); + } + break; + case 'T': + list = orglist.slice(); + spam_visible = !spam_visible; + if(!spam_visible) { + list = []; + for(i in orglist) { + if(!(orglist[i].attr & MSG_SPAM)) + list.push(orglist[i]); + } + } + break; + case '!': + list.reverse(); + reversed = !reversed; + break; + case KEY_LEFT: + list_format--; + break; + case KEY_RIGHT: + list_format++; + break; + case '?': + case 'H': + help(); + break; + default: + if(key>='0' && key <='9') + list_format = parseInt(key); + break; + } + } +} + +function msg_attributes(msg, msgbase, short) +{ + var result = []; + var str = ''; + if(msg.attr&MSG_SPAM) result.push(options.attr_spam || "Sp"), str += 'S'; + if(msg.auxattr&(MSG_FILEATTACH|MSG_MIMEATTACH)) result.push(options.attr_attach || "Att"), str += 'A'; + if(msg.attr&MSG_DELETE) result.push(options.attr_delete || "Del"), str += 'D'; + if(msgbase.cfg + && msg.number > msg_area.sub[msgbase.cfg.code].scan_ptr) + result.push(options.attr_new || "New"), str += 'N'; + if(msg.attr&MSG_REPLIED) result.push(options.attr_replied || "Re"), str += 'r'; + if(msg.attr&MSG_READ) result.push(options.attr_read ||"Rd"), str += 'R'; + if(msg.attr&MSG_PERMANENT) result.push(options.attr_perm ||"Perm"), str += 'P'; + if(msg.attr&MSG_ANONYMOUS) result.push(options.attr_anon || "Anon"), str += 'A'; + if(msg.attr&MSG_LOCKED) result.push(options.attr_locked || "Lck"), str += 'L'; + if(msg.attr&MSG_KILLREAD) result.push(options.attr_kill || "Kill"), str += 'K'; + if(msg.attr&MSG_NOREPLY) result.push(options.attr_noreply || "NoRe"), str += 'n'; + if(msg.attr&MSG_MODERATED) result.push(options.attr_mod || "Mod"), str += 'M'; + if(msg.attr&MSG_VALIDATED) result.push(options.attr_valid || "Valid"), str += 'V'; + if(msg.attr&MSG_PRIVATE) result.push(options.attr_private || "Priv"), str += 'p'; + if(msg.attr&MSG_POLL) result.push(options.attr_poll || "Poll"), str += '?'; + if(msg.netattr&MSG_SENT) result.push(options.attr_intransit || "Sent"), str += 's'; + if(msg.netattr&MSG_INTRANSIT) result.push(options.attr_intransit || "InTransit"), str += 'T'; + /* + if(sub_op(subnum) && msg->hdr.attr&MSG_ANONYMOUS) return 'A'; + */ + if(short) { + return str; + } + return result.join(options.attr_sep || ' '); +} + +// Update a message header's attributes (only) +function update_msg_attr(msgbase, msg) +{ + return true; + var hdr = msgbase.get_msg_header(msg.number, /* expand: */false); + if(hdr == null) { + alert("get_msg_header(" + msg.number +") failed"); + return false; + } + hdr.attr = msg.attr; + var result = msgbase.put_msg_header(hdr); + if(!result) + alert("put_msg_header(" + msg.number + ") failed"); + return result; +} + +// Return an array of msgs +function load_msgs(msgbase, which, mode, usernumber) +{ + var mail = (msgbase.attributes & SMB_EMAIL); + var list = []; + if(mail && which !== undefined && which != MAIL_ALL) { + var idxlist = msgbase.get_index(); + var total_msgs = idxlist.length; + for(var i = 0; i < total_msgs; i++) { + var idx = idxlist[i]; + if((idx.attr&MSG_DELETE) && !(mode&LM_INCDEL)) + continue; + if((idx.attr&MSG_SPAM)) { + if(mode&LM_NOSPAM) + continue; + } else { + if(mode&LM_SPAMONLY) + continue; + } + if((idx.attr&MSG_READ) && (mode&LM_UNREAD)) + continue; + switch(which) { + case MAIL_YOUR: + if(idx.to != usernumber) + continue; + break; + case MAIL_SENT: + if(idx.from != usernumber) + continue; + break; + case MAIL_ANY: + if(idx.to != usernumber + && idx.from != usernumber) + continue; + break; + } + list.push(msgbase.get_msg_header(/* by_offset: */true, i, /* expand_fields: */false)); + } + } else { + list = msgbase.get_all_msg_headers(/* votes: */false, /* expand_fields: */false); + } + var msgs = []; + for(var i in list) { + var msg = list[i]; + msg.attributes = msg_attributes(msg, msgbase); + msg.num = msgs.length + 1; + msg.score = 0; + if(msg.upvotes) + msg.score += msg.upvotes; + if(msg.downvotes) + msg.score -= msg.downvotes; + msgs.push(msg); + } + if(mode&LM_REVERSE) + msgs.reverse(); + return msgs; +} + +var which; +var usernumber; +var lm_mode; +var preview; +var msgbase_code; + +for(var i in argv) { + var arg = argv[i].toLowerCase(); + switch(arg) { + case '-p': + case '-preview': + preview = true; + break; + case '-nospam': + if(lm_mode === undefined) + lm_mode = 0; + lm_mode |= LM_NOSPAM; + break; + case '-spam': + if(lm_mode === undefined) + lm_mode = 0; + lm_mode |= LM_SPAMONLY; + break; + case '-unread': + if(lm_mode === undefined) + lm_mode = 0; + lm_mode |= LM_UNREAD; + break; + case '-reverse': + if(lm_mode === undefined) + lm_mode = 0; + lm_mode |= LM_REVERSE; + break; + case '-all': + which = MAIL_ALL; + break; + case '-sent': + which = MAIL_SENT; + break; + default: + if(msgbase_code === undefined) + msgbase_code = arg; + else if(which === undefined) + which = parseInt(arg, 10); + else if(usernumber === undefined) + usernumber = parseInt(arg, 10); + else if(lm_mode === undefined) + lm_mode = parseInt(arg, 10); + break; + } +} + +if(!msgbase_code) + msgbase_code = bbs.cursub_code; + +var msgbase = new MsgBase(msgbase_code); +if(!msgbase.open()) { + alert(msgbase.error); + exit(); +} + +var options=load({}, "modopts.js", "msglist:" + msgbase_code); +if(!options) + options=load({}, "modopts.js", "msglist"); +if(!options) + options = {}; +if(options.large_msg_threshold === undefined) + options.large_msg_threshold = 0x10000; +if(options.preview_properties === undefined) + options.preview_properties = "date,attributes,subject"; +if(options.date_fmt === undefined) + options.date_fmt = "%Y-%m-%d"; +// options.date_time_fmt = "%a %b %d %Y %H:%M:%S"; + +if(!msgbase.total_msgs) { + alert("No messages"); + exit(); +} + +function remove_list_format_property(name) +{ + for(var i in list_formats) { + var j = list_formats[i].indexOf(name); + if(j >= 0) { + list_formats[i].splice(j, 1); + if(!list_formats[i].length) + list_formats.splice(i, 1); + } + } +} + +var curmsg = 0; +if(msgbase.cfg) { + list_formats = sub_list_formats; + if(msgbase.cfg.settings & (SUB_FIDO | SUB_QNET | SUB_INET)) { + list_formats = list_formats.concat(net_sub_list_formats); + if(msgbase.cfg.settings & SUB_FIDO) + list_formats = list_formats.concat(fido_sub_list_formats); + } else + list_formats = list_formats.concat(local_sub_list_formats); + if(!(msgbase.cfg.settings & SUB_MSGTAGS)) + remove_list_format_property("tags"); + if(msgbase.cfg.settings & SUB_NOVOTING) + remove_list_format_property("score"); + if(!(msgbase.cfg.settings & SUB_TOUSER)) + remove_list_format_property("to"); + curmsg = msg_area.sub[msgbase.cfg.code].last_read; +} else { + if(which === undefined) + which = MAIL_YOUR; + if(which != MAIL_YOUR) + list_formats = list_formats.concat(mail_sent_list_formats); + list_formats = list_formats.concat(mail_list_formats); +} +list_formats = list_formats.concat(extra_list_formats); + +if(lm_mode === undefined) + lm_mode = 0; + +if((system.settings&SYS_SYSVDELM) && (user.is_sysop || (system.settings&SYS_USRVDELM))) + lm_mode |= LM_INCDEL; + +if(msgbase.attributes & SMB_EMAIL) { + if(isNaN(which)) + which = MAIL_YOUR; +// lm_mode |= LM_REVERSE; + if(lm_mode&(LM_NOSPAM | LM_SPAMONLY)) + remove_list_format_property("spam"); +} + +if(!usernumber) + usernumber = user.number; + +console.print("Loading messages \x01i...\x01n "); +var list = load_msgs(msgbase, which, lm_mode, usernumber); +if(!list || !list.length) { + alert("No messages"); + exit(); +} + +js.on_exit("console.status = " + console.status); +console.status |= CON_CR_CLREOL; +js.on_exit("console.ctrlkey_passthru = " + console.ctrlkey_passthru); +console.ctrlkey_passthru |= (1<<16); // Disable Ctrl-P handling in sbbs +console.ctrlkey_passthru |= (1<<26); // Disable Ctrl-Z handling in sbbs +js.on_exit("bbs.sys_status &= ~SS_MOFF"); +bbs.sys_status |= SS_MOFF; // Disable automatic messages + +var grp_name; +var sub_name = "???"; +if(msgbase.cfg) { + grp_name = msgbase.cfg.grp_name; + sub_name = msgbase.cfg.name; +} else { + grp_name = "E-mail"; + switch(which) { + case MAIL_YOUR: + sub_name = "Inbox"; + break; + case MAIL_SENT: + sub_name = "Sent"; + break; + case MAIL_ALL: + sub_name = "All"; + break; + case MAIL_ANY: + sub_name = "Any"; + break; + default: + sub_name = String(which); + break; + } +} +var result = list_msgs(msgbase, list, curmsg, preview, grp_name, sub_name); +if(result) { + if(msgbase.cfg) { + msg_area.sub[msgbase.cfg.code].last_read = result; + bbs.scan_msgs(msgbase.cfg.code); + } +}