Newer
Older
19001
19002
19003
19004
19005
19006
19007
19008
19009
19010
19011
19012
19013
19014
19015
19016
19017
19018
19019
// codes and read that file instead. This is done so that this script can
// accurately get the file line lengths using console.strlen().
var syncConvertedHdrFilename = txtFilenameFullPath + "_converted.asc";
if (!file_exists(syncConvertedHdrFilename))
{
if (getStrAfterPeriod(txtFileFilename).toUpperCase() == "ANS")
{
var filenameBase = txtFileFilename.substr(0, dotIdx);
var cmdLine = system.exec_dir + "ans2asc \"" + txtFileFilename + "\" \""
+ syncConvertedHdrFilename + "\"";
// Note: Both system.exec(cmdLine) and
// bbs.exec(cmdLine, EX_NATIVE, gStartupPath) could be used to
// execute the command, but system.exec() seems noticeably faster.
system.exec(cmdLine);
}
else
syncConvertedHdrFilename = txtFileFilename;
}
*/
19020
19021
19022
19023
19024
19025
19026
19027
19028
19029
19030
19031
19032
19033
19034
19035
19036
19037
19038
19039
19040
19041
19042
19043
19044
19045
19046
19047
19048
19049
// Read the header file into txtFileLines
var hdrFile = new File(syncConvertedHdrFilename);
if (hdrFile.open("r"))
{
var fileLine = null;
while (!hdrFile.eof)
{
// Read the next line from the header file.
fileLine = hdrFile.readln(2048);
// fileLine should be a string, but I've seen some cases
// where it isn't, so check its type.
if (typeof(fileLine) != "string")
continue;
// Make sure the line isn't longer than the user's terminal
//if (fileLine.length > console.screen_columns)
// fileLine = fileLine.substr(0, console.screen_columns);
txtFileLines.push(fileLine);
// If the header array now has the maximum number of lines, then
// stop reading the header file.
if (txtFileLines.length == maxNumLines)
break;
}
hdrFile.close();
}
}
return txtFileLines;
}
// Returns the portion (if any) of a string after the period.
//
// Parameters:
// pStr: The string to extract from
//
// Return value: The portion of the string after the dot, if there is one. If
// not, then an empty string will be returned.
function getStrAfterPeriod(pStr)
{
var strAfterPeriod = "";
var dotIdx = pStr.lastIndexOf(".");
if (dotIdx > -1)
strAfterPeriod = pStr.substr(dotIdx+1);
return strAfterPeriod;
}
// Adjusts a message's when-written time to the BBS's local time.
//
// Parameters:
// pMsgHdr: A message header object
//
// Return value: The message's when_written_time adjusted to the BBS's local time.
// If the message header doesn't have a when_written_time or
// when_written_zone property, then this function will return -1.
function msgWrittenTimeToLocalBBSTime(pMsgHdr)
{
if (!pMsgHdr.hasOwnProperty("when_written_time") || !pMsgHdr.hasOwnProperty("when_written_zone_offset") || !pMsgHdr.hasOwnProperty("when_imported_zone_offset"))
return -1;
var timeZoneDiffMinutes = pMsgHdr.when_imported_zone_offset - pMsgHdr.when_written_zone_offset;
//var timeZoneDiffMinutes = pMsgHdr.when_written_zone - system.timezone;
var timeZoneDiffSeconds = timeZoneDiffMinutes * 60;
var msgWrittenTimeAdjusted = pMsgHdr.when_written_time + timeZoneDiffSeconds;
return msgWrittenTimeAdjusted;
}
19086
19087
19088
19089
19090
19091
19092
19093
19094
19095
19096
19097
19098
19099
19100
19101
19102
19103
19104
19105
19106
19107
19108
19109
19110
19111
19112
19113
19114
19115
19116
19117
// Returns a string containing the message group & sub-board numbers and
// descriptions.
//
// Parameters:
// pMsgbase: A MsgBase object
//
// Return value: A string containing the message group & sub-board numbers and
// descriptions
function getMsgAreaDescStr(pMsgbase)
{
if (typeof(pMsgbase) != "object")
return "";
if (!pMsgbase.is_open)
return "";
var descStr = "";
if (pMsgbase.cfg != null)
{
descStr = format("Group/sub-board num: %d, %d; %s - %s", pMsgbase.cfg.grp_number,
pMsgbase.subnum, msg_area.grp_list[pMsgbase.cfg.grp_number].description,
pMsgbase.cfg.description);
}
else
{
if ((pMsgbase.subnum == -1) || (pMsgbase.subnum == 65535))
descStr = "Electronic Mail";
else
descStr = "Unspecified";
}
return descStr;
}
19118
19119
19120
19121
19122
19123
19124
19125
19126
19127
19128
19129
19130
19131
19132
19133
19134
19135
19136
19137
19138
19139
19140
19141
19142
19143
19144
19145
19146
19147
19148
19149
19150
19151
19152
// Lets the sysop edit a user.
//
// Parameters:
// pUsername: The name of the user to edit
//
// Return value: A function containing the following properties:
// errorMsg: An error message on failure, or a blank string on success
function editUser(pUsername)
{
var retObj = new Object();
retObj.errorMsg = "";
if (typeof(pUsername) != "string")
{
retObj.errorMsg = "Given username is not a string";
return retObj;
}
// If the logged-in user is not a sysop, then just return.
if (!gIsSysop)
{
retObj.errorMsg = "Only a sysop can edit a user";
return retObj;
}
// If the user exists, then let the sysop edit the user.
var userNum = system.matchuser(pUsername);
if (userNum != 0)
bbs.exec("*str_cmds uedit " + userNum);
else
retObj.errorMsg = "User \"" + pUsername + "\" not found";
return retObj;
}
// Returns an object containing bare minimum properties necessary to
// display an invalid message header. Additionally, an object returned
// by this function will have an extra property, isBogus, that will be
// a boolean set to true.
//
// Parameters:
// pSubject: Optional - A string to use as the subject in the bogus message
// header object
function getBogusMsgHdr(pSubject)
{
var msgHdr = new Object();
msgHdr.subject = (typeof(pSubject) == "string" ? pSubject : "");
msgHdr.when_imported_time = 0;
msgHdr.when_written_time = 0;
msgHdr.when_written_zone = 0;
msgHdr.date = "Fri, 1 Jan 1960 00:00:00 -0000";
msgHdr.attr = 0;
msgHdr.to = "Nobody";
msgHdr.from = "Nobody";
msgHdr.number = 0;
msgHdr.offset = 0;
msgHdr.isBogus = true;
return msgHdr;
}
// Returns whether a message is readable to the user, based on its
// header and the sub-board code.
//
// Parameters:
// pMsgHdr: The header object for the message
// pSubBoardCode: The internal code for the sub-board the message is in
//
// Return value: Boolean - Whether or not the message is readable for the user
function isReadableMsgHdr(pMsgHdr, pSubBoardCode)
{
if (pMsgHdr === null)
return false;
// Let the sysop see unvalidated messages and private messages but not other users.
if (!gIsSysop)
{

nightfox
committed
if (pSubBoardCode != "mail")
{

nightfox
committed
if ((msg_area.sub[pSubBoardCode].is_moderated && ((pMsgHdr.attr & MSG_VALIDATED) == 0)) ||
(((pMsgHdr.attr & MSG_PRIVATE) == MSG_PRIVATE) && !userHandleAliasNameMatch(pMsgHdr.to)))
{
return false;
}
19200
19201
19202
19203
19204
19205
19206
19207
19208
19209
19210
19211
19212
19213
19214
19215
19216
19217
19218
19219
19220
19221
19222
19223
19224
19225
19226
19227
19228
19229
19230
}
}
// If the message is deleted, determine whether it should be viewable, based
// on the system settings.
if ((pMsgHdr.attr & MSG_DELETE) == MSG_DELETE)
{
// If the user is a sysop, check whether sysops can view deleted messages.
// Otherwise, check whether users can view deleted messages.
if (gIsSysop)
{
if ((system.settings & SYS_SYSVDELM) == 0)
return false;
}
else
{
if ((system.settings & SYS_USRVDELM) == 0)
return false;
}
}
// The message voting and poll variables were added in sbbsdefs.js for
// Synchronet 3.17. Make sure they're defined before referencing them.
if (typeof(MSG_UPVOTE) != "undefined")
{
if ((pMsgHdr.attr & MSG_UPVOTE) == MSG_UPVOTE)
return false;
}
if (typeof(MSG_DOWNVOTE) != "undefined")
{
if ((pMsgHdr.attr & MSG_DOWNVOTE) == MSG_DOWNVOTE)
return false;
}
// Don't include polls as being unreadable messages - They just need to have
// their answer selections read from the header instead of the message body
/*
if (typeof(MSG_POLL) != "undefined")
{
if ((pMsgHdr.attr & MSG_POLL) == MSG_POLL)
return false;
}
*/
return true;
}
// Returns the number of readable messages in a sub-board.
//
// Parameters:
// pMsgbase: The MsgBase object representing the sub-board
// pSubBoardCode: The internal code of the sub-board
//
// Return value: The number of readable messages in the sub-board
function numReadableMsgs(pMsgbase, pSubBoardCode)
{
if ((pMsgbase === null) || !pMsgbase.is_open)
return 0;
var numMsgs = 0;
if (typeof(pMsgbase.get_all_msg_headers) === "function")
19257
19258
19259
19260
19261
19262
19263
19264
19265
19266
19267
19268
19269
19270
19271
19272
19273
19274
19275
19276
19277
19278
19279
19280
19281
{
var msgHdrs = pMsgbase.get_all_msg_headers(true);
for (var msgHdrsProp in msgHdrs)
{
if (msgHdrs[msgHdrsProp] == null)
continue;
else if (isReadableMsgHdr(msgHdrs[msgHdrsProp], pSubBoardCode))
++numMsgs;
}
}
else
{
var msgHeader;
for (var i = 0; i < pMsgbase.total_msgs; ++i)
{
msgHeader = msgBase.get_msg_header(true, i, false);
if (msgHeader == null)
continue;
else if (isReadableMsgHdr(msgHeader, pSubBoardCode))
++numMsgs;
}
}
return numMsgs;
}

