Mail Distribution Center (#47)(an instance of Root Class made by Hacker)     This is the database of mailing-list/mail-folder objects.      The basic procedure for creating a new list/folder is to create a child of $mail_recipient (Generic Mail Recipient) assign it a suitable name&aliases, set a suitable .mail_forward/.mail_notify (or create suitable :mail_forward() and :mail_notify() verbs) and then teleport it here.            Avaliable aliases:       Mail Distribution Center contains New-Prog-Log, Quota-Log, Site-Locks, Wizard-Mail, graffiti, Core-Updates, Player-Requests, Player-Creation-Log, Quota-Requests, Text, cuffs, boot-log, coolthingstoread, I'd-Rather-Be-Smashing-the-MOO and web. VERB SOURCE CODE: resolve_addr:
"resolve(name,from,seen,prevrcpts,prevnotifs) => {rcpts,notifs} or E_INVARG";
"resolve(list,from,seen,prevrcpts,prevnotifs) => {bogus,rcpts,notifs}";
"Given either an address (i.e., objectid) or a list of such, traces down all .mail_forward
lists and .mail_notify to determine where a message should actually go and who should
be told about it. Both forms take previous lists of recipients/notifications and
add only those addresses that weren't there before. `seen' is the stack of addresses
we are currently resolving (for detecting loops). The first form returns E_INVARG
if `name' is invalid. The second form returns all invalid addresses in the `bogus'
list but still does the appropriate search on the remaining addresses.";
recip = args[1];
from = args[2];
if (length(args) == 2)
args = {@args, {}, {}, {}};
endif
seen = args[3];
sofar = args[4..5];
if (typeof(recip) == LIST)
bogus = {};
for r in (recip)
result = this:resolve_addr(r, from, seen, @sofar);
if (result)
sofar = result;
else
bogus = setadd(bogus, r);
endif
endfor
return {bogus, @sofar};
else
fwd = include_recip = 0;
if ((recip == $nothing) || (recip in seen))
return sofar;
elseif ((!valid(recip)) || ((!(is_player(recip) || $object_utils:isa(recip, $mail_recipient)))
|| (typeof(fwd = recip:mail_forward(from)) != LIST)))
"recip is a non-player non-mailing-list/folder or forwarding is screwed.";
if (typeof(fwd) == STR)
player:tell(fwd);
endif
return E_INVARG;
elseif (fwd)
if (r = recip in fwd)
include_recip = 1;
fwd = listdelete(fwd, r);
endif
result = this:resolve_addr(fwd, recip, setadd(seen, recip), @sofar);
if (bogus = result[1])
player:tell(recip.name, "(", recip, ")'s .mail_forward list includes
the following bogus entr", (length(bogus) > 1) ? "ies: " | "y: ", $string_utils:english_list(bogus));
endif
sofar = result[2..3];
else
include_recip = 1;
endif
if ((ticks_left() < 1000) || (seconds_left() < 2))
suspend(0);
endif
biffs = sofar[2];
for n in (this:mail_notify(recip, from))
if (valid(n))
if (i = $list_utils:iassoc_suspended(n, biffs))
biffs[i] = setadd(biffs[i], recip);
else
biffs = {{n, recip}, @biffs};
endif
endif
if ((ticks_left() < 1000) || (seconds_left() < 2))
suspend(0);
endif
endfor
return {include_recip ? setadd(sofar[1], recip) | sofar[1], biffs};
endif
.
sends_to:
"sends_to(from,addr,rcpt[,seen]) ==> true iff mail sent to addr passes through rcpt.";
if ((addr = args[2]) == (rcpt = args[3]))
return 1;
elseif (!(addr in (seen = (length(args) >= 4) ? args[4] | {})))
seen = {@seen, addr};
for a in ((typeof(fwd = this:mail_forward(addr, @args[1] ? {} | {args[1]})) ==
LIST) ? fwd | {})
if (this:sends_to(addr, a, rcpt, seen))
return 1;
endif
$command_utils:suspend_if_needed(0);
endfor
endif
return 0;
.
send_message:
"send_message(from,rcpt-list,hdrs,msg) -- formats and sends a mail message. hders
is either the text of the subject line, or a {subject,{reply-to,...}} list.";
"Return E_PERM if from isn't owned by the caller.";
"Return {0, @invalid_rcpts} if rcpt-list contains any invalid addresses. No mail
is sent in this case.";
"Return {1, @actual_rcpts} if successful.";
from = args[1];
to = args[2];
hdrs = args[3];
msg = args[4];
if ($perm_utils:controls(caller_perms(), from))
text = $mail_agent:make_message(from, to, hdrs, msg);
return this:raw_send(text, to, from);
else
return E_PERM;
endif
.
raw_send:
"WIZARDLY";
"raw_send(text,rcpts,sender) -- does the actual sending of a message. Assumes that
text has already been formatted correctly. Decides who to send it to and who wants
to be notified about it and does so.";
"Return {E_PERM} if the caller is not entitled to use this verb.";
"Return {0, @invalid_rcpts} if rcpts contains any invalid addresses. No mail is
sent in this case.";
"Return {1, @actual_rcpts} if successful.";
text = args[1];
rcpts = args[2];
from = args[3];
if (typeof(rcpts) != LIST)
rcpts = {rcpts};
endif
if (!(caller in {$mail_agent, $mail_editor}))
return {E_PERM};
elseif (bogus = (resolve = this:resolve_addr(rcpts, from))[1])
return {0, bogus};
else
this:touch(rcpts);
actual_rcpts = resolve[2];
biffs = resolve[3];
results = {};
for recip in (actual_rcpts)
if ((ticks_left() < 10000) || (seconds_left() < 2))
player:notify(tostr("...", recip));
suspend(1);
endif
if (typeof(e = recip:receive_message(text, from)) in {ERR, STR})
"...receive_message bombed...";
player:notify(tostr(recip, ":receive_message: ", e));
e = 0;
elseif ((!is_player(recip)) || (!e))
"...not a player or receive_message isn't giving out the message number";
"...no need to force a notification...";
elseif (i = $list_utils:iassoc(recip, biffs))
"...player-recipient was already getting a notification...";
"...make sure notification includes a mention of him/her/itself.";
if (!(recip in listdelete(biffs[i], 1)))
biffs[i][2..1] = {recip};
endif
else
"...player-recipient wasn't originally being notified at all...";
biffs = {{recip, recip}, @biffs};
endif
results = {@results, e};
endfor
fork (0)
for b in (biffs)
if ((ticks_left() < 10000) || (seconds_left() < 2))
suspend(1);
endif
if ($object_utils:has_callable_verb(b[1], "notify_mail"))
mnums = {};
for r in (listdelete(b, 1))
mnums = {@mnums, (rn = r in actual_rcpts) && results[rn]};
endfor
b[1]:notify_mail(from, listdelete(b, 1), mnums);
endif
endfor
endfork
if ((len = length(actual_rcpts)) > 10)
len = 10;
endif
if (len)
this.total_recipients[len] = this.total_recipients[len] + 1;
endif
return {1, @actual_rcpts};
endif
.
mail_forward mail_notify:
who = args[1];
if ($object_utils:has_verb(who, verb))
return who:(verb)(@listdelete(args, 1));
else
return {};
endif
.
touch:
"touch(name or list,seen) => does .last_used_time = time() if we haven't already
touched this in the last hour";
recip = args[1];
seen = (length(args) >= 2) ? args[2] | {};
if (typeof(recip) == LIST)
for r in (recip)
result = this:touch(r, seen);
$command_utils:suspend_if_needed(0);
endfor
else
if (((!valid(recip)) || (recip in seen)) || ((!is_player(recip)) && (!($mail_recipient
in $object_utils:ancestors(recip)))))
"recip is neither a player nor a mailing list/folder";
else
if (fwd = this:mail_forward(recip))
this:touch(fwd, {@seen, recip});
endif
if (!is_player(recip))
recip.last_used_time = time();
endif
endif
endif
.
look_self:
player:tell_lines(this.description);
for c in (this.contents)
c:look_self();
endfor
.
acceptable: "Only allow mailing lists/folders in here and only if their names aren't already taken."; what = args[1]; return ($object_utils:isa(what, $mail_recipient) && this:check_names(@what.aliases)) && (what:description() != parent(what):description()); . check_names:
"...make sure the list has at least one usable name.";
"...make sure none of the aliases are already taken.";
ok = 0;
for a in (args)
if (index(a, " "))
elseif (rp = $mail_agent:reserved_pattern(a))
player:tell("Mailing list name \"", a, "\" uses a reserved pattern: ", rp[1]);
return 0;
elseif (valid(p = $mail_agent:match(a, #-1)) && (a in p.aliases))
player:tell("Mailing list name \"", a, "\" in use on ", p.name, "(", p, ")");
return 0;
else
ok = 1;
endif
endfor
return ok;
.
match_old match:
":match(string) => mailing list object in here that matches string.";
":match(string,player) => similar but also matches against player's private mailing
lists (as kept in .mail_lists).";
if (!(string = args[1]))
return $nothing;
elseif (string[1] == "*")
string = string[2..length(string)];
endif
if (valid(o = $string_utils:literal_object(string)) && ($mail_recipient in $object_utils:ancestors(o)))
return o;
elseif (rp = this:reserved_pattern(string))
return rp[2]:match_mail_recipient(string);
else
if (valid(who = {@args, player}[2]) && (typeof(use = who.mail_lists) == LIST))
use = {@this.contents, @use};
else
use = this.contents;
endif
partial = 1;
string = strsub(string, "_", "-");
for l in (use)
if (string in l.aliases)
return l;
endif
if (partial != $ambiguous_match)
for a in (l.aliases)
if ((index(a, string) == 1) && (!index(a, " ")))
if (partial)
partial = l;
elseif (partial != l)
partial = $ambiguous_match;
endif
endif
endfor
endif
endfor
return partial && $failed_match;
endif
.
match_recipient:
":match_recipient(string[,meobj]) => $player or $mail_recipient object that matches
string. Optional second argument (defaults to player) is returned in the case string==\"me\"
and is also used to obtain a list of private $mail_recipients to match against.";
string = args[1];
me = {@args, #-1}[2];
if (valid(me) && ($failed_match != (o = me:my_match_recipient(string))))
return o;
elseif (!string)
return $nothing;
elseif ((string[1] == "*") && (string != "*"))
return this:match(@args);
elseif (string[1] == "`")
args[1][1..1] = "";
return $string_utils:match_player(@args);
elseif (valid(o = $string_utils:match_player(@args)) || (o == $ambiguous_match))
return o;
else
return this:match(@args);
endif
.
match_failed:
match_result = args[1];
string = args[2];
cmd_id = {@args, ""}[3] || "";
if (match_result == $nothing)
player:tell(cmd_id, "You must specify a valid mail recipient.");
elseif (match_result == $failed_match)
player:tell(cmd_id, "There is no mail recipient called \"", string, "\".");
elseif (match_result == $ambiguous_match)
if ((nostar = index(string, "*") != 1) && (lst = $player_db:find_all(string)))
player:tell(cmd_id, "\"", string, "\" could refer to ", (length(lst) > 20)
? tostr("any of ", length(lst), " players") | $string_utils:english_list($list_utils:map_arg(2,
$string_utils, "pronoun_sub", "%n (%#)", lst), "no one", " or "), ".");
else
player:tell(cmd_id, "I don't know which \"", nostar ? "*" | "", string, "\"
you mean.");
endif
elseif (!valid(match_result))
player:tell(cmd_id, match_result, " does not exist.");
else
return 0;
endif
return 1;
.
make_message:
":make_message(sender,recipients,subject/replyto,body)";
" => message in the form as it will get sent.";
from = args[1];
fromline = tostr(valid(from) ? from.name | "???", " (", from, ")");
if (typeof(recips = args[2]) != LIST)
recips = {recips};
endif
recips = this:name_list(@recips);
if (typeof(hdrs = args[3]) != LIST)
subj = hdrs;
replyto = valid(from) && ((!is_player(from)) && ((!$object_utils:isa(from, $mail_recipient))
&& this:name(from.owner)));
else
subj = hdrs[1];
replyto = {@hdrs, 0}[2] && this:name_list(@hdrs[2]);
endif
body = args[4];
if (typeof(body) != LIST)
body = body ? {body} | {};
endif
return {time(), fromline, recips, subj || " ", @replyto ? {"Reply-to: " + replyto}
| {}, "", @body};
.
name:
what = args[1];
if (!valid(what))
name = "???";
elseif ($object_utils:has_callable_verb(what, "mail_name"))
name = what:mail_name();
else
name = what.name;
endif
return tostr(strsub(strsub(name, "(", ""), ")", ""), " (", what, ")");
.
name_list: return $string_utils:english_list($list_utils:map_arg(this, "name", args), "no one"); . parse_address_field:
":parse_address_field(string) => list of objects";
"This is the standard routine for parsing address lists that appear in From:, To:
and Reply-To: lines";
objects = {};
string = args[1];
while (e = index(string, ")"))
if ((s = rindex(string[1..e], "(#")) && (#0 != (o = toobj(string[s + 2..e - 1]))))
objects = {@objects, o};
endif
string = string[e + 1..length(string)];
endwhile
return objects;
.
display_seq_full:
":display_seq_full(msg_seq[,preamble]) => {cur, last-read-date}";
"This is the default message display routine.";
"Prints entire messages on folder (caller) to player. msg_seq is the handle returned
by :parse_message_seq(...) and indicates which messages should be printed. preamble,
if given will precede the output of the message itself, in which case the message
number will be substituted for \"%d\". Returns the number of the final message in
the sequence (which can be then used as the new current message number).";
set_task_perms(caller_perms());
preamble = {@args, ""}[2];
cur = date = 0;
for x in (msgs = caller:messages_in_seq(args[1]))
cur = x[1];
date = x[2][1];
player:display_message(preamble ? strsub(preamble, "%d", tostr(cur)) | {}, player:msg_text(@x[2]));
if ((ticks_left() < 500) || (seconds_left() < 2))
suspend(0);
endif
endfor
return {cur, date};
.
display_seq_headers:
":display_seq_headers(msg_seq[,cur[,last_read_date]])";
"This is the default header display routine.";
"Prints a list of headers of messages on caller to player. msg_seq is the handle
returned by caller:parse_message_seq(...). cur is the player's current message.
last_read_date is the date of the last of the already-read messages.";
set_task_perms(caller_perms());
msg_seq = args[1];
cur = {@args, 0}[2];
last_old = {@args, $maxint, $maxint}[3];
keep_seq = {@$seq_utils:contract(caller:kept_msg_seq(), $seq_utils:complement(msg_seq,
1, caller:length_all_msgs())), $maxint};
k = 1;
mcount = 0;
width = player:linelen() || 79;
for x in (msgs = caller:messages_in_seq(msg_seq))
if (keep_seq[k] <= (mcount = mcount + 1))
k = k + 1;
endif
annot = ((d = x[2][1]) > last_old) ? "+" | ((k % 2) ? " " | "=");
line = tostr($string_utils:right(x[1], 4, (cur == x[1]) ? ">" | " "), ":", annot,
" ", caller:msg_summary_line(@x[2]));
player:tell(line[1..min(width, length(line))]);
if ((ticks_left() < 500) || (seconds_left() < 2))
suspend(0);
endif
endfor
player:tell("----+");
.
rm_message_seq:
":rm_message_seq(msg_seq) removes the given sequence of from folder (caller)";
"...removed messages are saved in .messages_going for possible restoration.";
set_task_perms(caller_perms());
old = caller.messages;
new = save = nums = {};
next = 1;
for i in [1..length(seq = args[1]) / 2]
if ($command_utils:running_out_of_time())
player:tell("... rmm ", old[next][1] - 1);
suspend(0);
endif
start = seq[(2 * i) - 1];
new = {@new, @old[next..start - 1]};
save = {@save, {start - next, old[start..(next = seq[2 * i]) - 1]}};
nums = {@nums, old[start][1], old[next - 1][1] + 1};
endfor
new = {@new, @old[next..length(old)]};
$command_utils:suspend_if_needed(0, "... rmm ...");
save_kept = $seq_utils:intersection(caller.messages_kept, seq);
$command_utils:suspend_if_needed(0, "... rmm ...");
new_kept = $seq_utils:contract(caller.messages_kept, seq);
$command_utils:suspend_if_needed(0, "... rmm ...");
caller.messages_going = save_kept ? {save_kept, save} | save;
caller.messages = new;
caller.messages_kept = new_kept;
if ($object_utils:has_callable_verb(caller, "_fix_last_msg_date"))
caller:_fix_last_msg_date();
endif
return $seq_utils:tostr(nums);
.
undo_rmm:
":undo_rmm() restores previously deleted messages in .messages_going to .messages.";
set_task_perms(caller_perms());
old = caller.messages;
going = caller.messages_going;
new = seq = {};
last = 0;
next = 1;
"there are two possible formats here:";
"OLD: {{n,msgs},{n,msgs},...}";
"NEW: {kept_seq, {{n,msgs},{n,msgs},...}}";
if (going && ((!going[1]) || (typeof(going[1][2]) == NUM)))
kept = going[1];
going = going[2];
else
kept = {};
endif
for s in (going)
new = {@new, @old[last + 1..last + s[1]], @s[2]};
last = last + s[1];
seq = {@seq, next + s[1], next = length(new) + 1};
endfor
caller.messages = {@new, @old[last + 1..length(old)]};
caller.messages_going = {};
caller.messages_kept = $seq_utils:union(kept, $seq_utils:expand(caller.messages_kept,
seq));
if ($object_utils:has_callable_verb(caller, "_fix_last_msg_date"))
caller:_fix_last_msg_date();
endif
return seq;
.
expunge_rmm list_rmm:
":list_rmm() displays contents of .messages_going.";
":expunge_rmm() destroys contents of .messages_going once and for all.";
"... both return the number of messages in .messages_going.";
set_task_perms(caller_perms());
cmg = caller.messages_going;
if (cmg && ((!cmg[1]) || (typeof(cmg[1][2]) == NUM)))
kept = cmg[1];
cmg = cmg[2];
else
kept = {};
endif
if (verb == "expunge_rmm")
caller.messages_going = {};
count = 0;
for s in (cmg)
count = count + length(s[2]);
endfor
return count;
elseif (!cmg)
return 0;
else
msgs = seq = {};
next = 1;
for s in (cmg)
msgs = {@msgs, @s[2]};
seq = {@seq, next = next + s[1], next = next + length(s[2])};
endfor
kept = {@$seq_utils:contract(kept, $seq_utils:complement(seq, 1, $seq_utils:last(seq))),
$maxint};
k = 1;
mcount = 0;
for x in (msgs)
if (kept[k] <= (mcount = mcount + 1))
k = k + 1;
endif
player:tell($string_utils:right(x[1], 4), ":", (k % 2) ? " " | "= ", caller:msg_summary_line(@x[2]));
if ((ticks_left() < 500) || (seconds_left() < 2))
suspend(0);
endif
endfor
if (msgs)
player:tell("----+");
endif
return length(msgs);
endif
.
renumber:
":renumber([cur]) -- assumes caller is a $mail_recipient or a $player.";
"...renumbers caller.messages, doing a suspend() if necessary.";
"...returns {number of messages,new cur}.";
set_task_perms(caller_perms());
cur = {@args, 0}[1];
caller.messages_going = {};
"... blow away @rmm'ed messages since there's no way to tell what their new numbers
should be...";
msgs = caller.messages;
if (cur)
cur = $list_utils:iassoc_sorted(cur, msgs);
endif
while (1)
"...find first out-of-sequence message...";
l = 0;
r = (len = length(msgs)) + 1;
while ((r - 1) > l)
if (msgs[i = (r + l) / 2][1] > i)
r = i;
else
l = i;
endif
endwhile
"... r == first out-of-sequence, l == last in-sequence, l+1 == r ...";
if (l >= len)
return {l, cur};
endif
"...renumber as many messages as we have time for...";
chunk = {};
while (((r <= len) && (ticks_left() > 3000)) && (seconds_left() > 2))
for x in (msgs[r..min(r + 9, len)])
chunk = {@chunk, {r, x[2]}};
r = r + 1;
endfor
endwhile
caller.messages = {@msgs[1..l], @chunk, @msgs[r..len]};
if (chunk)
player:tell("...(renumbering ", l + 1, " -- ", r - 1, ")");
suspend(0);
else
player:tell("You lose. This message collection is just too big.");
return;
endif
"... have to be careful since new mail may be received at this point...";
msgs = caller.messages;
endwhile
.
msg_summary_line:
":msg_summary_line(@msg) => date/from/subject as a single string.";
body = ("" in {@args, ""}) + 1;
if ((body > length(args)) || (!(subject = args[body])))
subject = "(None.)";
endif
date = ctime(args[1])[5..16];
from = args[2];
if (args[4] != " ")
subject = args[4];
endif
return tostr(date, " ", $string_utils:left(from, 20), " ", subject);
.
msg_summary_line(slow):
":msg_summary_line(@msg) => date/from/subject as a single string.";
blank = "" in {@args, ""};
if ((blank < length(args)) && args[blank + 1])
if (length(args[blank + 1]) <= 28)
subject = args[blank + 1];
else
subject = args[blank + 1][1..25] + "...";
endif
else
subject = "(None.)";
endif
if (typeof(args[1]) == NUM)
date = ctime(args[1])[5..16];
start = 2;
else
date = "?Date?";
start = 1;
endif
if ((start == 2) && (index(args[2], "From:") != 1))
from = args[2];
if (args[4] != " ")
subject = args[4];
endif
else
from = "?From?";
for line in (args[start..blank - 1])
if (index(line, "Date:") == 1)
date = $string_utils:triml(line[6..length(line)])[5..16];
elseif (index(line, "From:") == 1)
from = $string_utils:triml(line[6..length(line)]);
elseif (index(line, "Subject:") == 1)
s = $string_utils:triml(line[9..length(line)]);
if (s != "(None.)")
subject = s;
endif
endif
endfor
endif
(subject && (length(subject) > 32)) && (subject = subject[1..32]);
return tostr(date, " ", $string_utils:left(from, 20), " ", subject);
.
parse_message_seq: "parse_message_seq(strings,cur[,last_old])"; "This is the default _parse_from _parse_to:
":_parse_from(string with |'s in it) => object list";
":_parse_to(string with |'s in it) => object list";
" for from:string and to:string items in :parse_message_seq";
if (verb == "_parse_to")
match_obj = fail_obj = this;
match_verb = "match_recipient";
fail_verb = "match_failed";
else
match_obj = $string_utils;
match_verb = "match_player";
fail_obj = $command_utils;
fail_verb = "player_match_failed";
endif
plist = {};
for w in ($string_utils:explode(args[1], "|"))
if (fail_obj:(fail_verb)(p = match_obj:(match_verb)(w), w))
p = $string_utils:literal_object(w);
if ((p == $failed_match) || (!$command_utils:yes_or_no("Continue? ")))
return "Bad address list: " + args[1];
endif
endif
plist = setadd(plist, p);
endfor
return plist;
.
_parse_date:
words = $string_utils:explode(args[1], "-");
if (length(words) == 1)
time = $time_utils:from_day(words[1], -1);
if (typeof(time) == ERR)
time = "weekday expected.";
endif
elseif ((!words) || ((length(words) > 3) || ((!tonum(words[1])) || (E_TYPE == (year
= $code_utils:tonum({@words, "-1"}[3]))))))
time = "Date should be of the form `5-Jan', `5-Jan-92', `Wed',`Wednesday'";
else
day = tonum(words[1]);
time = $time_utils:dst_midnight($time_utils:from_month(words[2], -1, day));
if (length(words) == 3)
thisyear = tonum(ctime(time)[21..24]);
if (100 > year)
year = (thisyear + 50) - (((thisyear - year) + 50) % 100);
endif
time = $time_utils:dst_midnight($time_utils:from_month(words[2], (year -
thisyear) - (year <= thisyear), day));
endif
endif
return time;
.
new_message_num:
":new_message_num() => number that the next incoming message will receive.";
set_task_perms(caller_perms());
new = (msgs = caller.messages) ? msgs[length(msgs)][1] + 1 | 1;
if (rmsgs = caller.messages_going)
if ((!rmsgs[1]) || (typeof(rmsgs[1][2]) == NUM))
rmsgs = rmsgs[2];
endif
lbrm = rmsgs[length(rmsgs)][2];
return max(new, lbrm[length(lbrm)][1] + 1);
else
return new;
endif
.
length_all_msgs: set_task_perms(caller_perms()); return length(caller.messages); . length_date_le:
set_task_perms(caller_perms());
date = args[1];
msgs = caller.messages;
if ((r = length(caller.messages)) < 25)
for l in [1..r]
if (msgs[l][2][1] > date)
return l - 1;
endif
endfor
return r;
else
l = 1;
while (l <= r)
if (date < msgs[i = (r + l) / 2][2][1])
r = i - 1;
else
l = i + 1;
endif
endwhile
return r;
endif
.
length_date_gt:
set_task_perms(caller_perms());
date = args[1];
msgs = caller.messages;
if ((len = length(caller.messages)) < 25)
for r in [0..len - 1]
if (msgs[len - r][2][1] <= date)
return r;
endif
endfor
return len;
else
l = 1;
r = len;
while (l <= r)
if (date < msgs[i = (r + l) / 2][2][1])
r = i - 1;
else
l = i + 1;
endif
endwhile
return len - r;
endif
.
length_num_le: ":length_num_le(num) => number of messages in folder numbered <= num"; set_task_perms(caller_perms()); return $list_utils:iassoc_sorted(args[1], caller.messages); . exists_num_eq: ":exists_num_eq(num) => index of message in folder numbered == num"; set_task_perms(caller_perms()); return (i = $list_utils:iassoc_sorted(args[1], caller.messages)) && ((caller.messages[i][1] == args[1]) && i); . from_msg_seq:
":from_msg_seq(object or list[,mask])";
" => msg_seq of messages from any of these senders";
set_task_perms(caller_perms());
if (typeof(plist = args[1]) != LIST)
plist = {plist};
endif
mask = {@args, {1}}[2];
i = 1;
fseq = {};
for msg in (caller.messages)
if ((!mask) || (i < mask[1]))
elseif ((length(mask) < 2) || (i < mask[2]))
fromline = msg[2][2];
if (toobj(fromline[rindex(fromline, "(") + 1..rindex(fromline, ")") - 1])
in plist)
fseq = $seq_utils:add(fseq, i, i);
endif
else
mask = mask[3..length(mask)];
endif
i = i + 1;
$command_utils:suspend_if_needed(0);
endfor
return fseq || ("%f %
%from_msg_seq:
":%from_msg_seq(string or list of strings[,mask])";
" => msg_seq of messages with one of these strings in the from line";
set_task_perms(caller_perms());
if (typeof(nlist = args[1]) != LIST)
nlist = {nlist};
endif
i = 1;
fseq = {};
mask = {@args, {1}}[2];
for msg in (caller.messages)
if ((!mask) || (i < mask[1]))
elseif ((length(mask) < 2) || (i < mask[2]))
fromline = " " + msg[2][2];
for n in (nlist)
if (index(fromline, n))
fseq = $seq_utils:add(fseq, i, i);
endif
endfor
else
mask = mask[3..length(mask)];
endif
i = i + 1;
$command_utils:suspend_if_needed(0);
endfor
return fseq || ("%f %
to_msg_seq:
":to_msg_seq(object or list[,mask]) => msg_seq of messages to those people";
set_task_perms(caller_perms());
if (typeof(plist = args[1]) != LIST)
plist = {plist};
endif
mask = {@args, {1}}[2];
i = 1;
seq = {};
for msg in (caller.messages)
if ((!mask) || (i < mask[1]))
elseif ((length(mask) < 2) || (i < mask[2]))
toline = msg[2][3];
for r in ($mail_agent:parse_address_field(toline))
if (r in plist)
seq = $seq_utils:add(seq, i, i);
endif
endfor
else
mask = mask[3..length(mask)];
endif
i = i + 1;
$command_utils:suspend_if_needed(0);
endfor
return seq || ("%f %
%to_msg_seq:
":%to_msg_seq(string or list of strings[,mask])";
" => msg_seq of messages containing one of strings in the to line";
set_task_perms(caller_perms());
if (typeof(nlist = args[1]) != LIST)
nlist = {nlist};
endif
i = 1;
seq = {};
mask = {@args, {1}}[2];
for msg in (caller.messages)
if ((!mask) || (i < mask[1]))
elseif ((length(mask) < 2) || (i < mask[2]))
toline = " " + msg[2][3];
for n in (nlist)
if (index(toline, n))
seq = $seq_utils:add(seq, i, i);
endif
endfor
else
mask = mask[3..length(mask)];
endif
i = i + 1;
$command_utils:suspend_if_needed(0);
endfor
return seq || ("%f %
subject_msg_seq:
":subject_msg_seq(target) => msg_seq of messages with target in the Subject:";
set_task_perms(caller_perms());
target = args[1];
i = 1;
seq = {};
mask = {@args, {1}}[2];
for msg in (caller.messages)
if ((!mask) || (i < mask[1]))
elseif ((length(mask) < 2) || (i < mask[2]))
subject = msg[2][4];
if (index(subject, target))
seq = $seq_utils:add(seq, i, i);
endif
else
mask = mask[3..length(mask)];
endif
i = i + 1;
$command_utils:suspend_if_needed(0);
endfor
return seq || (("%f %
body_msg_seq:
":body_msg_seq(target[,mask]) => msg_seq of messages with target in the body";
set_task_perms(caller_perms());
target = args[1];
i = 1;
seq = {};
mask = {@args, {1}}[2];
for msg in (caller.messages)
if ((!mask) || (i < mask[1]))
elseif ({@mask, $maxint}[2] <= i)
mask = mask[3..length(mask)];
elseif ((bstart = "" in (msg = msg[2])) && ((l = length(msg)) > bstart))
while ((!index(msg[l], target)) && ((l = l - 1) > bstart))
$command_utils:suspend_if_needed(0);
endwhile
if (l > bstart)
seq = $seq_utils:add(seq, i, i);
endif
endif
i = i + 1;
$command_utils:suspend_if_needed(0);
endfor
return seq || tostr("%f %
messages_in_seq:
":messages_in_seq(msg_seq) => list of messages in msg_seq on folder (caller)";
set_task_perms(caller_perms());
if (typeof(msgs = args[1]) != LIST)
return caller.messages[msgs];
elseif (length(msgs) == 2)
return caller.messages[msgs[1]..msgs[2] - 1];
else
return $seq_utils:extract(msgs, caller.messages);
endif
.
__convert_new:
":__convert_new(@msg) => msg in new format (if it isn't already)";
" ^ don't forget the @ here.";
"If the msg is already in the new format it passes through unchanged.";
"If the msg format is unrecognizable, warnings are printed.";
if (typeof(date = args[1]) != NUM)
date = 0;
start = 1;
else
start = 2;
if (!((colon = index(args[2], ":")) && (args[2][1..colon] in {"From:", "To:",
"Subject:"})))
return args;
endif
endif
from = to = 0;
subject = " ";
blank = "" in {@args, ""};
newhdr = {};
for line in (args[start..blank - 1])
if (index(line, "Date:") == 1)
if (date)
player:notify("Warning: two dates?");
endif
date = $time_utils:from_ctime(line[6..length(line)]);
elseif (index(line, "From:") == 1)
if (from)
player:notify("Warning: two from-lines?");
endif
from = $string_utils:triml(line[6..length(line)]);
elseif (index(line, "To:") == 1)
if (to)
player:notify("Warning: two to-lines?");
endif
to = $string_utils:triml(line[6..length(line)]);
elseif (index(line, "Subject:") == 1)
subject = $string_utils:triml(line[9..length(line)]);
else
newhdr = {@newhdr, line};
endif
endfor
if (!from)
player:notify("Warning: no from-line.");
endif
if (!to)
player:notify("Warning: no to-line.");
endif
return {date, from, to, subject, @newhdr, @args[blank..length(args)]};
.
to_text:
":to_text(@msg) => message in text form (suitable for printing)";
return {"Date: " + ctime(args[1]), "From: " + args[2], "To: " + args[3],
@(args[4] == " ") ? {} | {"Subject: " + args[4]}, @args[5..length(args)]};
.
is_readable_by is_writable_by is_usable_by:
what = args[1];
if ($object_utils:isa(what, $mail_recipient))
return what:(verb)(@listdelete(args, 1));
else
"...it's a player:";
"... anyone can send mail to it.";
"... only the player itself or a wizard can read it.";
return (verb == "is_usable_by") || $perm_utils:controls(args[2], what);
endif
.
raw_send(new):
"WIZARDLY";
"raw_send(text,rcpts,sender) -- does the actual sending of a message. Assumes that
text has already been formatted correctly. Decides who to send it to and who wants
to be notified about it and does so.";
"Return {E_PERM} if the caller is not entitled to use this verb.";
"Return {0, @invalid_rcpts} if rcpts contains any invalid addresses. No mail is
sent in this case.";
"Return {1, @actual_rcpts} if successful.";
text = args[1];
rcpts = args[2];
from = args[3];
if (typeof(rcpts) != LIST)
rcpts = {rcpts};
endif
if (!(caller in {$mail_agent, $mail_editor}))
return {E_PERM};
elseif (bogus = (resolve = this:resolve_addr(rcpts, from))[1])
return {0, bogus};
else
this:touch(rcpts);
actual_rcpts = resolve[2];
biffs = resolve[3];
results = {};
for recip in (actual_rcpts)
if ((ticks_left() < 1000) || (seconds_left() < 5))
player:notify(tostr("...", recip));
suspend(0);
endif
if (typeof(e = recip:receive_message(text, from)) in {ERR, STR})
"...receive_message bombed...";
player:notify(tostr(recip, ":receive_message: ", e));
e = 0;
elseif ((!is_player(recip)) || (!e))
"...not a player or receive_message isn't giving out the message number";
"...no need to force a notification...";
elseif (i = $list_utils:iassoc(recip, biffs))
"...player-recipient was already getting a notification...";
"...make sure notification includes a mention of him/her/itself.";
if (!(recip in listdelete(biffs[i], 1)))
biffs[i][2..1] = {recip};
endif
else
"...player-recipient wasn't originally being notified at all...";
biffs = {{recip, recip}, @biffs};
endif
results = {@results, e};
endfor
fork (0)
for b in (biffs)
if ((ticks_left() < 1000) || (seconds_left() < 5))
suspend(0);
endif
if ($object_utils:has_callable_verb(b[1], "notify_mail"))
mnums = {};
for r in (listdelete(b, 1))
mnums = {@mnums, (rn = r in actual_rcpts) && results[rn]};
endfor
b[1]:notify_mail(from, listdelete(b, 1), mnums);
endif
endfor
endfork
return {1, @actual_rcpts};
endif
.
reserved_pattern:
":reserved_pattern(string)";
" if string matches one of the reserved patterns for mailing list names, ";
" we return that element of .reserved_patterns.";
string = args[1];
for p in (this.reserved_patterns)
if (match(string, p[1]))
return p;
endif
endfor
return 0;
.
is_recipient: return valid(what = args[1]) && (($player in (ances = $object_utils:ancestors(what))) || ($mail_recipient in ances)); . keep_message_seq:
":keep_message_seq(msg_seq)";
"...If msg_seq nonempty {}, this marks the indicated messages on this folder (caller)";
"...as immune from expiration.";
"...If msg_seq == {}, this clears all such marks.";
set_task_perms(caller_perms());
msg_seq = args[1];
if (!msg_seq)
caller.messages_kept = {};
return 1;
endif
prev_kept = caller.messages_kept;
caller.messages_kept = new_kept = $seq_utils:union(prev_kept, msg_seq);
added = $seq_utils:intersection(new_kept, $seq_utils:complement(prev_kept));
if (!added)
return "";
endif
"... urk. now we need to get the actual numbers of the messages being kept.";
nums = {};
start = 0;
for a in (added)
nums = {@nums, (start = !start) ? caller:messages_in_seq(a)[1] | (caller:messages_in_seq(a
- 1)[1] + 1)};
endfor
return $seq_utils:tostr(nums);
.
kept_msg_seq unkept_msg_seq:
":kept_msg_seq([mask])";
" => msg_seq of messages that are marked kept";
":unkept_msg_seq([mask])";
" => msg_seq of messages that are not marked kept";
set_task_perms(caller_perms());
mask = {@args, {1}}[1];
if (k = verb == "kept_msg_seq")
kseq = $seq_utils:intersection(mask, caller.messages_kept);
else
kseq = $seq_utils:intersection(mask, $seq_utils:range(1, caller:length_all_msgs()),
$seq_utils:complement(caller.messages_kept));
endif
return kseq;
.
set_mail_name add_mail_name:
"NEW";
":set_mail_name(object,parent,name)";
":add_mail_name(object,parent,name)";
"both add the parent/name pair to object.names if necessary";
"set_mail_name indicates that this is to be the primary name.";
"return true if successful, error if not.";
object = args[1];
parent = args[2];
name = args[3];
if ((((index(name, "(") || index(name, ")")) || index(name, ":")) || index(name,
" ")) || index(name, "*"))
return E_INVARG;
elseif ((caller != object) && (!object:is_writable_by(caller_perms())))
return E_PERM;
elseif (this:includes(object, parent))
"... Don't introduce cycles!";
return E_RECMOVE;
elseif (!this:_accept_subname(parent, object, name, caller_perms()))
return E_NACC;
else
return $mail_name_db:add(object, parent, name, verb == "add_mail_name");
endif
.
remove_mail_name:
"NEW";
":remove_mail_name(object,parent,name)";
"removes parent/name pair from object.names if necessary.";
"return true if successful, error if not.";
object = args[1];
parent = args[2];
name = args[3];
if ((caller in {object, parent}) || (object:is_writable_by(caller_perms()) || (valid(parent)
? parent:is_writable_by(caller_perms()) | $perm_utils:controls(caller_perms(), this))))
$mail_name_db:remove(object, parent, name);
return 1;
else
return E_PERM;
endif
.
match_new match:
"NEW";
":match(string[,player]) => mail recipient matching string.";
" (if player supplied, includes player.mail_lists)";
if (!(string = args[1]))
return $nothing;
elseif (string[1] == "*")
string = string[2..length(string)];
endif
string = strsub(string, "_", "-");
if (valid(o = $string_utils:literal_object(string)) && $object_utils:isa(o, $mail_recipient))
return o;
elseif (rp = this:reserved_pattern(string))
"...This is going away REAL SOON...";
return rp[2]:match_mail_recipient(string);
else
c = index(string + ":", ":");
if (c == 1)
first = #-1;
elseif (!(first = $mail_name_db:find(":" + (name = string[1..c - 1]))))
return first;
elseif (length(first) > 1)
return $ambiguous_match;
else
first = first[1];
endif
if (c > length(string))
return first;
else
string[1..c] = "";
return this:match_subname(first, string);
endif
endif
.
includes:
":includes(object,subobject)";
"Is subobject (nonstrictly) a subname of object?";
object = args[1];
if (!valid(sub = args[2]))
return 0;
elseif (object == sub)
return 1;
else
for p in (sub.names)
if (this:includes(object, p[1]))
return 1;
endif
endfor
endif
.
_accept_subname: ":_accept_subname(parent,object,name[,perms])"; "determines if match_subname:
":match_subname(recip,string)";
recip = args[1];
string = args[2];
while (valid(recip = $mail_name_db:find(tostr(recip, ":", string[1..(c = index(string
+ ":", ":")) - 1]))))
if (c > length(string))
return recip;
endif
string[1..c] = "";
endwhile
return recip;
.
match_recipient_new:
":match_recipient(string[,meobj]) => $player or $mail_recipient object that matches
string. Optional second argument (defaults to player) is returned in the case string==\"me\"
and is also used to obtain a list of private $mail_recipients to match against.";
string = args[1];
me = {@args, player}[2];
if (valid(me) && ($failed_match != (o = me:my_match_recipient(string))))
return o;
elseif (!string)
return $nothing;
elseif ((string[1] == "*") && (string != "*"))
return this:match(@args);
else
if (player_only = string[1] == "`")
string[1..1] = "";
endif
c = index(string + ":", ":");
who = $string_utils:match_player(string[1..c - 1], me);
if ((who == $failed_match) && (!player_only))
return this:match(@args);
elseif ((!valid(who)) || (c > length(string)))
return who;
else
string[1..c] = "";
return this:match_subname(who, string);
endif
endif
.
msg_seq_to_msg_num_string: ":msg_seq_to_msg_num_string(msg_seq) => string giving the corresponding message numbers"; set_task_perms(caller_perms()); return $seq_utils:tostr($seq_utils:from_list($list_utils:slice(caller:messages_in_seq(args[1])))); . msg_seq_to_msg_num_list: ":msg_seq_to_msg_num_list(msg_seq) => list of corresponding message numbers"; set_task_perms(caller_perms()); return $list_utils:slice(caller:messages_in_seq(args[1])); . PROPERTY DATA:       options       reserved_patterns       total_recipients       player_expire_time       player_default_@mail |