@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  <or whatever>
  
  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 <player[s]>=<subject> .... Send mail on <subject> to <player[s]>
  mail .......................... List messages in your Inbox
  mail <msg> .................... Show message <msg> in Inbox
  mail <folder> ................. List contents of <folder> 
  mail <folder>=<msg> ........... Show <msg> in folder <folder>
  mail #delete <msg> ............ Delete <msg>
  mail #reply <msg> ............. Reply to <msg>
  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 <num> <units> where
  <num> is
  a positive number and <units> 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 <player>=page-only ... <player> may not use mail
    mail #usage <player>=!page-only .. <player> may use mail
  
  To completely block a player from using JMail, use the #usage option
  with the following syntax:
    
    mail #usage <player>=lock-out ..... <player> may not use JMail
    mail #usage <player>=lock-out .... <player> 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 "<unknown>"
  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 `<num>  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 "<unknown>" 
  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 <somedate>  *)
      (* 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 "<none defined>" 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 "<none defined>" 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 <player> " strcat DoDot39
  " Block messages from <player>" strcat Tell
  DoComString "#!block <player> " strcat DoDot39
  " Don't block messages from <player>" 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 <player>' 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 <player> " strcat DoDot39
  " Cancel an unread message to <player>" strcat Tell
  DoComString "#view <player> " strcat DoDot39
  " View messages you sent to <player>" 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 <msg> " strcat DoDot39
  " Delete <msg> in you Inbox" strcat Tell
  DoComString "#delete <folder>=<msg> " strcat DoDot39
  " Delete <msg> from  if
    dup 24 strcut pop
    dup " " rinstr strcut pop strip
    "... " strcat
  then
  " (page mail)" strcat DoCrypt1 ourSubject !
  DoCrypt1
;
 
: DoConvertPageMail  (  --  )  (* convert existing page mail to jmail *)
  
  me @ "_page/mail#/" nextprop ourCounter !
  begin
    ourCounter @ while
    me @ ourCounter @ getpropstr DoConvPageMailString
    me @ "@/mail/fo/  Inbox/" ourMessage @ strcat "#" strcat
    over over "1" setprop "/1" strcat rot setprop
    me @ "@/mail/fr/" ourMessage @ strcat ourPlayer  @ setprop
    me @ "@/mail/su/" ourMessage @ strcat ourSubject @ setprop
    me @ "@/mail/un/" ourMessage @ strcat "1" setprop
    me @ "@/mail/pm/" ourMessage @ strcat "1" setprop
    me @ ourCounter @ nextprop
    me @ ourCounter @ DoRemoveProp
    dup ourCounter !
  repeat
  pop
  me @ "_page/mail#" DoRemoveProp
;
  
: DoPageMailCheck  (  --  )   (* see if user has page mail to convert *)
  
  me @ "_page/mail#/" nextprop if
    DoConvertPageMail
  then
;
 
: DoPagePreemptOn  (  --  )        (* set up action to handle p #mail *)
  
  prog "@/mail/pmh" getprop dup if
    ">>  Jmail's prop settings show that #preempt is already on." Tell
    ">>  If this is incorrect, type '@ent #" prog atoi intostr strcat
    "' to find any actions with " strcat Tell
    ">>  '#mail' in their names, recycle those by dbref, and then type "
    Tell ">>  " Tell
    ">>  @set #" prog intostr strcat "=@/mail/1206: " strcat 
    Tell ">>  " Tell
    ">>  You should then be able to do #preempt normally." Tell
    pop exit
  then
  
  me @ "@/mail/da" getpropstr not if
    ">>  Please confirm: You want JMail to handle page mail? (y/n)"
    Tell DoYesNo
  then
  #0 "page #mail;pag #mail;pa #mail;p #mail" newexit
  prog "@/mail/pmh" 3 pick setprop
  dup "@/mail/1206" "Do Not Remove This Property" setprop
  dup "@/mail/ve" "1.0" setprop
  prog setlink
  
  ">>  Set. JMail will now handle page #mail." Tell
;
 
: DoPagePreemptOff  (  --  )                (* recycle p #mail action *)
  
  prog "@/mail/pmh" getprop dup not if
    pop "page #mail" match
  then
                     (* flag if problem finding the page #mail action *)
  dup                       not if 1 ourBoolean ! else 
  dup ok?                   not if 1 ourBoolean ! else
  dup exit?                 not if 1 ourBoolean ! else
  dup getlink prog dbcmp    not if 1 ourBoolean ! else
  dup "@/mail/1206" getprop not if 1 ourBoolean ! else
  then then then then then 
  
  ourBoolean @ if
    ">>  Unable to locate page #mail action." Tell
    ">>  Cmd-page should be handling page #mail now." Tell
    ">>  Check actions linked to cmd-page and jmail.muf." Tell
    prog "@/mail/pmh" DoRemoveProp
    pop 
  else
    me @ "@/mail/da" getpropstr not if
      ">>  Please confirm: You want cmd-page to handle page mail? (y/n)"
      Tell DoYesNo
    then
    recycle
    prog "@/mail/pmh" DoRemoveProp
    ">>  Set. Cmd-page will now handle page #mail." Tell
  then
;
  
: DoReadPageMail  (  --  )      (* read page #mail, real and emulated *)
  
  me @ "@/mail/un/" nextprop
  begin
    dup while
    dup dup DoPropToMsg ourMessage !
    DoShowPageMailMsg
    me @ swap nextprop
  repeat
  pop
  "Done." Tell
;
  
: DoSendPageMail  (  --  )          (* send a page #mail emulated msg *)
  
  DoTimeToString ourMessage !
  ourPlayer @ "@/mail/fo/  Inbox/" ourMessage @ strcat "#" strcat
  over over
  "1" setprop
  "/1" strcat ourArg3 @ setprop
  
  ourPlayer @ "@/mail/su/" ourMessage @ strcat 
  ourSubject @ "E(dTMuEKTV1)" strcat setprop
  ourPlayer @ "@/mail/fr/" ourMessage @ strcat me @ setprop
  ourPlayer @ "@/mail/pm/" ourMessage @ strcat "1" setprop
  ourPlayer @ "@/mail/un/" ourMessage @ strcat "1" setprop
  ourPlayer @ "  Inbox" DoFldrNotEmpty
  me @ "@/mail/dl/-jmtemp-" getpropstr " " instr if
    me @ "@/mail/dl/-jmtemp-"
    ourPlayer @ "@/mail/mu/" ourMessage @ strcat DoCopyProp
  then
  ourPlayer @
  "You sense that you have new mail from %p%. Use 'page #mail' to read."
  me @ name "%p%" subst
  notify
;
 
: DoAddressPageMail  (  --  )     (* find players to page mail; setup *)
  
  ourArg2 @                             (* do all pmails as distlists *)
  dup ":," stringpfx over ":'" stringpfx or if
    1 strcut swap pop
    me @ name swap strcat
  else
    dup ":" stringpfx if 
      1 strcut swap pop
      me @ name " " strcat swap strcat 
    then
  then
  DoCrypt1 ourArg3 !
  " " explode
  begin
    dup while
    swap
    DoGetPlayers
    ourPlayer @ if
      me @ "@/mail/dl/-jmtemp-" ourPlayer @ REF-add
      "-jmtemp-" ourDistList !
    then
    1 -
  repeat
  pop
  ourDistList @ if
    ourSubject @ dup strlen 24 > if
      24 strcut pop strip
      dup " " rinstr strcut pop
      "..." strcat
    then
    DoCrypt1 ourSubject !
    me @ "@/mail/dl/-jmtemp-" REF-allrefs      (* put dbrefs on stack *)
    begin
      dup while
      swap ourPlayer !                            (* check: can send? *)
      ourPlayer @ DoCheckCanMailTo not if 
        1 - continue
      then
      DoSendPageMail                                   (* if so, send *)
      1 -
    repeat
    pop             (* notify regarding any players we couldn't match *)
    me @ "@/mail/bn" getpropstr if 
      me @ "@/mail/bn" getpropstr dup " " smatch not if
        strip
        dup " " instr if
          dup dup " " rinstr strcut
          strip swap strip "~~and~~" strcat swap strcat
          "\" \"" " " subst
          "\" and \"" "~~and~~" subst
          "\"" strcat "\"" swap strcat
          "I don't recognize the players " swap strcat
          Tell 
        else
          "I don't recognize the player \"" 
          swap strcat "\"" strcat Tell
        then
      else
        pop
      then
    then
    "You page-mail \"" 
    ourArg2 @ ":," stringpfx ourArg2 @ ":'" stringpfx or if
      ourArg2 @ 1 strcut swap pop
      me @ name swap strcat
      ourArg2 !
    else
      ourArg2 @ ":" stringpfx if
        ourArg2 @ 1 strcut swap pop
        me @ name " " strcat swap strcat
        ourArg2 !
      then
    then
    ourArg2 @ strcat 
    "\" to " strcat
    me @ "@/mail/dl/-jmtemp-" REF-list strcat "." strcat Tell
  else
    "page: #mail format: 'page #mail <players>=<message> Tell
  then
  
  me @ "@/mail/dl/-jmtemp-" DoRemoveProp
  me @ "@/mail/bn"          DoRemoveProp
;
 
(****************** Begin command-parsing functions *******************) 
 
: DoParseAsk  (  --  )                           (* parse #ask option *)
  
  DoMailPermCheck
  DoAsk
;
  
: DoParseAskNo  (  --  )                        (* parse #!ask option *)
  
  DoMailPermCheck
  DoAskNo
;
 
: DoParseBlock  (  --  )                       (* parse #block option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoSynString " #block <player>" strcat Tell exit
  then
  
  ourArg1 @ DoGetPlayer
  ourPlayer @ if
    DoBlock
  else
    ">>  Player not found." Tell
  then
;
 
: DoParseBlockNo  (  --  )                    (* parse #!block option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoSynString " #!block <player>" strcat Tell exit
  then
  
  ourArg1 @ DoGetPlayer
  ourPlayer @ if
    DoBlockNo
  else
    ">>  Player not found." Tell
  then
;
 
: DoParseCancel  (  --  )                     (* parse #cancel option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoSynString " #cancel <player>" strcat Tell exit
  then
  
  ourArg1 @ DoGetPlayer
  ourPlayer @ if
    DoCancel
  else
    ">>  Player not found." Tell
  then
;
  
: DoParseDelete  (  --  )                     (* parse #delete option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoDeleteLast exit
  then
  
  ourArg3 @ if
    "#all" ourArg3 @ stringpfx if
      DoDeleteAll exit
    then
  then
   
  DoParseFldrMsgArgs
  ourMessage @ if
    DoDelete
  else
    ourFolder @ if
      DoDeleteFldr
    else
      me @ "@/mail/dl/" ourArg1 @ strcat getpropstr if
        ourArg1 @ DoDeleteDistList
      else
        me @ "W" flag? if
          prog "@/mail/dl/" ourArg1 @ strcat getpropstr if
            ourArg1 @ DoDeleteDistList
          else
            ">>  Message, folder, or distribution list not found." Tell
          then
        else
          ">>  Message, folder, or distribution list not found." Tell
        then
      then
    then
  then
;
 
: DoParseDeleteNew  (  --  )                      (* parse #dn option *)
  
  DoMailPermCheck
  1 ourBoolean !                  (* flag: we're deleting and reading *)
  DoDeleteLast
  DoNextNew
;
  
: DoParseEdit  (  --  )                         (* parse #edit option *)
 
  DoMailPermCheck
  ourArg1 @ not if
    DoSynString " #edit [<folder>=]<message>" strcat Tell exit
  then
  
  DoParseFldrMsgArgs
  ourMessage @ if
    DoEditMsg
  else
    DoTimeToString ourMessage !
    ourArg1 @ 38 strcut pop ourSubject !
    " Drafts" ourFolder !
    DoEditMsg
  then
;
 
: DoParseFolders (  --  )                    (* parse #folders option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoShowFldrs
  else
    ourArg1 @ DoFindFldrByName ourFolder @ if
      DoShowFldrContents
    else
      ourArg1 @ DoFindFldrByNumber ourFolder @ if
        DoShowFldrContents
      else
        ourArg1 @ DoCreateFldr
      then
    then
  then
;
 
: DoParseGList  (  --  )                       (* parse #glist option *)
   
  DoMailPermCheck
  me @ "W" flag? if
    ourArg1 @ if
      ourArg2 @ if
        ourArg1 @ DoCapitalize ourDistList !
        ourArg2 @ DoGetPlayerRng
        DoGDistLists
      else
        ourArg1 @ DoCapitalize ourDistList !
        DoShowGDistList
      then
    else
      DoShowGDistLists
    then
  else
    DoShowGDistLists
  then
;
  
: DoParseGListNo  (  --  )                    (* parse #!glist option *)
  
  DoMailPermCheck
  1 ourBoolean !                (* flag: we're deleting from the list *)
  DoParseGList
;
   
: DoParseHelp  (  --  )                         (* parse #help option *)
  
  ourArg1 @ if
    ourArg1 @ "" "#" subst
    "ask"     over stringpfx if DoAskHelp     else
    "block"   over stringpfx if DoBlockHelp   else
    "cancel"  over stringpfx if DoCancelHelp  else
    "edit"    over stringpfx if DoEditHelp    else
    "delete"  over stringpfx if DoDeleteHelp  else
    "folders" over stringpfx if DoFolderHelp  else
    "glist"   over stringpfx if DoListHelp    else
    "keep"    over stringpfx if DoKeepHelp    else
    "lists"   over stringpfx if DoListHelp    else
    "move"    over stringpfx if DoMoveHelp    else
    "new"     over stringpfx if DoNewHelp     else
    "preempt" over stringpfx if DoPreemptHelp else
    "quell"   over stringpfx if DoQuellHelp   else
    "reply"   over stringpfx if DoReplyHelp   else
    "send"    over stringpfx if DoSendHelp    else
    "touch"   over stringpfx if DoTouchHelp   else
    "usage"   over stringpfx if DoUsageHelp   else
    "view"    over stringpfx if DoViewHelp    else
    ">>  Sorry, no help available for '" 
    swap strcat "'." strcat Tell 
    then then then then then then then then then
    then then then then then then then then then
  else
    DoHelp
  then
;
 
: DoParseKeep  (  --  )                         (* parse #keep option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoSynString " #keep [<folder>=]<message>" strcat Tell exit
  then
  
  DoParseFldrMsgArgs
  ourMessage @ if
    DoKeep
  else
    ">>  Message not found." Tell exit
  then
;
 
: DoParseKeepNo  (  --  )                      (* parse #!keep option *)
  
  DoMailPermCheck
  ourArg1 @ not if
    DoSynString " #!keep [<folder>=]<message>" strcat Tell exit
  then
  
  DoParseFldrMsgArgs
  ourMessage @ if
    DoKeepNo
  else
    ">>  Message not found." Tell
  then
;
  
: DoParseList  (  --  )                        (* parse #glist option *)
   
  DoMailPermCheck
  me @ "W" flag? if
    ourArg1 @ if
      ourArg2 @ if
        ourArg1 @ DoCapitalize ourDistList !
        ourArg2 @ DoGetPlayers
        DoDistLists
      else
        ourArg1 @ DoCapitalize ourDistList !
        DoShowDistList
      then
    else
      DoShowDistLists
    then
  else
    ourArg1 @ if
      ourArg1 @ DoCapitalize ourDistList !
      ourArg2 @ DoGetPlayers
      DoDistLists
    else
      DoShowDistLists
    then
  then
;
  
: DoParseListNo  (  --  )                      (* parse #!list option *)
  
  DoMailPermCheck
  1 ourBoolean !                (* flag: we're deleting from distlist *)
  DoParseList
;
 
: DoParseSendPageMail  (  --  )     (* parse a send page mail command *)
  
  ourArg2 @ if
    ourArg2 @ 38 strcut pop ourSubject !
  else
    "page: #mail format: 'page #mail <players>=<message>'" Tell exit
  then
  
  me @ "@/mail/po" getpropstr not if
    me @ DoCheckBoxFull if
     ">>  Your mailbox is full." Tell
      ">>  You must delete messages before you can send or receive mail."
      Tell exit
    then
  then
                                          (* pretend this is cmd-page *)
  ourArg1 @ DoAddressPageMail
; 
  
: DoParsePageMail  (  --  )                (* parse page mail command *)
  
  ourArg1 @ if
    DoParseSendPageMail
  else
    DoReadPageMail
  then
;
  
: DoParseMailTo  (  --  )                        (* parse for new msg *)
  
  trig "@/mail/1206" getpropstr if
    DoParsePageMail exit
  then
   
  DoMailPermCheck
  ourArg1 @ ourArg2 @ and not if
    DoSynString " <player>=<subject>" strcat Tell exit
  then
  
  ourArg2 @ 38 strcut pop ourSubject !
  
  me @ DoCheckBoxFull if
    ">>  Your mailbox is full." Tell
    ">>  You must delete messages before you can send or receive mail."
    Tell exit
  then
  
  0 ourBoolean !
  ourDistList @ if
    DoMailToList exit
  then
  
  ourPlayer @ if
    DoMailTo exit
  else
    ">>  Player not found." Tell
  then
;
 
: DoParseMove  (  --  )                         (* parse #move option *)
  
  DoMailPermCheck
  ourArg3 @ not if
    DoSynString " #move [<folder>=]<message> to