gopher.moo September 21, Version 1.1
Copyright (c) 1992, 1993, Larry Masinter, Erik Ostrom
All Rights Reserved
Permission granted to use this software for non-commercial purposes;
we'd like to be notified of any enhancements, applications, or
bug-fixes in the software.
This is a general MOO interface to Gopher. To use it, you need a MOO
server. The MOO software is available from
.
It's split up into 2 files: network.moo includes $network and $gopher,
and this file includes the generic gopher slate. $network and $gopher
are now part of LambdaCore, so if you're starting a new MOO you don't
need them.
Be sure you're running LambdaMOO 1.7.5 or later, and that you have
networking enabled.
Create the slate:
@create $thing named Generic Gopher Slate
and then edit the following script to replace #50122 with the number
of Generic Gopher Slate.
Using the Generic slate, use 'goto host port on generic slate' and
'remember on slate' to set up the default 'top level' menu of new
gopher slates.
Change log:
Version 0.1 -- initial release
Version 0.2 -- use $network:open instead of raw o_n_c
validity check on host names
limit on retrievals
add (some) documentation to $gopher.room verbs
gopher rooms have a remembered set
add CSO phone book entries
use .desclines property instead of :description
(exam won't spam).
add gopher lists
Version 0.3: change gopher room to portable slate
subsumes notes
differential cache timeout (shorter for failures)
Version 0.4: Include $network in release (Thanks to unattributed JHM programmers)
Add 'controlled' state on slate.
Slates show headers when they update if watcher isn't controller.
Version 0.5: clean up the $network dump some
Version 0.6:
Version 0.7: very minor patches: more general mailing,
hopefully better installation instructions
Version 1.0: after port to LambdaMOO, simplify $network, $gopher
Version 1.1: split $network and $gopher into separate file
================================================================
@prop #50122."value" {} r
@prop #50122."stack" {} r
@prop #50122."busy" 0 r
@prop #50122."remembered" {} r
@prop #50122."desclines" {} r
@prop #50122."seen" {} r
@prop #50122."length" 20 rc
@prop #50122."help_msg" {} rc
;;#50122.("help_msg") = {"Moving around:", " pick - on slate", " select the given menu item (either a number or partial name).", " If it is a text item, it will show it to you.", " on slate", " e.g., 12 on slate. You can omit `pick' when chosing items", " by their number.", " back slate [for n]", " go back up a level; with n supplied, goes back n levels", " reset slate", " reset slate to the default list of `remember'-ed nodes", " goto host [port [path]] on slate", " make a direct jump to a specified host. Please be careful --", " at the moment this slows everyone down if the host isn't valid.", "", "Controlling noise:", " ignore slate", " stop listening when other people fiddle with the slate", " watch slate", " start watching while other people fiddle with the slate", " show slate to ", " show the contents of the slate to someone even if they're not watching", "", "Modifying the `reset' list:", " remember [
- ] on slate", " adds item to the list you get when you `reset' slate", " will prompt you for title", " remember on slate", " remembers the current menu choice rather than any ", " particular item", " forget
- on slate", " (Only when the slate is `reset')", " deletes the given item", "", "In long menus and text:", " next [] on slate", " prev [] on slate", " move you forward/backward in the set of visible menu items.", " You can give a `number of pages' to move forward.", " read slate", " show you the entire contents of the slate", " read
- on slate", " if
- is a text menu, it will show the text without", " actually changing the state of the slate.", "", "Miscellaneous:", " stack slate", " show stack, where `back' will go", " details
- on slate", " show host, port number, and selection string for a given item. ", " mailme slate", " if you have a valid registration address: send mail with the", " slate contents to your email address.", " mailme
- on slate", " this will mail you the
- , if it is text.", "", "When you first make a gopher slate, you will need to use `goto'", "and then `remember' to set up the default list of nodes."}
@prop #50122."locked" 0 r
@prop #50122."ignoring" {} r
@prop #50122."watching" {} r
@prop #50122."controlled" #-1 r
@prop #50122."work_with_msg" "%N % to work with %d." rc
;;#50122.("description") = "A laptop size computer, with various controls on it."
@verb #50122:"p*ick" any on this rxd
@program #50122:pick
"pick on slate";
" entry is either a line number or an initial substring of a line description";
" select that entry: if it is a menu, go to that node. If it is a search,";
" asks you for the search term & does the search.";
" Some kinds of nodes are not implemented.";
if (this:_textp() || (!(this.stack || this.remembered)))
return player:tell("There's nothing to pick.");
endif
if (this:busy("picking"))
return;
endif
if (!(which = this:match_choice(dobjstr)))
"match_choice took care of it.";
this:busy(0);
return;
endif
if ((tostr(tonum(dobjstr)) == dobjstr) && (!({player, @this:_place()} in this.seen)))
player:tell($string_utils:pronoun_sub("Oooops, perhaps you should look at the %t first."));
this:busy(0);
return;
endif
parse = $gopher:parse(this.value[which]);
desc = this.desclines[which];
this:announce_op("%N % '", desc, "' on the %t.");
this:do_pick(@parse);
return;
.
@verb #50122:"reset" this none none rxd
@program #50122:reset
"reset slate";
" reset the slate to its set of 'remembered' selections";
if (why = this:is_locked(player))
return player:tell($string_utils:pronoun_sub("Sorry, %t seems to be "), why, ".");
elseif (this:busy("resetting"))
return;
endif
this:announce_op("%N % the %t.");
this.seen = {};
this:set_pointer();
this:busy(0);
.
@verb #50122:"pop back" any any any rxd
@program #50122:pop
"back this [by ]";
" move back up the gopher stack to the previous menu";
" or previous N menus.";
n = 1;
if (iobjstr && (!(iobjstr == tostr(n = tonum(iobjstr)))))
return player:tell("Sorry, '", iobjstr, "' doesn't look like a number.");
endif
if (length(this.stack) < n)
player:tell("Sorry, there aren't ", n, " levels to go back.");
return;
endif
if (this:busy("going back"))
return;
endif
this:announce_op("%N % back up ", (n == 1) ? "a level" | tostr(n, " levels"), " on the %t.");
this:set_pointer(@this.stack[n + 1..length(this.stack)]);
this:busy(0);
.
@verb #50122:"location_string" this none this rx
@program #50122:location_string
"location_string([location])";
"A nice-looking version of the location provided, or current location.";
loc = ((args && args[1]) || this.stack[1]);
where = loc[1];
if (st = loc[4])
"human readable string";
return ((st[2..length(st)] + " (from ") + where) + ")";
return (where + ": ") + st[2..length(st)];
endif
if (loc[3])
return ((loc[3] + " (from ") + where) + ")";
return (where + ": ") + loc[3];
endif
return where;
.
@verb #50122:"stack" this none none rxd
@program #50122:stack
"stack slate";
" show a summary of the gopher stack";
max = 0;
if (!this.stack)
return player:tell($string_utils:pronoun_sub("%T is at the top level."));
endif
for x in (this.stack)
max = max(max, length(x[1]));
endfor
max = (max + 6);
for x in ($list_utils:reverse(this.stack))
summary = $gopher:summary(x);
player:tell($string_utils:left(summary[1], max), " ", summary[2]);
endfor
.
@verb #50122:"busy" this none this
@program #50122:busy
"interlock for caching -- mark cache busy or clear; return true of interlock failed";
if (args[1])
if ((args[1] != "reading") && (why = this:is_locked(player)))
player:tell($string_utils:pronoun_sub("Sorry, %t seems to be "), why, ".");
return 1;
endif
"make player running this watch it.";
this.watching = setadd(this.watching, player);
"set busy";
if (this.busy && (this.busy[1] > time()))
player:tell("***Sorry, ", this.name, " is busy ", this.busy[2], " for ", this.busy[3], " -- wait a bit.");
return 1;
else
this.busy = {time() + (60 * 5), args[1], player.name, task_id()};
return 0;
endif
else
this.busy = 0;
return 0;
endif
.
@verb #50122:"match_choice" this none this
@program #50122:match_choice
"match_choice(input string)";
"returns the index of the choice, or 0.";
"is noisy.";
if (this:_textp())
player:tell($string_utils:pronoun_sub("%T is looking at a text node and has no choices."));
return 0;
endif
input = args[1];
which = $code_utils:tonum(input);
len = length(value = this.value);
if (typeof(which) == NUM)
if ((which < 1) || (which > len))
player:tell("Sorry, ", input, " isn't a number between 1 and ", len, ".");
return 0;
endif
return which;
else
exact = (partial = {});
for choice in [1..len]
valchoice = value[choice][2..index(value[choice], " ") - 1];
if (input == valchoice)
exact = {@exact, choice};
elseif (index(valchoice, input) == 1)
partial = {@partial, choice};
endif
endfor
if (length(exact) > 1)
player:tell("I'm not sure whether you meant ", $string_utils:english_list(exact, "", " or "), ".");
return 0;
elseif (exact)
return exact[1];
elseif (length(partial) > 1)
player:tell("I'm not sure whether you meant ", $string_utils:english_list(partial, "", " or "), ".");
return 0;
elseif (partial)
return partial[1];
else
player:tell("Sorry, there is no choice named ", $string_utils:print(input), ".");
return 0;
endif
endif
.
@verb #50122:"jump goto" any on this rxd
@program #50122:jump
"goto [socket] on slate";
" given an explicit host name and optional socket, attempt to open a";
" gopher connection to that socket";
words = $string_utils:words(dobjstr);
if (!words)
player:tell("Usage: ", verb, " [socket]", prepstr ? tostr(" on ", iobjstr) | "");
return;
endif
host = words[1];
socket = 70;
if (length(words) > 1)
socket = tonum(words[2]);
if (socket < 3)
player:tell("The value '", words[2], "' is not a valid socket.");
return;
endif
endif
path = "";
if (length(words) > 2)
path = dobjstr[(index(dobjstr, words[2]) + length(words[2])) + 1..length(dobjstr)];
endif
if (this:busy(tostr("jumping to ", host, " socket ", socket)))
return;
endif
this:announce_op(tostr("%N % to ", host, " socket ", socket, path ? " " | "", path, " on the %t."));
parse = {host, socket, path, "1"};
this:set_pointer(parse, @this:_textp() ? listdelete(this.stack, 1) | this.stack);
this:busy(0);
.
@verb #50122:"details" any on this rxd
@program #50122:details
if (!(which = this:match_choice(dobjstr)))
"match_choice took care of it.";
return;
endif
parse = $gopher:parse(this.value[which]);
sel = parse[4];
if (sel)
for x in ({"Type=" + sel[1], "Name=" + sel[2..length(sel)], "Path=" + parse[3], "Host=" + parse[1], "Port=" + tostr(parse[2]), "#"})
player:tell(x);
endfor
else
player:tell("**** ERROR, ", which, " is not a valid entry.");
endif
.
@verb #50122:"set_pointer" this none this rx
@program #50122:set_pointer
if (!args)
value = this.remembered;
else
value = $gopher:get(@args[1]);
endif
if (!value)
this:busy(0);
this:announce_op($gopher:interpret_error(value));
return 0;
endif
if (value[1][1] == "3")
this:busy(0);
this:announce_op("The gopher request results in an error:");
for x in (value)
this:announce_op(": ", x ? x[2..length(x)] | x);
endfor
return 0;
endif
if (args && (args[1][4][1] == "0"))
"text node";
desc = value;
else
desc = {};
cnt = 1;
for x in (value)
$command_utils:suspend_if_needed(0);
type = $gopher:type(x[1]);
if (type == "text")
type = "";
else
type = ((" (" + type) + ")");
endif
tab = index(x, " ");
label = x[2..tab - 1];
desc = {@desc, tostr(cnt, ". ", label, type)};
cnt = (cnt + 1);
endfor
endif
$command_utils:suspend_if_needed(0);
this.desclines = desc;
this.stack = args;
this.value = value;
this:busy(0);
this:show_results();
return 1;
.
@verb #50122:"do_pick" this none this
@program #50122:do_pick
"do_pick(host, port, path, string) -- take parsed output & interact with user as appropriate.";
string = args[4];
if ((!string) || index("1?", type = string[1]))
"menu";
this:set_pointer(args, @this.stack);
elseif (type == "7")
player:tell("Search for what? Enter search line or @abort:");
search = read();
if (search != "@abort")
this:announce_op("%N % for ", search, " on %t.");
this:set_pointer({args[1], args[2], (args[3] + " ") + search, args[4]}, @this.stack);
else
this:busy(0);
this:announce_op("%N % not to search.");
endif
elseif (type == "3")
this:busy(0);
this:announce_op("%N chose an error line.");
elseif (type == "0")
"slates can point at text nodes";
this:set_pointer(args, @this.stack);
elseif (type == "2")
search = $command_utils:read("one of 'name=' 'phone=' 'email='");
if (!match(search, "[a-z]+=[a-z0-9@-]+"))
this:busy(0);
player:tell((search == "@abort") ? "No search." | ("Invalid query: " + search));
return;
endif
this:announce_op("%N % for ", search, " on %t.");
this:set_pointer({args[1], args[2], (args[3] + " query ") + search, args[4]}, @this.stack);
elseif ($object_utils:has_property(player, "gopher_local") && player.gopher_local)
this:busy(0);
notify(player, tostr("#$# gopher ", args[1], " ", args[2], " ", args[4], " ", args[3]));
else
this:busy(0);
this:announce_op("Type ", type, " (", $gopher:type(type), ") gopher requests not implemented.");
if (type == "8")
player:tell("**** telnet ", args[1], (args[2] in {23, 0}) ? "" | (" " + tostr(args[2])));
if (args[3])
player:tell(" log in as: ", args[3]);
endif
endif
endif
.
@verb #50122:"remember" any on this rxd
@program #50122:remember
"remember on ";
" add the entry (or this menu) to the 'remembered set' for this room.";
" use 'remembered' to retrieve the set.";
if (!this.stack)
return player:tell("Sorry, remembering remembered nodes doesn't work.");
endif
if (dobjstr == "")
parse = this.stack[1];
desc = "the current menu";
elseif (choice = this:match_choice(dobjstr))
parse = $gopher:parse(this.value[choice]);
desc = this.desclines[choice];
else
"Match_choice took care of it.";
return;
endif
parse[4] = (parse[4][1] + $command_utils:read("description for " + desc));
this.remembered = {@this.remembered, $gopher:unparse(@parse)};
this:announce_op("%N % ", desc, " on the %t as ", parse[4][2..length(parse[4])], ".");
.
@verb #50122:"forget delete" any on this rxd
@program #50122:forget
"forget on slate";
" erase an entry from the 'remembered set'";
" only works if you're looking at the 'remembered set'";
if (this.stack)
player:tell("You're not looking at the top.");
return;
endif
if (!(choice = this:match_choice(dobjstr)))
return;
endif
this:announce_op("%N % '", this.desclines[choice], "' on the %t.");
this.remembered = listdelete(this.remembered, choice);
this:set_pointer();
.
@verb #50122:"look_self" this none this
@program #50122:look_self
if (this.stack)
sum = $gopher:summary(this.stack[1]);
player:tell(this:titlec(), ": ", sum[1], " ", sum[2]);
else
player:tell(this:titlec());
endif
player:tell_lines(this:description());
this:_tell_desc();
state = "";
if (valid(this.controlled))
state = (($string_utils:pronoun_sub("The %t is being controlled by ") + this.controlled:title()) + ".");
endif
if ((busy = this:_is_busy()) || state)
player:tell(state ? state + " " | "", busy ? $string_utils:pronoun_sub(tostr("The %t is busy ", this.busy[2], " for ", this.busy[3], ".")) | "");
endif
.
@verb #50122:"_tell_desc" this none this
@program #50122:_tell_desc
who = (args ? args[1] | player);
plen = ((length(args) > 1) ? args[2] | this.length);
header = ((length(args) > 2) && args[3]);
if (this:_textp())
text = this:text();
len = length(text);
if ((!plen) || (len <= plen))
$command_utils:suspend_if_needed(0);
"6/24/93 change tell_lines to notify_lines to reduce lag.";
if (header)
who:tell("--------------- ", this.name, "-----");
who:notify_lines(text);
who:tell("--------------- ", this.name, "-----");
else
who:notify_lines(text);
endif
return;
endif
offset = this:offset();
npages = ((len / plen) + 1);
thispage = ((offset / plen) + 1);
if ((offset != 1) || header)
who:tell("--", thispage, " of ", npages, "----- 'prev on ", this.name, "' for previous----");
endif
end = ((offset + plen) - 1);
who:tell_lines(text[offset..min(len, end)]);
if ((len > end) || header)
who:tell("--", thispage, " of ", npages, "----- 'next on ", this.name, "' for more --------");
endif
return;
endif
this.seen = setadd(this.seen, {who, @this:_place()});
len = length(this.desclines);
if (header)
who:tell("--------------- ", this.name, "-----");
endif
if (plen && (len > plen))
offset = this:offset();
who:tell_lines(this.desclines[offset..min((offset + this.length) - 1, len)]);
nxt = ("next on " + this.name);
prv = ("previous on " + this.name);
who:tell("---- '", (offset == 1) ? nxt | (((offset + plen) > len) ? prv | (((("'" + nxt) + "' or '") + prv) + "'")), "' to see additional choices (", len, " total) ---");
else
who:tell_lines(this.desclines || {$string_utils:pronoun_sub("%T is empty right now.")});
if (header)
who:tell("--------------- ", this.name, "-----");
endif
endif
.
@verb #50122:"next prev*ious" any on this rxd
@program #50122:next
if (this:busy("reading"))
"can't 'next' if it is busy";
return;
endif
this:busy(0);
n = (tonum(dobjstr) || 1);
if (verb != "next")
n = (-n);
verb = "previous";
endif
offset = this:offset();
new = (offset + (n * this.length));
if (new < 1)
if (offset == 1)
return player:tell("You're already at the beginning.");
else
new = 1;
endif
elseif (new > length(this.desclines))
return player:tell("You're already at the end.");
endif
this:announce_op("%N % at the ", verb, " ", this:_textp() ? "page" | "results", " on the %t.");
this:offset(new);
this:show_results();
.
@verb #50122:"initialize" this none this
@program #50122:initialize
if ((caller == this) || $perm_utils:controls(caller_perms(), this))
"don't call this unless you mean it.";
this.seen = {};
this.desclines = {};
"The default is that slate's inherit the 'remembered' from their parent. This means, though, that they're initially blank but have to be 'reset' to fire up. See :do_reset";
"this.remembered = {}";
this.busy = 0;
this.stack = {};
this.watching = {};
this.controlled = #-1;
pass(@args);
endif
.
@verb #50122:"announce_op" this none this
@program #50122:announce_op
msg = tostr(@args);
player:tell($string_utils:pronoun_sub(msg, $you));
if (this.location != player)
this.location:announce($string_utils:pronoun_sub(msg));
endif
return;
"announcing only to watching";
if (watching = setremove($set_utils:intersection(this.watching, this.location:contents()), player))
msg = $string_utils:pronoun_sub(msg);
for x in (watching)
x:tell(msg);
endfor
endif
.
@verb #50122:"_place" this none this
@program #50122:_place
return this.stack && this.stack[1][1..3];
.
@verb #50122:"_textp" this none this
@program #50122:_textp
return this.stack && index("02", this.stack[1][4][1]);
.
@verb #50122:"r*ead" any any any rxd
@program #50122:read
if ((!argstr) || ((dobj == this) && (!prepstr)))
this:_tell_desc(player, 0);
elseif (which = this:match_choice((($code_utils:short_prep(prepstr) == "on") && (iobj == this)) ? dobjstr | argstr))
where = $gopher:parse(this.value[which]);
if (index("02", where[4][1]))
this:announce_op("%N % '", this.desclines[which], "' on the %t.");
$gopher:show_text(player, 0, 0, @where);
player:tell("-------");
else
player:tell("Item '", this.desclines[which], "' isn't text and can't be read.");
endif
else
player:tell("Read what?");
endif
.
@verb #50122:"lock unlock" this none none rxd
@program #50122:lock
this.locked = (verb == "lock");
this:announce_op("%N %<", $string_utils:lowercase(verb), "s> %t.");
.
@verb #50122:"text" this none this
@program #50122:text
return this.value;
"don't update slates";
.
@verb #50122:"update" this none none rxd
@program #50122:update
if (this:busy("updating", 1))
return;
endif
this:announce_op("%N % %t.");
if (this.stack)
$gopher:clear_cache(@this.stack[1]);
endif
this:set_pointer(@this.stack);
.
@verb #50122:"_mail_text" this none this
@program #50122:_mail_text
if (this:_textp())
return this.value;
else
text = {};
for x in (this.value)
parse = $gopher:parse(x);
sel = parse[4];
text = {@text, "Type=" + sel[1], "Name=" + sel[2..length(sel)], "Path=" + parse[3], "Host=" + parse[1], "Port=" + tostr(parse[2]), "#"};
endfor
return text;
endif
.
@verb #50122:"show_results" this none this
@program #50122:show_results
"after a selection is made, this verb is used to show the results; usually to 'player'";
inhere = ($object_utils:isa(this.location, $room) ? this.location:contents() | {player});
for x in (this.watching = setadd(this.watching, player))
$command_utils:suspend_if_needed(0);
if (x in inhere)
this:_tell_desc(x, this.length, player != x);
else
this.watching = setremove(this.watching, x);
endif
endfor
.
@verb #50122:"ignore watch" this none none rxd
@program #50122:ignore
was = (player in this.watching);
this.watching = ((verb == "watch") ? setadd(this.watching, player) | setremove(this.watching, player));
is = (player in this.watching);
if (was == is)
player:tell("You already were ", (verb == "watch") ? "watching" | "ignoring", " ", this:title(), ".");
elseif (this.location == player)
player:tell("You start to ", verb, " ", this:title(), ".");
else
$you:say_action(("%N % to " + verb) + " %t.");
endif
.
@verb #50122:"show" this to any rxd
@program #50122:show
if (!valid(iobj))
return player:tell("I don't see '", iobjstr, "' here.");
endif
$you:say_action("%N % %t to %i.");
this:_tell_desc(iobj, this.length, 1);
.
@verb #50122:"_is_busy" this none this
@program #50122:_is_busy
if (this.busy)
if (this.busy[1] > time())
return 1;
else
this.busy = 0;
endif
endif
return 0;
.
@verb #50122:"control" this none none rxd
@program #50122:control
if (this.controlled == player)
player:tell("You are already controlling ", this:title(), ".");
return;
endif
from = (valid(this.controlled) ? (" from " + this.controlled:title()) + "." | ".");
if (this.location != player)
this.location:announce_all_but({player}, $string_utils:pronoun_sub("%N takes the controls of %t"), from);
endif
player:tell("You take the controls of ", this:title(), from);
this.controlled = player;
.
@verb #50122:"release" this none none rxd
@program #50122:release
if (this.controlled == player)
$you:say_action("%N % the controls of %t.");
this.controlled = #-1;
else
player:tell("You weren't holding the controls of ", this.name, ".");
endif
.
@verb #50122:"is_locked" this none this
@program #50122:is_locked
"is this locked?";
if (this.locked)
return "locked";
elseif (valid(this.controlled) && (this.controlled != args[1]))
if (this.location in {this.controlled, this.controlled.location})
return "controlled by " + this.controlled.name;
else
this.controlled = #-1;
endif
endif
return 0;
.
@verb #50122:"match_command" this none this rx
@program #50122:match_command
"match_command(vrb, dlist, plist, ilist)";
"return true if this object can handle the command, false otherwise";
"vrb - name of the verb the player typed";
"dlist - list of objspecs that this command matches";
"plist and ilist - likewise for prepspecs, iobjspecs";
if ((player.focus_object == this) && (this.location in {player, player.location}))
vrb = args[1];
dlist = args[2];
plist = args[3];
ilist = args[4];
if (((vrb in {"pick", "jump", "goto", "details", "remember", "forget", "delete", "next", "prev", "previ", "previo", "previou", "previous"}) && ("none" in plist)) && ("none" in ilist))
return 1;
elseif (((vrb in {"read", "ignore", "watch"}) && ("none" in dlist)) && ("none" in plist))
return 1;
elseif (((vrb in {"show"}) && ("none" in dlist)) && ("at/to" in plist))
return 1;
elseif ((vrb in {"reset", "stack", "mailme", "lock", "unlock", "update", "control", "release"}) && (!("on top of/on/onto/upon" in plist)))
return 1;
elseif (((vrb in {"pop", "back"}) && ("none" in dlist)) && (("none" in plist) || ("for/about" in plist)))
return 1;
endif
endif
return pass(@args);
.
@verb #50122:"work" none with this r
@program #50122:work
"This is a JaysHouseMOO verb -- probably doesn't work on other MOOs without a 'focus' object.";
if (valid(player:set_focus_object(this)))
$you:say_action(this.work_with_msg);
else
player:tell("You just can't seem to focus on that.");
endif
.
@verb #50122:"mailme" any any any rxd
@program #50122:mailme
"mailme note";
if ((caller_perms() != player) && (caller != player))
return player:tell("Someone tried to mail you some text, but it didn't work.");
endif
if (!player.email_address)
return player:tell("Sorry, you don't have a registered email address.");
endif
if ((!argstr) || ((dobj == this) && (!prepstr)))
where = this.stack[1];
elseif (which = this:match_choice((($code_utils:short_prep(prepstr) == "on") && (iobj == this)) ? dobjstr | argstr))
where = $gopher:parse(this.value[which]);
endif
if (where)
player:tell("Mailing ", this:location_string(where), " to ", player.email_address, ".");
text = $gopher:_mail_text(where);
player:tell("... ", length(text), " lines ...");
text = {tostr("(Mail initiated by ", player.name, " (", player, ") connected from ", $string_utils:connection_hostname(connection_name(player)), " using ", this.name, ")"), @text};
suspend(0);
result = $network:sendmail(player.email_address, this:location_string(where), @text);
if (result == 0)
player:tell("Mail sent successfully.");
else
player:tell("Mail sending error: ", result, ".");
endif
else
player:tell("Sorry, can't mail this.");
endif
.
@verb #50122:"header" this none this
@program #50122:header
"used by _tell_desc for prefix & suffix lines";
args[1]:tell("------- ", $string_utils:left($string_utils:pronoun_sub(tostr(@listdelete(args, 1), " ")), args[1]:linelen(), "-"));
.
@verb #50122:"offset" this none this
@program #50122:offset
if (!this.stack)
return 1;
endif
menu = this.stack[1];
if (args)
if (length(menu) > 4)
this.stack[1][5] = args[1];
else
this.stack[1] = {@{@menu, "", "", "", ""}[1..4], args[1]};
endif
elseif (length(menu) > 4)
return menu[5];
else
return 1;
endif
.
"***finished loading gopher slate ***