Skip to content
Snippets Groups Projects
DDMsgReader.js 792 KiB
Newer Older
function deleteVoteMsgs(pMsgbase, pMsgNum, pMsgID, pIsEmailSub)
{
	var retObj = {
		numVoteMsgs: 0,
		numVoteMsgsDeleted: 0,
		allVoteMsgsDeleted: true
	};

	if ((pMsgbase === null) || !pMsgbase.is_open)
		return retObj;
	if (typeof(pMsgNum) != "number")
		return retObj;
	if (pIsEmailSub)
		return retObj;

	// This relies on get_all_msg_headers() returning vote messages.  The get_all_msg_headers()
	// function was added in Synchronet 3.16, and the 'true' parameter to get vote headers was
	// added in Synchronet 3.17.
	if (typeof(pMsgbase.get_all_msg_headers) === "function")
	{
		var msgHdrs = pMsgbase.get_all_msg_headers(true);
		for (var msgHdrsProp in msgHdrs)
		{
			if (msgHdrs[msgHdrsProp] == null)
				continue;
			// If this header is a vote header and its thread_back or reply_id matches the given message,
			// then we can delete this message.
			var isVoteMsg = (((msgHdrs[msgHdrsProp].attr & MSG_VOTE) == MSG_VOTE) || ((msgHdrs[msgHdrsProp].attr & MSG_UPVOTE) == MSG_UPVOTE) || ((msgHdrs[msgHdrsProp].attr & MSG_DOWNVOTE) == MSG_DOWNVOTE));
			if (isVoteMsg && (msgHdrs[msgHdrsProp].thread_back == pMsgNum) || (msgHdrs[msgHdrsProp].reply_id == pMsgID))
			{
				++retObj.numVoteMsgs;
				msgWasDeleted = pMsgbase.remove_msg(false, msgHdrs[msgHdrsProp].number);
				retObj.allVoteMsgsDeleted = (retObj.allVoteMsgsDeleted && msgWasDeleted);
				if (msgWasDeleted)
					++retObj.numVoteMsgsDeleted;
			}
		}
	}

	return retObj;
}

/////////////////////////////////////////////////////////////////////////
// Debug helper & error output functions