nightfox
committed
// Deletes vote messages (messages that have voting response data for a message with
// a given message number).
//
// Parameters:
// pMsgbase: A MessageBase object containing the messages to be deleted
// pMsgNum: The number of the message for which vote messages should be deleted
// pMsgID: The ID of the message for which vote messages should be deleted

nightfox
committed
// pIsMailSub: Boolean - Whether or not it's the personal email area
//
// Return value: An object containing the following properties:
// numVoteMsgs: The number of vote messages for the given message number
// numVoteMsgsDeleted: The number of vote messages that were deleted
// allVoteMsgsDeleted: Boolean - Whether or not all vote messages were deleted
function deleteVoteMsgs(pMsgbase, pMsgNum, pMsgID, pIsEmailSub)

nightfox
committed
19296
19297
19298
19299
19300
19301
19302
19303
19304
19305
19306
19307
19308
19309
19310
19311
19312
19313
19314
19315
19316
19317
19318
19319
{
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.

nightfox
committed
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))

nightfox
committed
{
++retObj.numVoteMsgs;
msgWasDeleted = pMsgbase.remove_msg(false, msgHdrs[msgHdrsProp].number);
retObj.allVoteMsgsDeleted = (retObj.allVoteMsgsDeleted && msgWasDeleted);
if (msgWasDeleted)
++retObj.numVoteMsgsDeleted;
}
}
}
return retObj;
}
/////////////////////////////////////////////////////////////////////////
19338
19339
19340
19341
19342
19343
19344
19345
19346
19347
19348
19349
19350
19351
19352
19353
19354
19355
19356
19357
19358
19359
19360
19361
19362
// 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)
{

nightfox
committed
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))
{

nightfox
committed
if (msgHdr.from.toUpperCase() == user.name.toUpperCase())
userNameOrAlias = msgHdr.from;
}

