class WTVMail { fs = require('fs'); path = require('path'); uuid = require('uuid'); ssid = null; unread_mail = 0; inbox_store = null; sent_store = null; saved_store = null; minisrv_config = []; wtvshared = null; wtvmime = null; wtvclient = null; WTVClientSessionData = null; mailstore_dir = null; mailboxes = null; msgFileExt = ".zmsg"; trashMailboxName = "Trash"; defaultColors = {}; sendmailDefaultBGColor = "#1F2033" constructor(minisrv_config, wtvclient) { if (!minisrv_config) throw ("minisrv_config required"); var WTVShared = require("./WTVShared.js")['WTVShared']; var WTVMime = require("./WTVMime.js"); this.WTVClientSessionData = require("./WTVClientSessionData.js"); this.minisrv_config = minisrv_config; this.wtvshared = new WTVShared(minisrv_config); this.wtvmime = new WTVMime(minisrv_config); this.wtvclient = wtvclient; this.ssid = this.wtvclient.ssid; this.unread_mail = this.wtvclient.getSessionData("subscriber_unread_mail") ? this.wtvclient.getSessionData("subscriber_unread_mail") : 0; this.mailboxes = [ // referenced by id, so order is important! "Inbox", "Sent", "Saved", this.trashMailboxName ]; this.defaultColors = { bgcolor: "#191919", text: "#82A9D9", link: "#BDA73A", vlink: "#62B362" }; } checkMailIntroSeen() { return (this.wtvclient.getSessionData("subscriber_mail_intro_seen")) ? this.wtvclient.getSessionData("subscriber_mail_intro_seen") : false; } setMailIntroSeen(seen) { this.wtvclient.setSessionData("subscriber_mail_intro_seen", (seen) ? true : false); } mailstoreExists() { if (!this.isguest) { if (this.mailstore_dir === null) { // set mailstore directory local var so we don't call the function every time var userstore_dir = this.wtvclient.getUserStoreDirectory(); // MailStore var store_dir = "MailStore" + this.path.sep; this.mailstore_dir = userstore_dir + store_dir; } return this.fs.existsSync(this.mailstore_dir); } return null; } getSignatureColors(signature = null, sendmail = true) { var colors = Object.assign({}, this.defaultColors); // start with default colors if (sendmail) colors.bgcolor = this.sendmailDefaultBGColor; if (signature) { if (signature.length > 0) { if (signature.indexOf('') >= 0) { if (signature.indexOf('
= 0) { // parse tag of html signature to get colors const htmlparser2 = require("htmlparser2"); const dom = htmlparser2.parseDocument(signature); const body = htmlparser2.DomUtils.getElementsByTagName('body', dom)[0]; if (body.attribs) { for (const [key, value] of Object.entries(body.attribs)) { colors[key] = value; } } } } } } if (!colors.cursor) colors.cursor = colors.link; return colors; } mailboxExists(mailboxid) { if (mailboxid >= this.mailboxes.length) return null; var mailbox_dir = null; if (this.mailstoreExists()) { var mailbox_name = this.getMailboxById(mailboxid); if (!mailbox_name) return null; var mailbox_dir = mailbox_name + this.path.sep; var store_dir = this.mailstore_dir + mailbox_dir; } return (store_dir !== null) ? this.fs.existsSync(store_dir) : false; } createMailstore() { if (this.mailstoreExists() === false) { if (!this.fs.existsSync(this.mailstore_dir)) this.fs.mkdirSync(this.mailstore_dir, { recursive: true }); return true; } return false; } getMailboxById(mailboxid) { return (mailboxid < this.mailboxes.length) ? this.mailboxes[mailboxid] : false; } getMailboxByName(mailbox_name) { var mailbox_id = false; this.mailboxes.every(function (v, k) { if (v.toLowerCase() == mailbox_name.toLowerCase()) { mailbox_id = k; return false; } return true; }); return mailbox_id; } getMailboxStoreDir(mailboxid) { if (this.mailboxExists(mailboxid)) { var mailbox_name = this.getMailboxById(mailboxid); return this.mailstore_dir + mailbox_name + this.path.sep; } return null; } createMailbox(mailboxid) { var mailbox_exists = this.mailboxExists(mailboxid); if (mailbox_exists === false) { var mailbox_name = this.getMailboxById(mailboxid); var mailbox_dir = mailbox_name + this.path.sep; var store_dir = this.mailstore_dir + mailbox_dir; if (!this.fs.existsSync(store_dir)) this.fs.mkdirSync(store_dir, { recursive: true }); return true; } return mailbox_exists; } createMessageID() { return this.uuid.v1(); } createMessage(mailboxid, from_addr, to_addr, msgbody, subject = null, from_name = null, to_name = null, signature = null, date = null, known_sender = false, attachments = [], url = null, url_title = null, allow_html = false) { if (this.createMailbox(mailboxid)) { if (!date) date = Math.floor(Date.now() / 1000); var mailbox_path = this.getMailboxStoreDir(mailboxid); var message_id = this.createMessageID(); var message_file = message_id + this.msgFileExt; var message_file_out = mailbox_path + message_file; var message_data = { "from_addr": from_addr, "from_name": from_name, "to_addr": to_addr, "to_name": to_name, "date": date, "subject": subject, "body": msgbody, "known_sender": known_sender, "signature": signature, "unread": true, "attachments": attachments, "url": url, "url_title": url_title, "allow_html": allow_html } try { if (this.fs.existsSync(message_file_out)) { console.error(" * ERROR: Message with this UUID (" + messageid + ") already exists (should never happen). Message lost."); return false; } // encode message into json var result = this.fs.writeFileSync(message_file_out, JSON.stringify(message_data)); if (!result) return false; // rely on filesystem times for sorting as it is quicker then reading every file var file_timestamp = new Date(date * 1000); fs.utimesSync(message_file, Date.now(), file_timestamp); if (!result) console.error(" WARNING: Setting timestamp on " + message_file + " failed, mail dates will be inaccurate."); } catch (e) { console.error(" # MailErr: Mail Store failed\n", e, "\n", message_file_out, "\n", message_data ,"\n"); } return false; } } createWelcomeMessage() { var welcomeTemplate = this.wtvshared.getTemplate("wtv-mail", "welcomeMail.txt").toString('ascii'); var end_of_headers = false; var msg = ""; var self = this; var to_addr = this.wtvclient.getSessionData("subscriber_username") + "@" + this.minisrv_config.config.service_name; var to_name = this.wtvclient.getSessionData("subscriber_name"); var available_tags = { ...this.minisrv_config.config, "user_address": to_addr, "user_name": to_name } var from_name, from_addr, subj = null; var lines = welcomeTemplate.replace(/\r/g, '').split("\n"); lines.forEach((line) => { if (line.indexOf(": ") > 1 && !end_of_headers) { var header = [line.slice(0, line.indexOf(':')), line.slice(line.indexOf(':') + 2).trim()]; switch (header[0].toLowerCase()) { case "from": if (header[1].indexOf("<") >= 0) { var email = header[1].match(/(.+) \<(.+)\>/); if (email) { from_name = email[1]; from_addr = email[2]; } else { var email = header[1].match(/\<(.+)\>/); from_addr = email[1]; } } else if (header[1].indexOf('@') >= 0) { from_addr = header[1]; } break; case "subject": subj = header[1]; break; } } else if (line == '') end_of_headers = true; else { msg += line.replace(/\$\{(\w{1,})\}/g, function (x) { var out = ''; var tag = x.replace("${", '').replace('}', ''); if (available_tags[tag]) out = available_tags[tag]; return out }) + "\n"; } }); return this.createMessage(0, from_addr, to_addr, msg, subj, from_name, to_name, null, null, true, [], null, null, true); } getMessage(mailboxid, messageid) { if (this.createMailbox(mailboxid)) { var mailbox_path = this.getMailboxStoreDir(mailboxid); var message_file = messageid + this.msgFileExt; var message_file_in = mailbox_path + this.path.sep + message_file; var message_data_raw = null; if (this.fs.existsSync(message_file_in)) message_data_raw = this.fs.readFileSync(message_file_in); else console.error(" # MailErr: could not find ", message_file_in); if (message_data_raw) { var message_data = JSON.parse(message_data_raw); message_data.mailbox_path = mailbox_path; message_data.message_file = message_file; if (message_data) { message_data.id = messageid; // backwards compat if (!message_data.attachments) message_data.attachments = []; return message_data; } else console.error(" # MailErr: could not parse json in ", message_file_in); } } return false; } updateMessage(message_data) { // encode message into json var message_out = new Object(); Object.assign(message_out, message_data); delete message_out.mailbox_path; delete message_out.message_file; var result = this.fs.writeFileSync(message_data.mailbox_path + this.path.sep + message_data.message_file, JSON.stringify(message_out)); if (!result) return false; // rely on filesystem times for sorting as it is quicker then reading every file var file_timestamp = new Date(message_data.date * 1000); fs.utimesSync(message_file, Date.now(), file_timestamp); if (!result) console.error(" WARNING: Setting timestamp on " + message_file + " failed, mail dates will be inaccurate."); } checkMessageIdSanity(messageid) { return /^[A-Za-z0-9\-]{36}$/.test(messageid); } listMessages(mailboxid, limit, reverse_sort = false, offset = 0) { if (this.createMailbox(mailboxid)) { var mailbox_path = this.getMailboxStoreDir(mailboxid); var self = this; var files = this.fs.readdirSync(mailbox_path) .map(function (v) { var message_data_raw = null; var message_date = null; var message_path = mailbox_path + self.path.sep + v; if (self.fs.existsSync(message_path)) message_data_raw = self.fs.readFileSync(message_path); if (message_data_raw) { var message_data = JSON.parse(message_data_raw); if (message_data) message_date = message_data.date; } var message_date_ret = (message_date) ? message_date : self.fs.statSync(mailbox_path + self.path.sep + v).mtime.getTime(); self.fs.statSync(mailbox_path + self.path.sep + v).mtime.getTime() return { name: v, time: message_date_ret }; }) .sort(function (a, b) { if (!reverse_sort) return b.time - a.time; else return a.time - b.time; }) .map(function (v) { if (v.name.substring((v.name.length - self.msgFileExt.length)) === self.msgFileExt) return v.name.substring(0, (v.name.length - 5)); }); if (files.length == 0) return false; // no messages else { // todo filter previous results when offset var messagelist_out = new Array(); Object.keys(files).forEach(function (k) { var message = self.getMessage(mailboxid, files[k]); if (message) messagelist_out.push(mailboxid, message); else console.error(" # MailErr: reading message ID: ", files[k]); }) return messagelist_out.filter(function (n) { return n; }); } } return null; // error } countMessages(mailboxid) { var messages = this.listMessages(mailboxid, 100, false); var message_count = Object.keys(messages).length; return (message_count) ? message_count : 0; } countUnreadMessages(mailboxid) { var messages = this.listMessages(mailboxid, 100, false); var unread = 0; Object.keys(messages).forEach(function (k) { if (messages[k].unread) unread++; }); return unread; } getMailboxIcon() { var icon_image = null; switch (this.countMessages(0)) { case 0: icon_image = "OpenMailbox0.gif"; break; case 1: icon_image = "OpenMailbox1.gif"; break; default: icon_image = "OpenMailbox2.gif"; break; } return icon_image; } checkUserExists(username, directory = null) { // returns the user's ssid, and user_id and userid in an array if true, false if not var search_dir = this.path.resolve(this.wtvshared.getAbsolutePath() + this.path.sep + this.minisrv_config.config.SessionStore); var return_val = false; var self = this; if (directory) search_dir = directory; this.fs.readdirSync(search_dir).forEach(file => { if (self.fs.lstatSync(search_dir + self.path.sep + file).isDirectory() && !return_val) { return_val = self.checkUserExists(username, search_dir + self.path.sep + file); } if (!file.match(/.*\.json/ig)) return; try { var temp_session_data_file = self.fs.readFileSync(search_dir + self.path.sep + file, 'Utf8'); var temp_session_data = JSON.parse(temp_session_data_file); if (temp_session_data.subscriber_username) { if (temp_session_data.subscriber_username.toLowerCase() == username.toLowerCase()) { return_val = search_dir.replace(this.minisrv_config.config.SessionStore + self.path.sep + "accounts" + self.path.sep, '').replace("user", '').split(self.path.sep); return_val.push(temp_session_data.subscriber_name); } } } catch (e) { console.error(" # Error parsing Session Data JSON", file, e); } }); return return_val; } getUserMailstore(username) { var user_data = this.checkUserExists(username); if (user_data) { var user_wtvsession = new this.WTVClientSessionData(this.minisrv_config, user_data[0]); user_wtvsession.user_id = user_data[1]; var user_mailstore = new WTVMail(this.minisrv_config, user_wtvsession) return user_mailstore; } return false; } sendMessageToAddr(from_addr, to_addr, msgbody, subject = null, from_name = null, to_name = null, signature = null, attachments = [], url = null, url_title = null) { if (!to_addr) return "Your message could not be sent.You must specify an addressee in the