// Prints information from a message header on the screen, for debugging purpurposes.
//
// Parameters:
//  pMsgHdr: A message header object
function printMsgHdr(pMsgHdr)
{
	for (var prop in pMsgHdr)
	{
		if ((prop == "field_list") && (typeof(pMsgHdr[prop]) == "object"))
		{
			console.print(prop + ":\r\n");
			for (var objI = 0; objI < pMsgHdr[prop].length; ++objI)
			{
				console.print(" " + objI + ":\r\n");
				for (var innerProp in pMsgHdr[prop][objI])
					console.print("  " + innerProp + ": " + pMsgHdr[prop][objI][innerProp] + "\r\n");
			}
		}
		else
			console.print(prop + ": " + pMsgHdr[prop] + "\r\n");
	}
	console.pause();
}
// Closes a poll, using an existing MessageBase object.
//
// Parameters:
//  pMsgbase: A MessageBase object representing the current sub-board.  It
//            must be open.
//  pMsgNum: The message number (not the index)
//
// Return value: Boolean - Whether or not closing the poll succeeded
function closePollWithOpenMsgbase(pMsgbase, pMsgNum)
{
	var pollClosed = false;
	if ((pMsgbase !== null) && pMsgbase.is_open)
	{
		var userNameOrAlias = user.alias;
		// See if the poll was posted using the user's real name instead of
		// their alias
		var msgHdr = pMsgbase.get_msg_header(false, pMsgNum, false);
		if ((msgHdr != null) && ((msgHdr.attr & MSG_POLL) == MSG_POLL))
		{
			if (msgHdr.from.toUpperCase() == user.name.toUpperCase())
				userNameOrAlias = msgHdr.from;
		// Close the poll (the close_poll() method was added in the Synchronet
		// 3.17 build on August 19, 2017)
		pollClosed = pMsgbase.close_poll(pMsgNum, userNameOrAlias);
	}
	return pollClosed;
}

// Closes a poll.
//
// Parameters:
//  pSubBoardCode: The internal code of the sub-board
//  pMsgNum: The message number (not the index)
//
// Return value: Boolean - Whether or not closing the poll succeeded
function closePoll(pSubBoardCode, pMsgNum)
{
	var pollClosed = false;
	var msgbase = new MsgBase(pSubBoardCode);
	if (msgbase.open())
	{
		pollClosed = closePollWithOpenMsgbase(msgbase, pMsgNum);
		msgbase.close();
	}
	return pollClosed;
}

// Gets a message header from the messagebase, either by index (offset) or number.
//
// Parameters:
//  pMsgbase: Optional messagebase object.  If this is provided, then pSubBoardCode is not used.
//  pSubBoardCode: The messagebase sub-board code
//  pByIdx: Boolean - Whether or not to get the message header by index (if false, then by number)
//  pMsgIdxOrNum: The message index or number of the message header to retrieve
//  pExpandFields: Boolean - Whether or not to expand fields for the message header
function getHdrFromMsgbase(pMsgbase, pSubBoardCode, pByIdx, pMsgIdxOrNum, pExpandFields)
{
	var msgbaseIsOpen = false;
	var msgbase = null;
	var msgHdr = null;
	if (pMsgbase == null)
	{
		msgbase = new MsgBase(pSubBoardCode);
		msgbaseIsOpen = msgbase.open();
	}
	else
	{
		msgbase = pMsgbase;
		msgbaseIsOpen = pMsgbase.is_open;
	}
	if (msgbaseIsOpen)
	{
		var getMsgHdr = true;
		if (pByIdx)
			getMsgHdr = ((pMsgIdxOrNum >= 0) && (pMsgIdxOrNum < msgbase.total_msgs))
		if (getMsgHdr)
			msgHdr = msgbase.get_msg_header(pByIdx, pMsgIdxOrNum, pExpandFields);
		if (pMsgbase == null)
			msgbase.close();
	}
	return msgHdr;
}

// Inputs a string from the user, restricting their input to certain keys (optionally).
//
// Parameters:
//  pKeys: A string containing valid characters for input.  Optional
//  pMaxNumChars: The maximum number of characters to input.  Optional
//  pCaseSensitive: Boolean - Whether or not the input should be case-sensitive.  Optional.
//                  Defaults to true.  If false, then the user input will be uppercased.
//
// Return value: A string containing the user's input
function consoleGetStrWithValidKeys(pKeys, pMaxNumChars, pCaseSensitive)
{
	var maxNumChars = 0;
	if ((typeof(pMaxNumChars) == "number") && (pMaxNumChars > 0))
		maxNumChars = pMaxNumChars;

	var regexPattern = (typeof(pKeys) == "string" ? "[" + pKeys + "]" : ".");
	var caseSensitive = (typeof(pCaseSensitive) == "boolean" ? pCaseSensitive : true);
	var regex = new RegExp(regexPattern, (caseSensitive ? "" : "i"));

	var CTRL_H = "\x08";
	var BACKSPACE = CTRL_H;
	var CTRL_M = "\x0d";
	var KEY_ENTER = CTRL_M;

	var modeBits = (caseSensitive ? K_NONE : K_UPPER);
	var userInput = "";
	var continueOn = true;
	while (continueOn)
	{
		var userChar = console.getkey(K_NOECHO|modeBits);
		if (regex.test(userChar) && isPrintableChar(userChar))
		{
			var appendChar = true;
			if ((maxNumChars > 0) && (userInput.length >= maxNumChars))
				appendChar = false;
			if (appendChar)
			{
				userInput += userChar;
				if ((modeBits & K_NOECHO) == 0)
					console.print(userChar);
			}
		}
		else if (userChar == BACKSPACE)
		{
			if (userInput.length > 0)
			{
				if ((modeBits & K_NOECHO) == 0)
				{
					console.print(BACKSPACE);
					console.print(" ");
					console.print(BACKSPACE);
				}
				userInput = userInput.substr(0, userInput.length-1);
			}
		}
		else if (userChar == KEY_ENTER)
		{
			continueOn = false;
			if ((modeBits & K_NOCRLF) == 0)
				console.crlf();
		}
	}
	return userInput;
}

// Returns whether or not a character is printable.
//
// Parameters:
//  pChar: A character to test
//
// Return value: Boolean - Whether or not the character is printable
function isPrintableChar(pChar)
{
	// Make sure pChar is valid and is a string.
	if (typeof(pChar) != "string")
		return false;
	if (pChar.length == 0)
		return false;

	// Make sure the character is a printable ASCII character in the range of 32 to 254,
	// except for 127 (delete).
	var charCode = pChar.charCodeAt(0);
	return ((charCode > 31) && (charCode < 255) && (charCode != 127));
}

// Adds message attributes to a message header and saves it in the messagebase.
// To do that, this function first loads the messag header from the messagebase
// without expanded fields, applies the attributes, and then saves the header
// back to the messagebase.
//
// Parameters:
//  pMsgbaseOrSubCode: An open MessageBase object or a sub-board code (string)
//  pMsgNum: The number of the message to update
//  pMsgAttrs: The message attributes to apply to the message (numeric bitfield)
//
// Return value: An object containing the following properties:
//               saveSucceeded: Boolean - Whether or not the message header was successfully saved
//               msgAttrs: A numeric bitfield containing the updated attributes of the message header
function applyAttrsInMsgHdrInMessagbase(pMsgbaseOrSubCode, pMsgNum, pMsgAttrs)
	var msgbaseOpen = false;
	var msgbase = null;
	if (typeof(pMsgbaseOrSubCode) == "object")
	{
		msgbase = pMsgbaseOrSubCode;
		msgbaseOpen = msgbase.is_open;
	}
	else if (typeof(pMsgbaseOrSubCode) == "string")
	{
		msgbase = new MsgBase(pMsgbaseOrSubCode);
		msgbaseOpen = msgbase.open();
	}
	else
		// Get the message header without expanded fields (we can't save it with
		// expanded fields), then add the 'read' attribute and save it back to the messagebase.
		var msgHdr = msgbase.get_msg_header(false, pMsgNum, false);
		if (msgHdr != null)
		{
			msgHdr.attr |= pMsgAttrs;
			retObj.saveSucceeded = msgbase.put_msg_header(false, pMsgNum, msgHdr);
			if (retObj.saveSucceeded)
				retObj.msgAttrs = msgHdr.attr;
			else
			{
				writeToSysAndNodeLog("Failed to save message header with the following attributes: " + msgAttrsToString(pMsgAttrs), LOG_ERR);
				writeToSysAndNodeLog(getMsgAreaDescStr(msgbase), LOG_ERR);
				writeToSysAndNodeLog(format("Message offset: %d, number: %d", msgHdr.offset, msgHdr.number), LOG_ERR);
				writeToSysAndNodeLog("Status: " + msgbase.status, LOG_ERR);
				writeToSysAndNodeLog("Error: " + msgbase.error, LOG_ERR);
				/*
				// For sysops, output a debug message
				if (gIsSysop)
				{
					console.print("\1n");
					console.crlf();
					console.print("* Failed to save msg header the with the following attributes: " + msgAttrsToString(pMsgAttrs));
					console.crlf();
					console.print("Status: " + msgbase.status);
					console.crlf();
					console.print("Error: " + msgbase.error);
					console.crlf();
					console.crlf();
					//console.print("put_msg_header params: false, " + msgHdr.number + ", header:\r\n");
					//console.print("put_msg_header params: true, " + msgHdr.offset + ", header:\r\n");
					//console.print("put_msg_header params: " + msgHdr.number + ", header:\r\n");
					printMsgHdr(msgHdr);
				}
				*/
			}
		}

		// If a sub-board code was passed in, then close the messagebase object
		// that we created here.
		if (typeof(pMsgbaseOrSubCode) == "string")
			msgbase.close();
// Converts a message attributes bitfield to a string.
//
// Parameters:
//  pMsgAttrs: A numeric type with message attribute bits
//
// Return value: A string containing a list of the message attributes
function msgAttrsToString(pMsgAttrs)
{
	if (typeof(pMsgAttrs) != "number")
		return "";

	var attrsStr = "";
	if ((pMsgAttrs & MSG_PRIVATE) == MSG_PRIVATE)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_PRIVATE";
	}
	if ((pMsgAttrs & MSG_READ) == MSG_READ)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_READ";
	}
	if ((pMsgAttrs & MSG_PERMANENT) == MSG_PERMANENT)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_PERMANENT";
	}
	if ((pMsgAttrs & MSG_LOCKED) == MSG_LOCKED)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_LOCKED";
	}
	if ((pMsgAttrs & MSG_DELETE) == MSG_DELETE)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_DELETE";
	}
	if ((pMsgAttrs & MSG_ANONYMOUS) == MSG_ANONYMOUS)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_ANONYMOUS";
	}
	if ((pMsgAttrs & MSG_KILLREAD) == MSG_KILLREAD)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_KILLREAD";
	}
	if ((pMsgAttrs & MSG_MODERATED) == MSG_MODERATED)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_MODERATED";
	}
	if ((pMsgAttrs & MSG_VALIDATED) == MSG_VALIDATED)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_VALIDATED";
	}
	if ((pMsgAttrs & MSG_REPLIED) == MSG_REPLIED)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_REPLIED";
	}
	if ((pMsgAttrs & MSG_NOREPLY) == MSG_NOREPLY)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_NOREPLY";
	}
	if ((pMsgAttrs & MSG_UPVOTE) == MSG_UPVOTE)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_UPVOTE";
	}
	if ((pMsgAttrs & MSG_DOWNVOTE) == MSG_DOWNVOTE)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_DOWNVOTE";
	}
	if ((pMsgAttrs & MSG_POLL) == MSG_POLL)
	{
		if (attrsStr.length > 0)
			attrsStr += ", ";
		attrsStr += "MSG_POLL";
	}
	return attrsStr;
}