nightfox
committed
// 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);
19389
19390
19391
19392
19393
19394
19395
19396
19397
19398
19399
19400
19401
19402
19403
19404
19405
19406
19407
19408
19409
19410
19411
}
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;
}
19412
19413
19414
19415
19416
19417
19418
19419
19420
19421
19422
19423
19424
19425
19426
19427
19428
19429
19430
19431
19432
19433
19434
19435
19436
19437
19438
19439
19440
19441
19442
19443
19444
19445
19446
19447
// 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;
}
19448
19449
19450
19451
19452
19453
19454
19455
19456
19457
19458
19459
19460
19461
19462
19463
19464
19465
19466
19467
19468
19469
19470
19471
19472
19473
19474
19475
19476
19477
19478
19479
19480
19481
19482
19483
19484
19485
19486
19487
19488
19489
19490
19491
19492
19493
19494
19495
19496
19497
19498
19499
19500
19501
19502
19503
19504
19505
19506
19507
19508
19509
19510
19511
19512
19513
19514
19515
19516
19517
19518
19519
19520
19521
19522
19523
19524
19525
19526
19527
19528
19529
19530
19531
19532
// 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 retObj = {
saveSucceeded: false,
msgAttrs: 0
};
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
return retObj;
if (msgbaseOpen)
{
19570
19571
19572
19573
19574
19575
19576
19577
19578
19579
19580
19581
19582
19583
19584
19585
19586
19587
19588
19589
19590
19591
19592
19593
19594
19595
19596
19597
19598
19599
19600
19601
19602
19603
19604
19605
19606
19607
19608
19609
19610
19611
// 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();
}
return retObj;
}
19617
19618
19619
19620
19621
19622
19623
19624
19625
19626
19627
19628
19629
19630
19631
19632
19633
19634
19635
19636
19637
19638
19639
19640
19641
19642
19643
19644
19645
19646
19647
19648
19649
19650
19651
19652
19653
19654
19655
19656
19657
19658
19659
19660
19661
19662
19663
19664
19665
19666
19667
19668
19669
19670
19671
19672
19673
19674
19675
19676
19677
19678
19679
19680
19681
19682
19683
19684
19685
19686
19687
19688
19689
19690
19691
19692
19693
19694
19695
19696
19697
19698
19699
19700
19701
19702
19703
19704
19705
19706
19707
19708
19709
19710
19711
19712
19713
19714
19715
// 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;
}
19716
19717
19718
19719
19720
19721
19722
19723
19724
19725
19726
19727
19728
19729
19730
19731
19732
19733
19734
19735
19736
19737
19738
19739
19740
19741
19742
19743
19744
19745
19746
19747
19748
19749
19750
19751
19752
19753
19754
19755
19756
19757
19758
19759
19760
19761
19762
19763
19764
19765
19766
19767
19768
19769
19770
19771
19772
19773
19774
19775
19776
19777
19778
19779
19780
19781
19782
19783
19784
19785
19786
19787
19788
19789
19790
19791
19792
19793
19794
19795
19796
19797
19798
19799
19800
19801
// 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;
}
19802
19803
19804
19805
19806
19807
19808
19809
19810
19811
19812
19813
19814
19815
19816
19817
19818
19819
19820
19821
// 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("downvotes")) && pMsgHdr.hasOwnProperty("upvotes"))
{
retObj.foundVoteInfo = true;
retObj.upvotes = pMsgHdr.upvotes;
if (pMsgHdr.hasOwnProperty("downvotes"))
retObj.downvotes = pMsgHdr.downvotes;
else
retObj.downvotes = pMsgHdr.total_votes - pMsgHdr.upvotes;
retObj.voteScore = pMsgHdr.upvotes - retObj.downvotes;
}
return retObj;
}
19836
19837
19838
19839
19840
19841
19842
19843
19844
19845
19846
19847
19848
19849
19850
19851
19852
19853
19854
19855
19856
19857
19858
19859
19860
19861
19862
19863
19864
19865
19866
19867
19868
19869
19870
19871
19872
19873
19874
19875
19876
19877
19878
19879
19880
19881
19882
// Removes any initial Synchronet attribute(s) from a message body,
// which can sometimes color the whole message.
//
// Parameters:
// pMsgBody: The original message body
//
// Return value: The message body, with any initial color removed
function removeInitialColorFromMsgBody(pMsgBody)
{
var msgBody = pMsgBody;
msgBodyLines = pMsgBody.split("\r\n", 3);
if (msgBodyLines.length == 3)
{
var onlySyncAttrsRegexWholeWord = /^([krgybmcw01234567hinpq,;\.dtl<>\[\]asz])+$/i;
var line1Match = /^ Re: .*/.test(strip_ctrl(msgBodyLines[0]));
var line2Match = /^ By: .* on .*/.test(strip_ctrl(msgBodyLines[1]));
var line3OnlySyncAttrs = onlySyncAttrsRegexWholeWord.test(msgBodyLines[2]);
if (line1Match && line2Match)
{
msgBodyLines = pMsgBody.split("\r\n");
msgBodyLines[0] = strip_ctrl(msgBodyLines[0]);
msgBodyLines[1] = strip_ctrl(msgBodyLines[1]);
if (line3OnlySyncAttrs)
{
var originalLine3SyncAttrs = msgBodyLines[2];
msgBodyLines[2] = strip_ctrl(msgBodyLines[2]);
// If the first word of the 4th line is only Synchronet attribute codes,
// and they're the same as the codes on the 3rd line, then remove them.
if (msgBodyLines.length >= 4)
{
var line4Words = msgBodyLines[3].split(" ");
if ((line4Words.length > 0) && onlySyncAttrsRegexWholeWord.test(line4Words[0]) && (line4Words[0] == originalLine3SyncAttrs))
msgBodyLines[3] = msgBodyLines[3].substr(line4Words[0].length);
}
}
msgBody = "";
for (var i = 0; i < msgBodyLines.length; ++i)
msgBody += msgBodyLines[i] + "\r\n";
// Remove the trailing \r\n characters from msgBody
msgBody = msgBody.substr(0, msgBody.length-2);
}
}
return msgBody;
}
// Finds a user with a name, alias, or handle matching a given string.
// If system.matchuser() can't find it, this will iterate through all users
// to find the first user with a name, alias, or handle matching the given
// name.
function findUserNumWithName(pName)
{
var userNum = system.matchuser(pName);
if (userNum == 0)
userNum = system.matchuserdata(U_NAME, pName);
if (userNum == 0)
userNum = system.matchuserdata(U_ALIAS, pName);
if (userNum == 0)
userNum = system.matchuserdata(U_HANDLE, pName);
return userNum;
}
19899
19900
19901
19902
19903
19904
19905
19906
19907
19908
19909
19910
19911
19912
19913
19914
19915
19916
19917
19918
19919
19920
19921
19922
19923
19924
19925
19926
19927
19928
19929
19930
19931
19932
19933
19934
19935
19936
19937
19938
19939
19940
19941
19942
19943
19944
19945
19946
19947
19948
19949
19950
19951
19952
19953
19954
19955
19956
19957
19958
19959
19960
19961
19962
19963
19964
19965
19966
19967
19968
19969
19970
19971
19972
19973
19974
19975
19976
19977
19978
19979
19980
19981
19982
19983
19984
19985
19986
19987
19988
19989
19990
19991
19992
19993
19994
19995
19996
19997
19998
19999
20000
// Inputs a string from the user, with a timeout
//
// Parameters:
// pMode: The mode bits to use for the input (i.e., defined in sbbsdefs.js)
// pMaxLength: The maximum length of the string (0 or less for no limit)
// pTimeout: The timeout (in milliseconds). When the timeout is reached,
// input stops and the user's input is returned.
//
// Return value: The user's input (string)
function getStrWithTimeout(pMode, pMaxLength, pTimeout)
{
var inputStr = "";
var mode = K_NONE;
if (typeof(pMode) == "number")
mode = pMode;
var maxWidth = 0;
if (typeof(pMaxLength) == "number")
maxWidth = pMaxLength;
var timeout = 0;
if (typeof(pTimeout) == "number")
timeout = pTimeout;
var setNormalAttrAtEnd = false;
if (((mode & K_LINE) == K_LINE) && (maxWidth > 0) && console.term_supports(USER_ANSI))
{
var curPos = console.getxy();
printf("\1n\1w\1h\1" + "4%" + maxWidth + "s", "");
console.gotoxy(curPos);
setNormalAttrAtEnd = true;
}
var curPos = console.getxy();
var userKey = "";
do
{
userKey = console.inkey(mode, timeout);
if ((userKey.length > 0) && isPrintableChar(userKey))
{
var allowAppendChar = true;
if ((maxWidth > 0) && (inputStr.length >= maxWidth))
allowAppendChar = false;
if (allowAppendChar)
{
inputStr += userKey;
console.print(userKey);
++curPos.x;
}
}
else if (userKey == BACKSPACE)
{
if (inputStr.length > 0)
{
inputStr = inputStr.substr(0, inputStr.length-1);
console.gotoxy(curPos.x-1, curPos.y);
console.print(" ");
console.gotoxy(curPos.x-1, curPos.y);
--curPos.x;
}
}
else if (userKey == KEY_ENTER)
userKey = "";
} while(userKey.length > 0);
if (setNormalAttrAtEnd)
console.print("\1n");
return inputStr;
}
// Calculates the page number (1-based) and top index for the page (0-based),
// given an item index.
//
// Parameters:
// pItemIdx: The index of the item
// pNumItemsPerPage: The number of items per page
//
// Return value: An object containing the following properties:
// pageNum: The page number of the item (1-based; will be 0 if not found)
// pageTopIdx: The index of the top item on the page (or -1 if not found)
function calcPageNumAndTopPageIdx(pItemIdx, pNumItemsPerPage)
{
var retObj = {
pageNum: 0,
pageTopIdx: -1
};
var pageNum = 1;
var topIdx = 0;
var continueOn = true;
do
{
var endIdx = topIdx + pNumItemsPerPage;
if ((pItemIdx >= topIdx) && (pItemIdx < endIdx))
{
continueOn = false;
retObj.pageNum = pageNum;
retObj.pageTopIdx = topIdx;
}
else
{
++pageNum;