@q @program jmail.muf 1 99999 d i ( jmail.muf v1.0 Jessy @ FurryMUCK 8/99 An email-like mail system. Features include forwarding, reply-to, reply-to-all, personal and global distribution lists, message recall, folders, configurable limits on mailbox size and message aging time, and transparent integration with page #mail. INSTALLATION: Installation must be performed by a wizard. Set jmail.muf Wizard. Create a global action with a name such as 'mail;m' or '+mail' and link the action to the program. Jmail.muf requires lib-reflist, lib-lmgr, lib-strings, lib-editor, and a .pmatch macro, all of which should be installed on an established MUCK. To allow players to be shown a 'You have mail' message at logon, port jmail-warn.muf. To allow JMail to use global page aliases in addition to distribution lists and personal page aliases when resolving names, set cmd-page prop on JMail to cmd-page's dbref. @set jmail.muf=cmd-page:88 To allow programs to send messages, set jmail.muf Link_OK. Or, to allow programs to send messages without setting jmail.muf Link_OK -- see note below -- port lib-jmail. NOTE: JMail includes some sensitive routines relating to cmd-page that could be abused if copied into other programs. For this reason, you may want to keep JMail !Link_OK, and forego program calls. My own belief is that this kind of security-through-obscurity is misguided: If someone wanted to hack mail, they could do so by copying and modifying routines from widely available programs. Keeping JMail !Link_OK wouldn't prevent this, and could give a false sense of security. As an alternative, you may want to leave JMail !Link_OK, and install lib-jmail, which duplicates the functions necessary to send mail, but does not include the more sensitive cmd-page functions. USAGE: JMail provides most of the features an email system would. The online #help system defaults to a simplified introduction, and has detailed help screens for specific options. The basic syntax is... mail =..... Send mail on to mail .......................... List messages in your Inbox mail .................... Show message in Inbox mail ................. List contents of mail = ........... Show in folder mail #delete ............ Delete mail #reply ............. Reply to etc... CONFIGURATION: The #usage option lets wizards specify default mailbox capacities and message aging-times. Mailbox capacities are in bytes: mail #usage 10000 or... mail #usage 10,000 Message aging-times are specified as , where is a positive number and is a standard time unit: '1 day', '3 weeks', '6 months', etc. mail #usage 3 days With the above settings, mail would be automatically deleted after three days, and users would have a mailbox capacity of 10k. Timed-out mail is not deleted immediately. Rather, JMail checks expiration times each time you use mail. If a new expired message is found, it's moved to a to-delete directory, and will be deleted the next time you run mail more than one hour after it was moved. This gives users a chance to read all mail. Individual messages can be protected with the #keep option. If a user's mailbox reaches capacity, she will be unable to send or receive mail until she deletes messages. All of which is background for a recommended configuration: A relatively large mailbox capacity -- 10 KB or so -- and a relatively short message aging value -- 3 days, say -- will usually work well: the mailbox will not grow exceptionally large unless the user deliberately saves a number of large messages, and it will be unlikely to fill up in normal use. This is the default setting. On a large MUCK, it would be reasonable to reduce both settings, especially mailbox capacity. Capacity and aging-times can also be set on a per-user basis. As a point of comparison, this header comment is about 7.2 KB. To prevent players such as guests from using mail -- while still allowing page #mail if page #mail integration is on -- use the #usage option with the following syntax: mail #usage =page-only ... may not use mail mail #usage =!page-only .. may use mail To completely block a player from using JMail, use the #usage option with the following syntax: mail #usage =lock-out ..... may not use JMail mail #usage =!lock-out .... may use JMail PUBLIC FUNCTIONS: jmail-player [ d s1 s2 -- i ] Sends message body s2 with subject s1 to player d. Returns true if successful. jmail-list [ d s1 s2 s3 -- i ] Sends message body s3 with subject s2 to members of reflist s1 on object d. Returns true if at least one list member received the message. For both, subject lines are limited to 36 characters. JMail must be set Link_OK in order for programs to use these functions. JMail registers itself as $lib/jmail after installation, so $include $lib/jmail in programs that need to send mail. FINAL NOTE - RESOURCE USAGE: With a program as large as JMail, it is reasonable to be concerned about resource usage. JMail is a CPU-intensive program, but not unreasonably so. Some comparisons: JMailing a 256 character message takes less than 10% the CPU time than page #mailing the same message if both players' mailboxes are empty. A break-even point is reached when the two players' mailboxes total about 4 KB. If the combined mailbox size of the players is larger than 4 KB, page #mail is quicker. Of course, JMail keeps mailboxes, so it does increase the size of the database. JMail's encryption routines are borrowed from cmd-page, slightly modified. List-handling routines, also slightly modified, are borrowed from cmd-lsedit... thanks Revar. Page #mail integration features have been tested with cmd-page version 2.40. JMail.muf may be freely ported. Please comment any changes. ) (2345678901234567890123456789012345678901234567890123456789012345678901) $include $lib/reflist $include $lib/lmgr $include $lib/editor $include $lib/strings $define DoNukeStack begin depth while pop repeat $enddef $define DoSynString ">> Syntax: " command @ strcat " " strcat $enddef $define DoRemoveProp remove_prop $enddef $define Tell me @ swap notify $enddef lvar ourArg1 (* str: first cmd arg; may be modified by jmail *) lvar ourArg2 (* str: second cmd arg; may be modified by jmail *) lvar ourArg3 (* str: third cmd arg; may be modified by jmail *) lvar ourBody (* str: msg body in public function calls *) lvar ourBoolean (* int: misc flow-control var *) lvar ourCounter (* int or str: misc counter var *) lvar ourDistList (* str: name of distribution list; may be encrypted *) lvar ourFolder (* str: target folder name *) lvar ourFunc (* str: cmd arg with leading # octothorpe *) lvar ourKey (* int: encryption key *) lvar ourMessage (* str: target message id *) lvar ourPlayer (* dbref: dbref of selected player *) lvar ourSubject (* str: subject of message; may be encrypted *) lvar ourTime (* int: systime of current message *) lvar ourTotal (* int: running total of bytes, messges, folders, etc *) (******** Begin initialization and permission-check functions *********) : DoInitProgram ( -- ) (* initialize global props *) prog "@/mail/ve" getprop not if #0 "_reg/lib/jmail" prog setprop prog "_defs/jmail-list" "\"$lib/jmail\" match \"jmail-list\" call" setprop prog "_defs/jmail-player" "\"$lib/jmail\" match \"jmail-player\" call" setprop prog "@/mail/ag" 259200 setprop prog "@/mail/ma" 10000 setprop prog "@/mail/ve" "1.0" setprop then ; : DoInitPlayer ( d -- ) (* initialize mail props for a player *) dup "@/mail/ve" getprop if pop exit then (* create system folders *) dup "@/mail/fo/ Inbox/000000000000.000#" "1" setprop dup "@/mail/fo/ Drafts/000000000000.000#" "1" setprop "@/mail/ve" "1.0" setprop ; : DoInitProcess ( -- ) (* initialize this process *) DoInitProgram (* make sure program is initialized *) "me" match me ! (* block dbref spoofing *) me @ DoInitPlayer (* set initial jmail vals if necessary *) me @ ourPlayer ! (* store user as ourPlayer *) strip ourArg1 ! (* store cmd arg *) " Inbox" ourFolder ! (* inbox is default folder *) ; : DoProgPermCheck ( -- ) (* kill process for unauthorized users *) me @ player? not me @ "@/mail/no" getprop or if ">> Permission denied." Tell pid kill then ; : DoMailPermCheck ( -- ) (* kill process for pmail-only users *) me @ "@/mail/po" getprop if ">> Permission denied." Tell pid kill then ; : DoAdminPermCheck ( -- ) (* kill process for non-wiz users *) me @ "W" flag? not if ">> Permission denied." Tell pid kill then ; (************ Encryption functions borrowed from cmd-page *************) ( The stronger encryption method is needed to convert page mail, but ) ( the simpler and quicker the better for JMail: it wouldn't matter if ) ( I could write armament-grade encryption; someone could still copy ) ( and paste from here to a reader program. So, we'll rely on the wiz- ) ( only propdir for mail privacy, and use simple encryption to keep ) ( prop-browsing wizards from seeing people's mail. ) : asc (stringchar -- int) dup if " 1234567890-=!@#$%&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:zxcvbnm,./ZXCVBNM<>?\"`~\\|^" swap instr 1 - exit then pop 0 ; : chr (int -- strchar) " 1234567890-=!@#$%&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:zxcvbnm,./ZXCVBNM<>?\"`~\\|^" swap strcut 1 strcut pop swap pop ; : cypher (key chars -- chars') 1 strcut asc swap asc over 89 > over 89 > or if chr swap chr strcat swap pop exit then dup 10 / 10 * 4 pick 10 + rot 10 % - 10 % rot dup 10 / 10 * 5 rotate 10 + rot 10 % - 10 % 4 rotate + chr -3 rotate + chr strcat ; : crypt-loop (key strcrypt strnorm -- strcrypt) dup not if pop swap pop exit then 2 strcut 4 pick rot cypher rot swap strcat swap crypt-loop ; : crypt-loop2 (key strcrypt strnorm -- strcrypt) dup strlen 200 < if crypt-loop exit then 200 strcut swap 4 pick 4 rotate rot crypt-loop swap crypt-loop2 ; : DoCrypt2 ( s -- s' ) ourKey @ swap swap 9 % 100 + "" rot crypt-loop2 ; : transpose (char -- char') "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz 1234567890_" over instr dup if swap pop 1 - "wG8D kBQzWm4gbRXHOqaZiJPtUTN2pu6M0VjFlK3sdS9oYe5A_7IE1cnLvfyCrhx" swap strcut 1 strcut pop swap pop else pop then ; : encrypt-charloop (nullstr string -- string') dup not if pop exit then 1 strcut swap transpose rot swap strcat swap encrypt-charloop ; : encrypt-loop (nullstr string -- string') dup not if pop exit then 100 strcut "" rot encrypt-charloop rot swap strcat swap encrypt-loop ; : DoCrypt1 (string -- string') "" swap encrypt-loop ; (************ List-handling functions borrowed from lsedit ************) : DoEditLoop ( listname dbref {rng} mask currline cmdstring -- ) (* read input for list editor *) EDITORloop dup "save" stringcmp not if pop pop pop pop 3 pick 3 + -1 * rotate over 3 + -1 * rotate dup 5 + pick over 5 + pick over over LMGR-DeleteList 1 rot rot LMGR-PutRange 4 pick 4 pick LMGR-GetList dup 3 + rotate over 3 + rotate ">> Message saved." Tell "" DoEditLoop exit then dup "abort" stringcmp not if ">> List not saved." Tell 1 ourBoolean ! (* flag: user aborted *) pop pop pop pop pop pop pop pop pop exit then dup "end" stringcmp not if pop pop pop pop pop pop dup 3 + rotate over 3 + rotate over over LMGR-DeleteList 1 rot rot LMGR-PutRange exit then ; : DoEditList ( d s -- ) (* edit list s on d *) swap ">> Welcome to the list editor. You can get help by entering '.h' on" Tell ">> a line by itself. '.end' will save and exit. '.abort' will abort" Tell ">> any changes. To save changes and continue editing, use '.save'." Tell over over LMGR-GetList "save" 1 ".i $" DoEditLoop ; : DoRemoveList ( d s -- ) (* remove list s from d *) over swap over over "#" strcat DoRemoveProp "#/" strcat nextprop begin dup while over over nextprop rot rot DoRemoveProp repeat pop ; : DoShowList ( d s -- ) (* display list s on object d *) "#/" strcat swap LMGR-GetList begin (* begin line-listing loop *) dup while dup 1 + rotate DoCrypt1 Tell 1 - repeat (* end line-listing loop *) pop ; : DoCryptList ( d s -- ) (* encrypt list s on d *) over rot rot "#/" strcat nextprop begin dup while over over over over getpropstr DoCrypt1 setprop over swap nextprop repeat pop pop ; : DoSetKey ( d -- ) (* set encryption key to value for player d *) ourPlayer @ int ourKey ! ; (*************** Begin internal data-handling functions ***************) : DoCapitalize ( s -- s' ) (* return s, capitalized *) 1 strcut swap toupper swap strcat ; : DoLine ( -- ) (* notify with 72 - dashes *) me @ "-----------------------------------------------------------------------" notify ; : DoComString ( -- s ) (* return space + command name + space *) " " command @ strcat " " strcat ; : DoDot39 ( s -- s' ) (* return s, padded with dots to 39 chars *) ".................................................................." strcat 39 strcut pop ; : DoSafeName ( d -- s ) (* return name of d; wrap for invalid dbrefs *) dup ok? if dup player? if name else unparseobj then else pop "" then ; : DoMsgToTime ( -- i ) (* return ourMessage as a systime *) ourMessage @ dup "/" rinstr strcut swap pop dup "." rinstr strcut pop atoi ; : DoParseTimeString ( s -- i1 i2 ) (* convert string s to number of seconds i1. i2 is true if successful *) (* format of s is ` ', eg `3 hours', `1 day', `2 weeks' *) (* tokenize string *) " " explode dup 2 = if (* check syntax and bail out if needed *) pop else begin dup while swap pop 1 - repeat pop ">> Entry not understood." Tell 0 exit then (* parse units and convert amount *) swap strip "seconds" over stringpfx if 1 else "minutes" over stringpfx if 60 else "hours" over stringpfx if 3600 else "days" over stringpfx if 86400 else "weeks" over stringpfx if 604800 else "months" over stringpfx if 1036800 else "years" over stringpfx if 12441600 else pop pop 0 exit then then then then then then then swap pop swap atoi * dup 0 < if ">> ERROR: Result out of range." Tell pid kill else 1 then ; : DoAddRngToRefList ( d .. d' i d2 s -- )( add drng to d2's rlist s *) begin 3 pick while over over 6 rotate REF-add 3 pick 1 - 3 put repeat pop pop pop ; : DoGetPlayer ( s -- ) (* pmatch s; store result in our oplyr *) 0 ourPlayer ! .pmatch dup #-1 dbcmp if pop 0 ourPlayer ! exit then dup #-2 dbcmp if pop 0 ourPlayer ! exit then ourPlayer ! ; : DoScanDistLists ( d -- ) (* scan distlists on d for invalid player dbrefs; remove invalid *) dup ourCounter ! "@/mail/dl/" nextprop begin (* begin distlist-fetching loop *) dup while dup dup "/" rinstr strcut swap pop ourDistList ! ourCounter @ over REF-allrefs begin (* begin player-checking loop *) dup while 1 - swap dup ok? not if ourCounter @ "@/mail/dl/" ourDistList @ strcat rot REF-delete continue then dup player? not if ourCounter @ "@/mail/dl/" ourDistList @ strcat rot REF-delete continue then pop repeat (* end player-checking loop *) pop ourCounter @ swap nextprop repeat (* end distlist-fetching loop *) pop ; : DoGetDistList ( d s -- ) (* match s as distlist; store in odlst *) 0 ourDistList ! "@/mail/dl/" swap strcat getprop dup if "-jmtemp-" ourDistList ! " " explode begin dup while me @ "@/mail/dl/-jmtemp-" 4 rotate "" "#" subst atoi dbref REF-add 1 - repeat pop else pop then ; : DoGetPageAlias ( s -- ) (* resolve s to a personal page alias *) me @ "Alias-" rot strcat getpropstr dup if " " explode begin dup while swap DoGetPlayer ourPlayer @ if me @ "@/mail/dl/-jmtemp-" ourPlayer @ REF-add "-jmtemp-" ourDistList ! 0 ourPlayer ! then 1 - repeat pop else pop then ; : DoGetGPageAlias ( s -- ) (* resolve s to a personal page alias *) prog "AliasGlobal-" rot strcat getpropstr dup if " " explode begin dup while swap DoGetPlayer ourPlayer @ if me @ "@/mail/dl/-jmtemp-" ourPlayer @ REF-add "-jmtemp-" ourDistList ! 0 ourPlayer ! then 1 - repeat pop else pop then ; : DoGetPlayers ( s -- ) (* resolve s to player or players *) (* store single results in ourPlayer and ourDistList *) (* store multiple results in ourDistList *) (* if cannot resolve, both will be false *) 0 ourPlayer ! 0 ourDistList ! " " explode begin 0 ourBoolean ! ourPlayer @ if me @ "@/mail/dl/-jmtemp-" ourPlayer @ REF-add "-jmtemp-" ourDistList ! then dup while swap dup DoGetPlayer ourPlayer @ if pop 1 - continue then me @ DoScanDistLists me @ over DoGetDistList ourDistList @ if pop 1 - continue then prog DoScanDistLists prog over DoGetDistList ourDistList @ if pop 1 - continue then dup DoGetPageAlias ourDistList @ if pop 1 - continue then dup DoGetGPageAlias ourDistList @ if pop 1 - continue then me @ "@/mail/bn" getpropstr dup if " " strcat over strcat else pop dup then me @ "@/mail/bn" rot setprop ourPlayer @ if me @ "@/mail/dl/-jmtemp-" ourPlayer @ REF-add "-jmtemp-" ourDistList ! then dup string? if pop then 1 - repeat pop me @ "@/mail/dl/-jmtemp-" getprop if "-jmtemp-" ourDistList ! then ; : DoParseTimeInt ( i -- s ) (* convert i seconds to time units *) dup 12441600 / dup if intostr " years" strcat else pop dup 1036800 / dup if intostr " months" strcat else pop dup 604800 / dup if intostr " weeks" strcat else pop dup 86400 / dup if intostr " days" strcat else pop dup 3600 / dup if intostr " hours" strcat else pop dup 60 / dup if intostr " minutes" strcat else pop dup intostr " seconds" strcat then then then then then then swap pop dup "1 " stringpfx if dup strlen 1 - strcut pop then ; : DoSetAging ( -- ) (* set a user's message aging time *) ourPlayer @ not if ">> Player not found." Tell exit then ourArg2 @ DoParseTimeString if ourPlayer @ "@/mail/ag" rot setprop ">> " ourPlayer @ name strcat "'s message-aging time set to " strcat ourArg2 @ strcat "." strcat Tell then ; : DoSetGAging ( -- ) (* set global message aging time *) ourArg1 @ DoParseTimeString if prog "@/mail/ag" rot setprop ">> Global message-aging set to " ourArg1 @ strcat "." strcat Tell then ; : DoSetMailboxCap ( -- ) (* set user's mailbox capacity *) ourPlayer @ not if ">> Player not found." Tell exit then ourArg2 @ atoi 0 < if ">> Sorry, mailbox capacity must be a non-negative number." Tell else ourPlayer @ "@/mail/ma" ourArg2 @ atoi setprop ">> " ourPlayer @ name strcat "'s mailbox capacity set to " strcat ourArg2 @ strcat " bytes." strcat Tell then ; : DoSetGMailboxCap ( -- ) (* set global mailbox capacity *) ourArg1 @ atoi 0 < if ">> Sorry, mailbox capacity must be a non-negative number." Tell else prog "@/mail/ma" ourArg1 @ atoi setprop ">> Global mailbox capacity set to " ourArg1 @ strcat " bytes." strcat Tell then ; : DoSetPageOnly ( -- )(* set ourPlayer page-only... cannot use mail *) ourPlayer @ not if ">> Player not found." Tell exit then ourPlayer @ "@/mail/po" "1" setprop ">> Set. " ourPlayer @ name strcat " may not use mail functions." strcat Tell ; : DoSetPageOnlyNo ( -- ) (* clear ourPlayer's page-only setting *) ourPlayer @ not if ">> Player not found." Tell exit then ourPlayer @ "@/mail/po" DoRemoveProp ">> Set. " ourPlayer @ name strcat " may use mail functions." strcat Tell ; : DoSetLockout ( -- ) (* set: ourPlayer cannot use jmail *) ourPlayer @ not if ">> Player not found." Tell exit then ourPlayer @ "@/mail/no" "1" setprop ">> Set. " ourPlayer @ name strcat " may not use JMail." strcat Tell ; : DoSetLockoutNo ( -- ) (* set: ourPlayer may use jmail *) ourPlayer @ not if ">> Player not found." Tell exit then ourPlayer @ "@/mail/no" DoRemoveProp ">> Set. " ourPlayer @ name strcat " may use JMail." strcat Tell ; : DoSetMailboxDefaults ( -- )(* restore default settings for a mbox *) ourPlayer @ "@/mail/ag" prog "@/mail/ag" getprop setprop ourPlayer @ "@/mail/ma" prog "@/mail/ma" getprop setprop ">> Default mailbox settings restored for " ourPlayer @ name strcat "." strcat Tell ; : DoPadSysFolders ( s -- s' ) (* Inbox and Drafts get padded with leading spaces, for sorting *) dup "{#inbox|#in|inbox|in}" smatch if pop " Inbox" exit then dup "{#drafts|#dr|drafts|dr}" smatch if pop " Drafts" exit then ; : DoYesNo ( -- ) (* read yes|no from input; kill pid for no *) begin (* begin input-reading loop *) read "no" over stringpfx if (* no! ... kill process *) ">> Aborted." Tell pid kill then "yes" swap stringpfx if (* yes... keep going *) break else (* silly input... keep user prisoner here *) ">> Entry not understood." Tell then repeat (* end input-reading loop *) ; : DoYesNoI ( -- ) (* read yes|no from input; return true for yes *) begin (* begin input-reading loop *) read "yes" over stringpfx if (* yes... exit true *) pop 1 break then "no" swap stringpfx if 0 break (* no... exit false *) else ">> Entry not understood." Tell (* silly input... stay here *) then repeat ; : DoTimeToString ( -- s ) (* format systime to mailbox format str *) (* props are stored by systime, padded with leading zeros and stringified so alphabetic ordering will be correct, and catted with a random 3-digit number to avoid overwrites when there are two or more writes during the same second... pseudo- millisecond granularity in other words. Not foolproof, but actually better than catting with a dbref, since uploading a file with two messages to the same player could cause an overwrite if a dbref were used for enhanced granularity. *) systime intostr (* get current time *) 12 over strlen - (* pad with leading zeros to 12 character string *) begin dup while "0" rot strcat swap 1 - repeat pop "." strcat (* cat on random number string from 000 to 999 *) random 1000 % intostr dup strlen 3 > if 3 over strlen - begin dup while "0" rot strcat swap 1 - repeat pop strcat swap pop else "0000" strcat 3 strcut pop strcat then ; : DoGetDaysFromNow ( -- s ) (* format ourMessage as day-from-now *) ourMessage @ not if "On an unknown date, at " exit then ourMessage @ "." rinstr not if "On an unknown date, at " exit then systime 86400 / ourMessage @ dup "." rinstr strcut pop dup "/" rinstr pop atoi 86400 / - dup if dup 1 = if pop "Yesterday at " else intostr " days ago at " then else pop "Today at " then ; : DoPropToFldr ( s -- s' ) (* return fldr prop s as a fldr name *) dup "/" rinstr strcut swap pop ; : DoPropToMsg ( s -- s' ) (* return msg prop s as a msg string *) dup "/" rinstr strcut swap pop "" "#" subst ; : DoFldrToProp ( s -- s' ) (* return fldr name s as a fldr prop *) "@/mail/fo/" swap strcat ; : DoMsgToProp ( s -- s' ) (* return msg string as a msg prop *) "@/mail/fo/" ourFolder @ strcat "/" strcat swap strcat ; : DoMsgToList ( s -- s' ) (* return msg string as a list prop *) DoMsgToProp "#" strcat ; : DoFldrNotEmpty ( d s -- ) (* mark d's folder s not empty *) "@/mail/fo/" swap strcat "/000000000000.000#" strcat DoRemoveProp ; : DoCopyDir ( d1 s1 d2 s2 -- ) (* copy dir s1 on d1 to dir s2 on d2. do not copy subdirs *) 4 pick 4 pick propdir? if 3 pick "*/" smatch not if 3 pick "/" strcat 3 put then else pop pop pop pop exit then dup "*/" smatch not if "/" strcat then 3 pick 5 rotate 5 rotate 5 rotate 5 rotate dup 5 rotate 5 rotate 5 rotate 5 rotate 4 pick 4 pick nextprop dup 4 put 5 rotate 5 rotate 5 rotate 5 rotate begin 4 pick 4 pick getprop if pop over 7 pick 7 pick swap subst 4 pick 4 pick 4 pick 4 pick 4 rotate 4 rotate getprop setprop 4 pick 4 pick nextprop dup not if break then dup 4 put 5 put else 4 pick 4 pick dup "*/" smatch if dup strlen 1 - strcut pop then over over nextprop not if pop pop break then nextprop dup 4 put 5 put then pop over 7 pick 7 pick swap subst repeat pop pop pop pop pop pop pop pop ; : DoCopyProp ( d1 s1 d2 s2 -- )(* copy d1's prop s1 to d2's prop s2 *) 4 rotate 4 rotate getprop setprop ; : DoMoveProp ( d1 s1 d2 s2 -- )(* move d1's prop s1 to d2's prop s2 *) 4 pick 4 pick getprop setprop DoRemoveProp ; : DoRemoveDir ( d s -- ) (* remove dir s from d *) dup "*/" smatch not if "/" strcat then over over nextprop swap pop begin dup while over over nextprop 3 pick rot "" setprop repeat pop pop ; : DoMoveDir ( d1 s1 d2 s2 -- ) (* move dir s1 on d1 to dir s2 on d2; delete originals; do not copy or delete subdirs *) 4 pick 4 pick 4 pick 4 pick DoCopyDir pop pop DoRemoveDir ; : DoCopyToWL ( -- ) (* copy ourMessage to workspace list *) me @ "@/mail/fo/" ourFolder @ strcat "/" strcat ourMessage @ strcat "#" strcat me @ "@/mail/wl#" 4 pick 4 pick 4 pick 4 pick DoCopyDir DoCopyProp ; : DoEditWL ( -- ) (* edit workspace list *) me @ "@/mail/wl" DoCryptList me @ "@/mail/wl" DoEditList ourBoolean @ if me @ "@/mail/dl/-jmtemp-" DoRemoveProp me @ "@/mail/wl#" over over DoRemoveDir DoRemoveProp exit then me @ "@/mail/wl" DoCryptList ; : DoCheckMemUsed ( d -- i ) (* return size of d's mailbox in bytes *) (* only message contents and subject lines are counted against mailbox size; actual size used including property names and other data props is larger. *) 0 ourTotal ! dup "@/mail/fo/" nextprop begin (* begin folder-listing loop *) dup while over over "/" strcat nextprop begin (* begin message-listing loop *) dup while 3 pick over "/" strcat nextprop begin (* begin line-listing loop *) dup while 4 pick over getpropstr strlen ourTotal @ + ourTotal ! 4 pick swap nextprop repeat (* end line-listing loop *) pop 3 pick swap nextprop repeat (* end message-listing loop *) pop over swap nextprop repeat (* end folder-listing loop *) pop dup "@/mail/su/" nextprop begin dup while over over getpropstr strlen ourTotal @ + ourTotal ! over swap nextprop repeat pop pop ourTotal @ ; : DoCheckDbref ( d -- i ) (* return true if d is a player *) dup ok? if dup player? if pop 1 else pop 0 then else pop 0 then ; : DoCheckBoxFull ( d -- i ) (* return true if d's mbox is full *) me @ "W" flag? if pop 0 exit then (* wizzes can always mail to *) dup "@/mail/ma" getprop ourCounter ! (* get capacity *) ourCounter @ not if prog "@/mail/ma" getprop ourCounter ! then (* compare box size to capacity *) ourCounter @ if DoCheckMemUsed ourCounter @ > if 1 else 0 then else pop 0 then ; : DoCheckMsgExists ( d s1 s2 -- i ) (* return true if message s2 exists in d's folder s1 *) swap "@/mail/fo/" swap strcat "/" strcat swap strcat "#/" strcat nextprop if 1 else 0 then ; : DoCheckFolderExists ( s -- i ) (* return true if s is a folder *) me @ "@/mail/fo/" rot strcat "/" strcat nextprop if 1 else 0 then ; : DoCheckCanMailTo ( d -- i ) (* return true if mail can go to d *) dup "@/mail/bl" me @ REF-inlist? if (* check: being blocked? *) ">> Your mail to " swap name strcat " is being blocked." strcat Tell 0 exit then dup DoCheckBoxFull if (* check: box full? *) ">> " swap name strcat "'s mailbox is full." strcat Tell 0 exit then dup "@/mail/po" getpropstr if (* check: page-mail only? *) ">> " swap name strcat " cannot use mail." strcat Tell 0 exit then dup "@/mail/no" getpropstr if (* check: lock-out? *) ">> " swap name strcat " cannot use JMail." strcat Tell 0 exit then pop me @ DoCheckBoxFull if (* check: sender full? *) ">> Your mailbox is full." Tell ">> You must delete messages in order to send or receive mail." Tell 0 exit then 1 (* passed all checks: return true *) ; : DoCheckCanMailToQ ( d -- i ) (* return true if mail can go to d *) (* this version has no notification, for public function calls *) dup "@/mail/bl" me @ REF-inlist? if (* check: being blocked? *) pop 0 exit then dup DoCheckBoxFull if (* check: box full? *) pop 0 exit then dup "@/mail/po" getpropstr if (* check: page-mail only? *) pop 0 exit then dup "@/mail/no" getpropstr if (* check: lock-out? *) pop 0 exit then pop 1 (* passed all checks: return true *) ; : DoCopyMsgTo ( -- ) (* copy new msg from workdir to oplyr *) ourPlayer @ DoInitPlayer (* initialize target player if needed *) ourSubject @ DoCrypt1 ourSubject ! (* encrypt subject *) me @ "@/mail/wl#" (* copy data *) ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat "#" strcat 4 pick 4 pick 4 pick 4 pick DoCopyDir DoCopyProp ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat DoCryptList ourPlayer @ ourFolder @ DoFldrNotEmpty ourPlayer @ "@/mail/su/" ourMessage @ strcat ourSubject @ setprop ourPlayer @ "@/mail/fr/" ourMessage @ strcat me @ setprop ourPlayer @ "@/mail/un/" ourMessage @ strcat "1" setprop ourDistList @ if me @ "@/mail/dl/-jmtemp-" ourPlayer @ "@/mail/mu/" ourMessage @ strcat DoCopyProp then ourPlayer @ "@/mail/qu" getpropstr not if (* notify player *) ourPlayer @ ">> You have new mail." notify then ; : DoCopyMsgToP ( -- ) (* put a new message in inbox from prog call *) ourPlayer @ DoInitPlayer (* initialize target player if needed *) ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat "#" strcat over over "/1" strcat ourBody @ setprop "1" setprop ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat DoCryptList ourPlayer @ ourFolder @ DoFldrNotEmpty ourPlayer @ "@/mail/su/" ourMessage @ strcat ourSubject @ setprop ourPlayer @ "@/mail/fr/" ourMessage @ strcat me @ setprop ourPlayer @ "@/mail/un/" ourMessage @ strcat "1" setprop ourDistList @ if ourArg3 @ ourDistList @ ourPlayer @ "@/mail/mu/" ourMessage @ strcat DoCopyProp then ourPlayer @ "@/mail/qu" getpropstr not if (* notify player *) ourPlayer @ ">> You have new mail." notify then ; : DoCheckEmptyFldr ( d s -- i ) (* return true if d's fldr s is empty *) "@/mail/fo/" swap strcat "/" strcat nextprop dup if "000000000000.000" instr if 1 else 0 then else pop 1 (* nonexistent counts as empty *) then ; : DoTouchFldr ( s -- ) (* put place holder in ofldr if necessary *) me @ "@/mail/fo/" 3 pick strcat "/" strcat nextprop if pop else me @ "@/mail/fo/" rot strcat "/000000000000.000#" strcat "1" setprop then ; : DoDeleteMsg ( -- ) (* delete omsg in oplyr's ofldr *) ourPlayer @ "@/mail/fr/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/mu/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/su/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/un/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/ke/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/de/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/pm/" ourMessage @ strcat DoRemoveProp ourPlayer @ "@/mail/fo/" ourFolder @ strcat "/" strcat ourMessage @ strcat "#" strcat over over DoRemoveDir DoRemoveProp ourFolder @ DoTouchFldr ; : DoMsgToRecyclingBin ( -- ) (* copy ourMessage to recycling bin *) me @ "@/mail/rb/" DoRemoveDir (* clear old data *) me @ dup "@/mail/rb/ms#/" DoRemoveDir me @ "@/mail/fo/" ourFolder @ strcat "/" strcat (* copy msg *) ourMessage @ strcat "#" strcat me @ "@/mail/rb/ms#" 4 pick 4 pick 4 pick 4 pick DoCopyDir DoCopyProp me @ "@/mail/fr/" ourMessage @ strcat me @ "@/mail/rb/fr" DoCopyProp me @ "@/mail/su/" ourMessage @ strcat me @ "@/mail/rb/su" DoCopyProp me @ "@/mail/un/" ourMessage @ strcat me @ "@/mail/rb/un" DoCopyProp me @ "@/mail/mu/" ourMessage @ strcat me @ "@/mail/rb/mu" DoCopyProp me @ "@/mail/pm/" ourMessage @ strcat me @ "@/mail/rb/pm" DoCopyProp me @ "@/mail/ke/" ourMessage @ strcat me @ "@/mail/rb/ke" DoCopyProp me @ "@/mail/rb/ms" ourMessage @ setprop me @ "@/mail/rb/fo" ourFolder @ DoCrypt1 setprop ; : DoGetMsgRng ( d s -- s .. s' i ) ( return contents of d's folder s as a str rng *) 0 rot dup "@/mail/fo/" 5 rotate strcat "/" strcat nextprop dup not if pop pop exit then dup "00000000000.000" instr if pop pop exit then begin dup while dup dup "/" rinstr strcut swap pop "" "#" subst 4 pick 4 + -1 * rotate 3 pick 1 + 3 put over swap nextprop repeat pop pop ; : DoGetPlayerRng ( s -- d .. d' i )(* parse s into rng of plyr dbrefs *) (* do it differently for 1 player vs 2+; makes loops a little cleaner *) strip " " explode dup ourCounter ! dup 2 >= if begin dup while over .pmatch dup #-1 dbcmp if pop ">> Player " rot DoCapitalize strcat " not found." strcat Tell ourCounter @ 1 - ourCounter ! 1 - continue then dup #-2 dbcmp if pop ">> Player name `" rot DoCapitalize strcat " is ambiguous. I don't know who you mean." strcat Tell ourCounter @ 1 - ourCounter ! 1 - continue then over 2 + -1 * rotate swap pop 1 - continue repeat pop ourCounter @ else pop dup if dup .pmatch dup #-1 dbcmp if pop ">> Player " swap DoCapitalize strcat " not found." strcat Tell 0 exit then dup #-2 dbcmp if pop ">> Player name `" swap rot DoCapitalize strcat " is ambigous. I don't kow who you mean." strcat Tell 0 exit then swap pop 1 exit else pop 0 then then ; : DoGetFldrCount ( d -- i ) (* return count of d's folders *) 0 swap dup "@/mail/fo/" nextprop begin dup while 3 pick 1 + 3 put over swap nextprop repeat pop pop ; : DoGetMsgCount ( d s -- i ) (* return count of msgs in d's folder s *) 0 rot dup "@/mail/fo/" 5 rotate strcat "/" strcat nextprop dup not if pop pop exit then dup "000000000000.000" instr if pop pop exit then begin dup while 3 pick 1 + 3 put over swap nextprop repeat pop pop ; : DoGetUnreadInFldr ( d s -- i ) (* return count of unread msgs in d's folder s *) 0 rot dup "@/mail/fo/" 5 rotate strcat "/" strcat nextprop dup not if pop pop exit (* folder doesn't exist; return 0 *) then dup "000000000000.000" instr if (* folder is empty; return 0 *) pop pop exit then begin dup while ourPlayer @ "@/mail/un/" 3 pick dup "/" rinstr strcut swap pop "" "#" subst strcat getprop if 3 pick 1 + 3 put then over swap nextprop repeat pop pop ; : DoGetAllFromMe ( -- d..d' ) (* put all msgs from user on stack *) ourPlayer @ "@/mail/fr/" nextprop (* NOTE: NOT A RANGE; NO INT *) begin dup while ourPlayer @ over getprop me @ dbcmp if dup DoPropToMsg swap then ourPlayer @ swap nextprop repeat pop ; : DoGetMsgInfo ( -- ) (* show subj and date/time of omsg *) ourPlayer @ "@/mail/su/" ourMessage @ strcat getpropstr DoCrypt1 " (sent %D %l:%M:%S %p)" DoMsgToTime timefmt " " " 0" subst strcat ; : DoGetMsgEntry ( -- s )(* return s with subj, date, sender of omsg *) me @ "@/mail/un/" ourMessage @ strcat getprop if "*" else "" then me @ "@/mail/su/" ourMessage @ strcat getprop DoCrypt1 strcat " [" me @ "@/mail/fr/" ourMessage @ strcat getprop dup if DoSafeName else pop "" then strcat ", %D %l:%M:%S %p]" DoMsgToTime timefmt " " " 0" subst strcat me @ "@/mail/de/" ourMessage @ strcat getprop if "(" "[" subst ")" "]" subst then " " " " subst strcat ; : DoFindMsgByNumber ( s -- ) (* find msg s in ofldr; store in omsg *) atoi 0 ourCounter ! 0 ourMessage ! dup 0 <= if pop exit then ourPlayer @ ourFolder @ DoCheckEmptyFldr if pop exit then ourPlayer @ ourFolder @ DoFldrToProp "/" strcat nextprop begin dup while ourCounter @ 1 + ourCounter ! over ourCounter @ = if dup DoPropToMsg ourMessage ! break then ourPlayer @ swap nextprop repeat pop pop ; : DoFindMsgByName (s -- ) (* find msg s in ofldr; store in omsg *) 0 ourMessage ! me @ "@/mail/su/" nextprop begin dup while me @ over getpropstr DoCrypt1 3 pick stringpfx if dup me @ "@/mail/fo/" ourFolder @ strcat "/" strcat 3 pick dup "/" rinstr strcut swap pop strcat "#/" strcat nextprop if dup "/" rinstr strcut swap pop "" "#" subst ourMessage ! break then then me @ swap nextprop repeat pop pop ; : DoFindFldrByNumber ( s -- ) (* find fldr s; store in ofldr *) atoi 0 ourCounter ! 0 ourFolder ! dup 0 <= if pop exit then ourPlayer @ "@/mail/fo/" nextprop begin dup while ourCounter @ 1 + ourCounter ! over ourCounter @ = if dup dup "/" rinstr strcut swap pop ourFolder ! break then ourPlayer @ swap nextprop repeat pop pop ; : DoFindFldrByName ( s -- ) (* find fldr s; store in ofldr *) 0 ourFolder ! me @ "@/mail/fo/" nextprop begin dup while dup DoPropToFldr 3 pick stringpfx if dup DoPropToFldr ourFolder ! break then me @ swap nextprop repeat pop pop ; : DoFindFldrByMsg ( -- ) (* find folder that contains omsg *) ourPlayer @ "@/mail/fo/" nextprop begin dup while ourPlayer @ over "/" strcat ourMessage @ strcat "#/" strcat nextprop if dup dup "/" rinstr strcut swap pop ourFolder ! break then ourPlayer @ swap nextprop repeat pop ; : DoInsertStampLine ( -- ) (* insert a stampline in working list *) "On , wrote:" "%D" DoMsgToTime timefmt "" subst me @ "@/mail/fr/" ourMessage @ strcat getprop DoSafeName "" subst DoCrypt1 1 1 "@/mail/wl" me @ LMGR-InsertRange ; : DoInsertGT ( -- ) (* preface all lines of list s on d with > *) (* no need to encrypt: > is same both encrypted and unencrypted *) me @ "@/mail/wl#/" nextprop begin dup while me @ over over over getpropstr ">" swap strcat setprop me @ swap nextprop repeat pop ; : DoFormatReply ( -- ) (* format working list for reply *) (* list needs to be encrypted when formatting *) DoInsertGT DoInsertStampLine "E" "@/mail/wl" me @ LMGR-GetCount 1 + "@/mail/wl" me @ LMGR-PutElem ; : DoConfirmCancel ( -- i ) (* confirm user wants to cancel omsg *) me @ "@/mail/da" getprop if 1 exit then ">> " DoGetMsgInfo strcat Tell ">> Please confirm: You wish to cancel this message? (y/n)" Tell DoYesNoI ; : DoShowDistList ( -- ) (* show members of ourDistList, personal *) me @ "@/mail/dl/" ourDistList @ strcat getpropstr if ourDistList @ DoCapitalize " Members: " strcat me @ "@/mail/dl/" ourDistList @ strcat REF-list strcat Tell else ">> Distribution list not found." Tell then ; : DoShowGDistList ( -- ) (* show members of ourDistList, global *) prog "@/mail/dl/" ourDistList @ strcat getpropstr if ourDistList @ DoCapitalize " Members: " strcat prog "@/mail/dl/" ourDistList @ strcat REF-list strcat Tell else ">> Distribution list not found." Tell then ; : DoShowDistLists ( -- ) (* show personal distribution lists *) ourArg2 @ if DoShowDistList exit then DoLine "DISTRIBUTION LISTS:" Tell " " Tell me @ "@/mail/dl/" nextprop dup if begin dup while dup dup "/" rinstr strcut swap pop strip DoCapitalize ": " strcat me @ 3 pick REF-list strcat Tell me @ swap nextprop dup if " " Tell then repeat else pop "" Tell then DoLine ; : DoShowGDistLists ( -- ) (* show global distribution lists *) ourArg2 @ if DoShowGDistList exit then DoLine "GLOBAL DISTRIBUTION LISTS:" Tell " " Tell prog "@/mail/dl/" nextprop dup if begin dup while dup dup "/" rinstr strcut swap pop strip DoCapitalize ": " strcat prog 3 pick REF-list strcat Tell prog swap nextprop dup if " " Tell then repeat else pop "" Tell then DoLine ; : DoShowUsage ( -- ) (* show mailbox usage stats for ourPlayer *) 0 ourTotal ! "MAILBOX: " ourPlayer @ name strcat Tell ">> " 0 ourCounter ! ourPlayer @ "@/mail/su/" nextprop (* tally number of messages *) begin dup while ourCounter @ 1 + ourCounter ! ourPlayer @ over getpropstr strlen ourTotal @ + ourTotal ! ourPlayer @ swap nextprop repeat pop ourCounter @ intostr strcat ourCounter @ 1 = if " message " else " messages " then strcat (* tally unread messages *) 0 ourCounter ! ourPlayer @ "@/mail/un/" nextprop begin dup while ourCounter @ 1 + ourCounter ! ourPlayer @ swap nextprop repeat pop ourCounter @ if "(" strcat ourCounter @ intostr strcat " unread) " strcat then "in " strcat (* tally number of folders *) 0 ourCounter ! ourPlayer @ "@/mail/fo/" nextprop begin dup while ourCounter @ 1 + ourCounter ! ourPlayer @ swap nextprop repeat pop ourCounter @ intostr strcat " folders" strcat Tell (* tally memory used *) ourPlayer @ "@/mail/fo/" nextprop begin (* begin folder-reading loop *) dup while ourPlayer @ over "/" strcat nextprop begin (* begin message-reading loop *) dup while ourPlayer @ over "/" strcat nextprop begin (* begin line-reading loop *) dup while ourPlayer @ over getpropstr strlen ourTotal @ + ourTotal ! ourPlayer @ swap nextprop repeat (* end line-reading loop *) pop ourPlayer @ swap nextprop repeat (* end message-reading loop *) pop ourPlayer @ swap nextprop repeat (* end folder-reading loop *) pop ">> Memory used: " ourTotal @ intostr strcat " bytes" strcat ourPlayer @ "@/mail/ma" getprop dup not if pop prog "@/mail/ma" getprop then (* calculate percentage of mailbox capacity used *) dup if 1000 * ourTotal @ / 100000 swap / intostr "% capacity)" strcat " (" swap strcat strcat else pop then Tell (* show message-aging time *) ourPlayer @ "@/mail/ag" getprop if ourPlayer @ "@/mail/ag" getprop else prog "@/mail/ag" getprop then ourTime ! ourTime @ if ">> Messages are kept for " ourTime @ DoParseTimeInt strcat Tell then ; : DoDeleteLast ( -- ) (* delete last message read *) me @ "@/mail/lf" getpropstr (* check: do we have one to delete? *) me @ "@/mail/lm" getpropstr and not if ">> No 'last read' message is currently recorded." Tell exit then me @ me @ "@/mail/lf" getpropstr DoCrypt1 dup ourFolder ! me @ "@/mail/lm" getpropstr dup ourMessage ! DoCheckMsgExists not if ">> Your last read message no longer exists." Tell exit then ourBoolean @ not if (* confirm if necessary *) me @ "@/mail/da" getpropstr not if ">> " DoGetMsgEntry strcat Tell ">> Please confirm: You wish to delete this message? (y/n)" Tell DoYesNo then then (* move msg to recycling bin; delete original *) DoMsgToRecyclingBin DoDeleteMsg me @ "@/mail/lm" DoRemoveProp me @ "@/mail/lf" DoRemoveProp ">> Message deleted." Tell ; : DoEditInPlace ( -- ) (* move omsgs to worklist, edit, move back *) (* it's not really an edit in place: @q aborts or ) ( unexpected disconnnections could leave a message ) ( being edited in shambles. So, do all edits of ) ( existing messages by copying to a work space, ) ( then copy back as needed *) me @ "@/mail/un/" ourMessage @ strcat DoRemoveProp "FROM: " me @ "@/mail/fr/" (* show header info *) ourMessage @ strcat getprop DoSafeName strcat Tell "SUBJECT: " me @ "@/mail/su/" ourMessage @ strcat getpropstr DoCrypt1 strcat Tell " " Tell me @ "@/mail/fo/" ourFolder @ strcat "/" strcat ourMessage @ strcat "#" strcat me @ "@/mail/wl#" 4 pick 4 pick 4 pick 4 pick DoCopyProp DoCopyDir (* copy list *) me @ "@/mail/wl" DoCryptList me @ "@/mail/wl" DoEditList me @ "@/mail/wl#/" nextprop if (* finish up if we have a msg *) background me @ "@/mail/wl" DoCryptList me @ "@/mail/fo/" ourFolder @ strcat "/" strcat ourMessage @ strcat "#" strcat over over DoRemoveDir DoRemoveProp me @ "@/mail/wl#" me @ "@/mail/fo/" ourFolder @ strcat "/" strcat ourMessage @ strcat "#" strcat 4 pick 4 pick 4 pick 4 pick DoCopyProp DoCopyDir me @ ourFolder @ DoFldrNotEmpty me @ ourFolder @ DoTouchFldr else DoDeleteMsg then ; : DoSendToRange ( d .. d' i -- ) (* send ourMessage to range *) ourArg3 ! (* we're done with this var... borrow it as a counter *) begin ourArg3 @ while ourArg3 @ 1 - ourArg3 ! ourPlayer ! (* initialize target player, check ok *) ourPlayer @ DoInitPlayer ourPlayer @ DoCheckCanMailTo not if continue then (* copy data *) me @ "@/mail/wl#" ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat "#" strcat 4 pick 4 pick 4 pick 4 pick DoCopyDir DoCopyProp ourPlayer @ "@/mail/su/" ourMessage @ strcat ourSubject @ setprop ourPlayer @ "@/mail/fr/" ourMessage @ strcat me @ setprop ourPlayer @ "@/mail/un/" ourMessage @ strcat "1" setprop ourPlayer @ " Inbox" DoFldrNotEmpty ourDistList @ if me @ "@/mail/dl/-jmtemp-" ourPlayer @ "@/mail/mu/" ourMessage @ strcat DoCopyProp then ourPlayer @ "@/mail/qu" getpropstr not if ourPlayer @ ">> You have new mail." notify then repeat ; : DoParseFldrMsgArgs ( -- ) (* parse arg1 & arg2 as fldr & msg *) (* generic parsing for 1=2, inbox=4, 4=policy, drafts=faq, etc. *) ourArg2 @ if ourArg1 @ DoFindFldrByNumber ourFolder @ if ourArg2 @ DoFindMsgByNumber ourMessage @ if exit then ourArg2 @ DoFindMsgByName ourMessage @ if exit then then ourArg1 @ DoFindFldrByName ourFolder @ if ourArg2 @ DoFindMsgByNumber ourMessage @ if exit then ourArg2 @ DoFindMsgByName ourMessage @ if exit then then else ourArg1 @ DoFindMsgByNumber ourMessage @ if exit then ourArg1 @ DoFindMsgByName ourMessage @ if exit then ourArg1 @ DoFindFldrByNumber ourFolder @ if exit then ourArg1 @ DoFindFldrByName ourFolder @ if exit then then ; : DoParseArgs ( -- ) (* parse arg into #func, arg1, arg2, arg3 *) (* when function is called, ourFunc, ourArg2, and ourArg3 are undef; ourArg1 is command line arg *) (* this should work of all jmail commands; put in one place to ensure consistency *) ourArg1 @ not if exit then (* exit if no args *) ourArg1 @ "#*" smatch (* check: solo #func arg? ) ourArg1 @ " " instr not and if ourArg1 @ "#" smatch if ">> Sorry, unable to parse input." Tell pid kill then ourArg1 @ "" "#" subst DoPadSysFolders DoCheckFolderExists if ourArg1 @ "" "#" subst DoPadSysFolders ourArg1 ! else ourArg1 @ ourFunc ! 0 ourArg1 ! then exit then (* check: a leading #func arg? *) ourArg1 @ "#*" smatch ourArg1 @ " " instr and if ourArg1 @ dup " " instr strcut strip ourArg1 ! strip ourFunc ! "#all" ourArg1 @ stringpfx if ourArg1 @ dup " " instr strcut strip ourArg3 ! strip dup if ourArg1 ! else pop ourArg3 @ ourArg1 ! then then then (* check: a 'to' for arg3? *) ourFunc @ if (* apply only in #send and #move *) "#send" ourFunc @ stringpfx "#move" ourFunc @ stringpfx or if ourArg1 @ " to " instr if ourArg1 @ dup " to " rinstr strcut strip dup " " instr strcut swap pop strip ourArg3 ! strip ourArg1 ! then then then (* check: = but not arg1 & arg2? bad syntax *) ourArg1 @ "=" instr if ourArg1 @ dup "=" instr strcut strip ourArg2 ! strip dup strlen 1 - strcut pop strip ourArg1 ! ourArg1 @ not ourArg2 @ not or if ">> Sorry, unable to parse input." Tell pid kill then then (* check: #func and arg1 the same? bad syntax *) ourFunc @ if ourFunc @ ourArg1 @ smatch if ">> Sorry, unable to parse input." Tell pid kill then then (* pad Inbox and Drafts as needed *) ourFunc @ if ourFunc @ DoPadSysFolders ourFunc ! then ourArg1 @ if ourArg1 @ DoPadSysFolders ourArg1 ! then ourArg2 @ if ourArg2 @ DoPadSysFolders ourArg2 ! then ourArg3 @ if ourArg3 @ DoPadSysFolders ourArg3 ! then ; (****************** Begin command-handling functions ******************) : DoAsk ( -- ) (* set: jmail will prompt for deletions, etc *) me @ "@/mail/da" DoRemoveProp ">> Set. JMail will ask confirmation to delete, move, or cancel." Tell ; : DoAskNo ( -- ) (* set: jmail will not prompt for deletions, etc *) me @ "@/mail/da" "1" setprop ">> Set. JMail will not ask confirmation to delete, move, or cancel." Tell ; : DoBlock ( -- ) (* set: block mail from ourPlayer *) me @ "@/mail/bl" ourPlayer @ REF-add ">> Set. You are blocking mail from " ourPlayer @ name strcat "." strcat Tell ; : DoBlockNo ( -- ) (* set: don't block mail from ourPlayer *) me @ "@/mail/bl" ourPlayer @ REF-delete ">> Set. You are not blocking mail from " ourPlayer @ name strcat "." strcat Tell ; : DoCancel ( -- ) (* cancel an unread message to ourPlayer *) 0 ourCounter ! (* put unread msgs from user on stack as str range *) (* filter results to range of unread, unkept in inbox *) ourPlayer @ "@/mail/un/" nextprop begin dup while dup DoPropToMsg ourMessage ! ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat "#/" strcat nextprop ourPlayer @ "@/mail/fr/" ourMessage @ strcat getprop me @ dbcmp ourPlayer @ "@/mail/ke/" ourMessage @ strcat getprop not and and if ourMessage @ swap ourCounter @ 1 + ourCounter ! then ourPlayer @ swap nextprop repeat pop (* notify if no unread messages available *) ourCounter @ not if ">> " ourPlayer @ DoSafeName strcat " has no unread messages from you." strcat Tell exit then ourCounter @ 1 = if (* if 1 unread msg, show and confirm *) DoConfirmCancel if ourMessage ! else ">> Aborted." Tell exit then else (* if more than 1, show and get selection *) ">> " ourPlayer @ DoSafeName strcat " has " strcat ourCounter @ intostr strcat " unread messages from you:" strcat Tell ourCounter @ 0 begin dup ourCounter @ = not while 1 + dup 2 + pick ourMessage ! dup intostr ") " strcat dup strlen 3 = if " " strcat then DoGetMsgInfo strcat Tell repeat pop ourCounter @ begin ">> Enter number of message to cancel, or .q to quit" Tell read ".quit" over stringpfx if ">> Aborted." Tell exit then dup number? not if ">> Sorry, that's not a number." Tell pop continue then atoi dup 0 < over ourCounter @ > or if ">> Sorry, invalid selection." Tell pop continue then 2 + pick ourMessage ! DoConfirmCancel if break else ">> Aborted." Tell exit then repeat DoNukeStack then (* find out if user wants to save a copy *) ">> Do you want to save a copy in your Drafts folder? (y/n)" Tell DoYesNoI if ourPlayer @ "@/mail/fo/ Inbox/" ourMessage @ strcat "#" strcat me @ "@/mail/fo/ Drafts/" ourMessage @ strcat "#" strcat 4 pick 4 pick 4 pick 4 pick DoCopyDir DoCopyProp ourPlayer @ "@/mail/su/" ourMessage @ strcat me @ "@/mail/su/" ourMessage @ strcat DoCopyProp me @ "@/mail/fr/" ourMessage @ strcat me @ setprop me @ " Drafts" DoFldrNotEmpty then (* cancel message and notify *) DoDeleteMsg ">> Message cancelled." Tell ; : DoDeleteAll ( -- ) (* delete all unprotected messages *) me @ "@/mail/da" getpropstr not if (* confirm *) ">> Please confirm: You wish to delete " "all unprotected messages? (y/n)" strcat Tell DoYesNo then me @ "@/mail/fo/" nextprop begin (* begin folder-listing loop *) dup while dup DoPropToFldr ourFolder ! me @ ourFolder @ DoGetMsgRng begin (* begin message-listing loop *) dup while swap ourMessage ! me @ "@/mail/ke/" ourMessage @ strcat getpropstr not if DoDeleteMsg (* delete message *) then 1 - repeat (* end message-listing loop *) pop me @ swap nextprop repeat (* end folder-listing loop *) pop ">> All unprotected messages deleted." Tell ; : DoDelete ( -- ) (* delete ourMessage *) me @ "@/mail/da" getpropstr not if (* confirm *) ">> " DoGetMsgEntry strcat Tell ">> Please confirm: You wish to delete this message? (y/n)" Tell DoYesNo then (* copy to recycling bin for undelete *) DoMsgToRecyclingBin DoDeleteMsg (* then delete it *) ">> Message deleted." Tell ; : DoCreateFldr ( s -- ) (* create folder s *) DoCapitalize (* check: valid name? *) "{block|cancel|delete|folder|folders|glist|global|keep|" "list|move|nuke|preempt|reply|send|tidy|usage|warn|-jmtemp-}" strcat over smatch if ">> Sorry, that folder name would conflict with a mail option." Tell exit then dup number? if ">> Sorry, numbers cannot be used as folder names." Tell exit then dup "#*" smatch if ">> Sorry, folder names cannot begin an # octothorpe." Tell exit then dup " " instr if ">> Sorry, folder names cannot include spaces." Tell exit then dup "/" instr if ">> Sorry, folder names cannot includes slashes." Tell exit then (* confirm *) me @ "@/mail/da" getpropstr not if ">> Please confirm: You wish to create a new folder called " over strcat "? (y/n)" strcat Tell DoYesNo then (* create it *) me @ over DoTouchFldr ">> Folder created." Tell ; : DoDeleteFldr ( -- ) (* delete ourFolder and its contents *) ourArg1 @ number? me @ "@/mail/da" getpropstr not or if (* confirm *) ourArg1 @ number? me @ "@/mail/da" getpropstr and if ">> Special case... over-riding don't-ask... " Tell then ">> Please confirm: You wish to delete " ourFolder @ strip strcat " and all its contents? (y/n)" strcat Tell DoYesNo then (* put folder contents on stack as string range *) me @ ourFolder @ DoGetMsgRng begin (* delete range *) dup while swap ourMessage ! DoDeleteMsg 1 - repeat pop (* delete folder *) me @ "@/mail/fo/" ourFolder @ strcat DoRemoveDir ourFolder @ " Inbox" smatch ourFolder @ " Drafts" smatch or if ourFolder @ DoTouchFldr then ">> Folder deleted." Tell ; : DoDeleteDistList ( s -- ) (* delete distlist s *) me @ "@/mail/dl/" 3 pick strcat getprop if me @ "@/mail/da" getpropstr not if ">> Please confirm: You wish to delete your " over DoCapitalize strcat " distribution list? (y/n)" strcat Tell DoYesNo then me @ "@/mail/dl/" rot strcat DoRemoveProp ">> Deleted." Tell exit then me @ "W" flag? if prog "@/mail/dl/" 3 pick strcat getprop if me @ "@/mail/da" getpropstr not if ">> Please confirm: You wish to delete the " over DoCapitalize strcat " global distribution list? (y/n)" strcat Tell DoYesNo then prog "@/mail/dl/" rot strcat DoRemoveProp ">> Deleted." Tell then then ; : DoEditMsg ( -- ) (* edit ourMessage *) (* if msg doesn't exist, start new one *) me @ ourFolder @ ourMessage @ DoCheckMsgExists not if ">> Composing new message in Drafts folder... " Tell ">> SUBJECT: " ourSubject @ strcat Tell me @ "@/mail/wl" DoEditList me @ "@/mail/wl#/" nextprop if background me @ "@/mail/wl" DoCryptList me @ "@/mail/wl#" me @ "@/mail/fo/ Drafts/" ourMessage @ strcat "#" strcat 4 pick 4 pick 4 pick 4 pick DoCopyProp DoCopyDir me @ "@/mail/su/" ourMessage @ strcat ourSubject @ DoCrypt1 setprop me @ "@/mail/fr/" ourMessage @ strcat me @ setprop me @ ourFolder @ DoFldrNotEmpty then ">> Done." Tell else DoEditInPlace (* otherwise edit existing one in place *) then me @ "@/mail/wl#" over over DoRemoveDir DoRemoveProp ; : DoDistLists ([drng]-- )(* add or remove members from user's dlists *) ourBoolean @ if me @ "@/mail/dl/-jmtemp-" REF-allrefs ourArg1 @ DoCapitalize ourDistList ! begin dup while me @ "@/mail/dl/" ourDistList @ strcat 4 pick REF-delete ">> " rot DoSafeName strcat " removed from your " strcat ourDistList @ strcat " distribution list." strcat Tell 1 - repeat else me @ "@/mail/dl/-jmtemp-" REF-allrefs ourArg1 @ DoCapitalize ourDistList ! begin dup while me @ "@/mail/dl/" ourDistList @ strcat 4 pick REF-add ">> " rot DoSafeName strcat " added to your " strcat ourDistList @ strcat " distribution list." strcat Tell 1 - repeat then pop ; : DoGDistLists ( [drng] -- ) (* add or remove from global dlists *) me @ "W" flag? not ourArg2 @ not or if (* just show *) DoShowGDistLists exit then me @ "W" flag? not if (* check permission to manipulate *) ">> Permission denied." Tell exit then ourBoolean @ if begin dup while prog "@/mail/dl/" ourDistList @ strcat 4 pick REF-delete ">> " rot DoSafeName strcat " removed from the " strcat ourDistList @ strcat " global distribution list." strcat Tell 1 - repeat else begin dup while prog "@/mail/dl/" ourDistList @ strcat 4 pick REF-add ">> " rot DoSafeName strcat " added to the " strcat ourDistList @ strcat " global distribution list." strcat Tell 1 - repeat then pop ; : DoKeep ( -- ) (* mark ourMessage keep *) me @ "@/mail/ke/" ourMessage @ strcat "1" setprop me @ "@/mail/de/" ourMessage @ strcat DoRemoveProp ">> Message marked 'keep'." Tell ; : DoKeepNo ( -- ) (* mark ourMessage don't-keep *) me @ "@/mail/ke/" ourMessage @ strcat DoRemoveProp ">> Message marked 'don't keep'." Tell ; : DoAskHelp ( s -- ) (* show help for #ask *) " " Tell "JMail: Ask" Tell " " Tell "By default, JMail asks for confirmation before deleting or moving " "a message, folder, or distribution list. You can turn off these " "confirmations with #!ask, and turn them back on with #ask." strcat strcat Tell " " Tell DoComString "#ask " strcat DoDot39 " JMail will ask for confirmations" strcat Tell DoComString "#!ask " strcat DoDot39 " Jmail will not ask for confirmations" strcat Tell " " Tell pop ; : DoBlockHelp ( s -- ) (* show help for #block *) " " Tell "JMail: Block" Tell " " Tell "You may block mail from a particular player with the " "#block option. You may not send messages to someone while you " "are blocking them. Messages from wizards will not be blocked." strcat strcat Tell " " Tell DoComString "#block " strcat DoDot39 " Block messages from " strcat Tell DoComString "#!block " strcat DoDot39 " Don't block messages from " strcat Tell " " Tell pop ; : DoCancelHelp ( s -- ) (* show help for #cancel *) " " Tell "JMail: Cancel" Tell " " Tell "You may cancel or recall unread messages that you sent to a player. " "Note that a message will be considered 'read' in this context if " "the target player has moved, touched, or kept the message, " "even if it has not actually been read." strcat strcat strcat Tell " " Tell "The syntax for cancelling a message is '" command @ strcat " #cancel '. If the player only has one unread message from " "you, this message will automatically be selected. If there is more " "than one unread message, you will be presented with a list to " "choose from. When cancelling a message, you have the option to " "save a copy in your Drafts folder, to be edited and remailed. " "See also #help for '#view'." strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString "#cancel " strcat DoDot39 " Cancel an unread message to " strcat Tell DoComString "#view " strcat DoDot39 " View messages you sent to " strcat Tell " " Tell pop ; : DoDeleteHelp ( s -- ) (* show help for #delete *) " " Tell "JMail: Delete" Tell " " Tell "You may use the #delete option to delete either messages, folders, " "or distribution lists. The #delete option by itself will delete " "the last message you read. #Delete #all will delete all " "unprotected messages. The shorthand option #dn will delete the " "last message you read, and display your next unread message. " "The #!delete option will recover or undelete your last-deleted " "message; #!delete does not recover folders or distribution lists." strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString "#delete " strcat DoDot39 " Delete the last message you read" strcat Tell DoComString "#delete " strcat DoDot39 " Delete in you Inbox" strcat Tell DoComString "#delete = " strcat DoDot39 " Delete from " strcat Tell DoComString "#delete #all " strcat DoDot39 " Delete all unprotected messages" strcat Tell DoComString "#delete " strcat DoDot39 " Delete and its contents" strcat Tell DoComString "#delete " strcat DoDot39 " Delete distribution list " strcat Tell DoComString "#dn " strcat DoDot39 " Delete last msg and show next unread" strcat Tell DoComString "#!delete " strcat DoDot39 " Recover the last message you deleted" strcat Tell " " Tell pop ; : DoEditHelp ( s -- ) (* show help for #edit *) " " Tell "JMail: Edit" Tell " " Tell "You may edit either existing or new messages, to later be forwarded " "with the #send option. JMail will first look for a message that " "matches what you specify. If none is found, it will open the list " "editor to create a new message stored in your Drafts folder." strcat strcat strcat Tell " " Tell DoComString "#edit " strcat DoDot39 " Edit in your Inbox" strcat Tell DoComString "#edit = " strcat DoDot39 " Edit in " strcat Tell DoComString "#edit " strcat DoDot39 " Edit new message on " strcat Tell " " Tell pop ; : DoFolderHelp ( s -- ) (* show help for #folders *) " " Tell "JMail: Folders" Tell " " Tell "Folders provide a way to organize your mail. You will always have " "at least two folders: Inbox and Drafts. Additional ones may be " "created with the #folders option. Inbox will always appear first " "in your list of folders, and Drafts will always appear second. " "Additional folders will appear after these, in alphabetical " "order. Inbox is the default folder, so messages " "in your Inbox may be specified simply by number or by the " "first few letters of their subject. To work with messages in other " "folders, use syntax =. If a player's name conflicts " "with a folder name, specify the folder by number or put an # " "octothorpe before the name." strcat strcat strcat strcat strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString " " strcat DoDot39 " List contents of " strcat Tell DoComString "= " strcat DoDot39 " Show from " strcat Tell DoComString "#folders " strcat DoDot39 " List your folders" strcat Tell DoComString "#folders " strcat DoDot39 " Create new folder " strcat Tell DoComString "#move [=] to " strcat DoDot39 " Move to " strcat Tell DoComString "#delete " strcat DoDot39 " Delete and its contents" strcat Tell " " Tell pop ; : DoKeepHelp ( s -- ) (* show help for #keep *) " " Tell "JMail: Keep" Tell " " Tell "By default, messages are cleared from your box after a time period " "specified with the #usage option, and when you use the #delete #all " "option. You can protect a message from being deleted in this way " "with the #keep option. Messages protected with #keep may still be " "explicitly deleted, but will not be deleted without specific action " "by you. The formatting of entries in folder contents lists provides " "visual indication of a message's status: expired, unprotected entries " "will have (parentheses) around the sender and time information; " "unexpired or kept messages will have [square brackets]. Expired, " "unprotected messages will be deleted from your box within one hour." strcat strcat strcat strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString "#keep " strcat DoDot39 " Protect or keep " strcat Tell DoComString "#!keep " strcat DoDot39 " Don't protect or keep " strcat Tell " " Tell pop ; : DoListHelp ( s -- ) (* show help for #list *) " " Tell "Jmail: Distribution Lists" Tell " " Tell "Distribution lists are permanently stored lists of players. " "You may create personal distribution lists, or use global " "ones. When mailing a message (either a new message or one that " "is being forwarded with #send), you may send to a player, to a " "group of players, or to a personal or global distribution list. " "JMail will also resolve personal " prog "cmd-page" getprop if "and global " strcat then "page aliases (see page #help2)" strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString "#list " strcat DoDot39 " Show your distribution lists" strcat Tell DoComString "#list = " strcat DoDot39 " Add to personal list " strcat Tell DoComString "#!list = " strcat DoDot39 " Remove from personal list " strcat Tell DoComString "#glist " strcat DoDot39 " Show global distribution lists" strcat Tell DoComString "#glist = " strcat DoDot39 " Add to global [wiz]" strcat Tell DoComString "#!glist = " strcat DoDot39 " Remove from global [wiz]" strcat Tell " " Tell "Multiple players may be specified when adding to or removing from " "lists." strcat Tell pop ; : DoMoveHelp ( s -- ) (* show help for #move *) " " Tell "JMail: Move" Tell " " Tell "Use the #move option to transfer a message from one folder to " "another. Moving a message does not affect its #keep status, but " "-- if it was unread -- it will no longer be in the sequence of " "messages displayed with #new, and may not be recalled with #cancel." strcat strcat strcat Tell " " Tell DoComString "#move to " strcat DoDot39 " Move from Inbox to " strcat Tell DoComString "#move = to " strcat DoDot39 " Move from to " strcat Tell " " Tell pop ; : DoNewHelp ( s -- ) (* show help for #new *) " " Tell "JMail: New" Tell " " Tell "The #new option displays your first or next new or unread message. " "Repeatedly entering '" command @ strcat " #new' would " strcat "cycle through all unread messages in your Inbox. Note also the " "shorthand options #dn, which deletes the last message you read " "and displays your next new or unread message, and #na (or #new " "#all), which displays all new messages." strcat strcat strcat strcat strcat Tell " " Tell DoComString "#new " strcat DoDot39 " Show next new or unread message" strcat Tell DoComString "#dn " strcat DoDot39 " Delete last message; show next unread" strcat Tell DoComString "#new #all " strcat DoDot39 " Show all new messages" strcat Tell DoComString "#na " strcat DoDot39 " Show all new messages" strcat Tell " " Tell pop ; : DoPreemptHelp ( s -- ) (* show help for #preempt *) " " Tell "JMail: Preempt" Tell " " Tell "The #preempt option is a wizard-only administrative command " "controlling JMail's integration with the page #mail system. " "When #preempt is on, mail sent with the standard 'page #mail " "=' syntax will be intercepted by JMail, and sent " "to the target player's mailbox, where it can be edited, sent, etc., " "like any other JMail message. Typing 'page #mail' by itself when " "#preempt is on displays all messages sent with page #mail in " "standard page #mail format." strcat strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString "#preempt " strcat DoDot39 " JMail preempts page #mail [wiz]" strcat Tell DoComString "#!preempt " strcat DoDot39 " Page #mail functions normally [wiz]" strcat Tell " " Tell pop ; : DoQuellHelp ( s -- ) (* show help for #quell *) " " Tell "JMail: Quell" Tell " " Tell "By default, JMail notifies you when you have new mail. Use the " "#quell option to turn these notifications on and off." strcat Tell " " Tell DoComString "#quell " strcat DoDot39 " Notify when you have new mail" strcat Tell DoComString "#!quell " strcat DoDot39 " Don't notify when you have new mail" strcat Tell " " Tell pop ; : DoReplyHelp ( s -- ) (* show help for #reply *) " " Tell "JMail: Reply" Tell " " Tell "You can respond to any message sent to you with the #reply option. " "If the original message was sent to multiple players, #reply #all " "-- or the shorthand #ra -- " "will send the reply to the originator and all recipients. When " "using #reply, you will be asked if you want to include the original " "message in your reply." strcat strcat strcat strcat strcat Tell " " Tell DoComString "#reply " strcat DoDot39 " Reply to in Inbox" strcat Tell DoComString "#reply = " strcat DoDot39 " Reply to in " strcat Tell DoComString "#reply #all " strcat DoDot39 " Reply to all recipients of " strcat Tell DoComString "#ra " strcat DoDot39 " Reply to all recipeints of " strcat Tell DoComString "#reply #all = " strcat DoDot39 " Reply to all recipients of " strcat Tell " " Tell pop ; : DoSendHelp ( s -- ) (* show help for #send *) " " Tell "JMail: Send" Tell " " Tell "You can forward a message from any of your folders to a player, " "group of players, or distribution list with the #send option. " "JMail will ask you if you want to edit the message before sending " "it. The existing contents of the message will be marked with > " "greater-than signs at the start of each line, and prefaced with " "the line 'On , said:', unless the message being " "sent is in your Drafts folder." strcat strcat strcat strcat strcat strcat Tell " " Tell DoComString "#send to " strcat DoDot39 " Send from Inbox to " strcat Tell DoComString "#send = to " strcat DoDot39 " Send from to " strcat Tell " " Tell "Multiple players or a distribution list may be substituted for " " in the above commands." strcat Tell pop ; : DoTouchHelp ( s -- ) (* show help for #touch *) " " Tell "JMail: Touch" Tell " " Tell "The #touch option can be used to mark a single message or all " "the messages in a folder as 'read'. The #!touch option marks " "messages as 'unread'." strcat strcat Tell " " tell DoComString "#touch " strcat DoDot39 " Mark contents of Inbox 'read'" strcat Tell DoComString "#!touch " strcat DoDot39 " Mark contents of Inbox 'unread'" strcat Tell DoComString "#touch " strcat DoDot39 " Mark in Inbox 'read'" strcat Tell DoComString "#!touch " strcat DoDot39 " Mark in Inbox 'unread'" strcat Tell DoComString "#touch " strcat DoDot39 " Mark contents of 'read'" strcat Tell DoComString "#!touch " strcat DoDot39 " Mark contents of 'unread'" strcat Tell DoComString "#touch = " strcat DoDot39 " Mark in 'read'" strcat Tell DoComString "#!touch = " strcat DoDot39 " Mark in 'unread'" strcat Tell " " Tell pop ; : DoUsageHelp ( s -- ) (* show help for #usage *) " " Tell "JMail: Usage" Tell " " Tell "The #usage option displays current statistics for your mailbox. " "You may also use it to set the aging-time for your messages to " "a value equal to or lower than the global parameter. Wizards may " "use this option to view players' mailbox statistics, to set global " "or individual limits on mailbox size, or set global or individual " "aging-times. Wizards, see also program header comments." strcat strcat strcat strcat strcat Tell " " Tell DoComString "#usage " strcat DoDot39 " Display your mailbox statistics" strcat Tell DoComString "#usage