// Returns the index of the first Synchronet attribute code before a given index
// in a string.
//
// Parameters:
//  pStr: The string to search in
//  pIdx: The index to search back from
//  pSeriesOfAttrs: Optional boolean - Whether or not to look for a series of
//                  attributes.  Defaults to false (look for just one attribute).
//  pOnlyInWord: Optional boolean - Whether or not to look only in the current word
//               (with words separated by whitespace).  Defaults to false.
//
// Return value: The index of the first Synchronet attribute code before the given
//               index in the string, or -1 if there is none or if the parameters
//               are invalid
function strIdxOfSyncAttrBefore(pStr, pIdx, pSeriesOfAttrs, pOnlyInWord)
{
	if (typeof(pStr) != "string")
		return -1;
	if (typeof(pIdx) != "number")
		return -1;
	if ((pIdx < 0) || (pIdx >= pStr.length))
		return -1;

	var seriesOfAttrs = (typeof(pSeriesOfAttrs) == "boolean" ? pSeriesOfAttrs : false);
	var onlyInWord = (typeof(pOnlyInWord) == "boolean" ? pOnlyInWord : false);

	var attrCodeIdx = pStr.lastIndexOf("\1", pIdx-1);
	if (attrCodeIdx > -1)
	{
		// If we are to only check the current word, then continue only if
		// there isn't a space between the attribute code and the given index.
		if (onlyInWord)
		{
			if (pStr.lastIndexOf(" ", pIdx-1) >= attrCodeIdx)
				attrCodeIdx = -1;
		}
	}
	if (attrCodeIdx > -1)
	{
		var syncAttrRegexWholeWord = /^\1[krgybmcw01234567hinpq,;\.dtl<>\[\]asz]$/i;
		if (syncAttrRegexWholeWord.test(pStr.substr(attrCodeIdx, 2)))
		{
			if (seriesOfAttrs)
			{
				for (var i = attrCodeIdx - 2; i >= 0; i -= 2)
				{
					if (syncAttrRegexWholeWord.test(pStr.substr(i, 2)))
						attrCodeIdx = i;
					else
						break;
				}
			}
		}
		else
			attrCodeIdx = -1;
	}
	return attrCodeIdx;
}

// Returns a string with any Synchronet color/attribute codes found in a string
// before a given index.
//
// Parameters:
//  pStr: The string to search in
//  pIdx: The index in the string to search before
//
// Return value: A string containing any Synchronet attribute codes found before
//               the given index in the given string
function getAttrsBeforeStrIdx(pStr, pIdx)
{
	if (typeof(pStr) != "string")
		return "";
	if (typeof(pIdx) != "number")
		return "";
	if (pIdx < 0)
		return "";

	var idx = (pIdx < pStr.length ? pIdx : pStr.length-1);
	var attrStartIdx = strIdxOfSyncAttrBefore(pStr, idx, true, false);
	var attrEndIdx = strIdxOfSyncAttrBefore(pStr, idx, false, false); // Start of 2-character code
	var attrsStr = "";
	if ((attrStartIdx > -1) && (attrEndIdx > -1))
		attrsStr = pStr.substring(attrStartIdx, attrEndIdx+2);
	return attrsStr;
}

// Given a message header, this function gets/calculates the message's
// upvotes, downvotes, and vote score, if that information is present.
//
// Parameters:
//  pMsgHdr: A message header object
//
// Return value: An object containign the following properties:
//               foundVoteInfo: Boolean - Whether the vote information exited in the header
//               upvotes: The number of upvotes
//               downvotes: The number of downvotes
//               voteScore: The overall vote score
function getMsgUpDownvotesAndScore(pMsgHdr)
{
	var retObj = {
		foundVoteInfo: false,
		upvotes: 0,
		downvotes: 0,
		voteScore: 0
	};

	if (pMsgHdr.hasOwnProperty("total_votes") && pMsgHdr.hasOwnProperty("upvotes"))
	{
		retObj.foundVoteInfo = true;
		retObj.upvotes = pMsgHdr.upvotes;
		retObj.downvotes = pMsgHdr.total_votes - pMsgHdr.upvotes;
		retObj.voteScore = pMsgHdr.upvotes - retObj.downvotes;
	}

	return retObj;
}

// Writes some text on the screen at a given location with a given pause.
//
// Parameters:
//  pX: The column number on the screen at which to write the message
//  pY: The row number on the screen at which to write the message
//  pText: The text to write
//  pPauseMS: The pause time, in milliseconds
//  pClearLineAttrib: Optional - The color/attribute to clear the line with.
//                    If not specified or null is specified, defaults to normal attribute.
//  pClearLineAfter: Whether or not to clear the line again after the message is dispayed and
//                   the pause occurred.  This is optional.
function writeWithPause(pX, pY, pText, pPauseMS, pClearLineAttrib, pClearLineAfter)
{
	var clearLineAttrib = "\1n";
	if ((pClearLineAttrib != null) && (typeof(pClearLineAttrib) == "string"))
		clearLineAttrib = pClearLineAttrib;
	console.gotoxy(pX, pY);
	console.cleartoeol(clearLineAttrib);
	console.print(pText);
	if (pClearLineAfter)
	{
		console.gotoxy(pX, pY);
		console.cleartoeol(clearLineAttrib);
	}