/* Copyright 1994, 1995 - Tim Hollebeek
*
* Permission is granted to copy and use this code elsewhere, provided
* that all code derived from this code retain this header and credit
* the author (Tim Hollebeek) for the source, that all improvements
* to this code be communicated to the above mentioned author for possible
* inclusion in this code, that all derived works are made publicly
* available to whoever wants them, and no profit is made off of this
* code or any other derived works or any other package or system this
* is used in without express written permission of the author.
*/
/* written quickly by Beek, Oct 12, 1994 */
#include <lib.h>
#include <daemons.h>
#define NEWSRC_SAVE_DIR "/save/news/rn/"
inherit LIB_DAEMON;
private void main_loop();
private void unsubscribe(string);
private void show_headers();
private void post(string, function);
static void ask_what_next(string);
static void ask_about_group(string);
#define STRIP_UNSUB(x) ((x) ? ((x)[0] == '#' ? (x)[1..strlen(x)] : x) : 0)
#define IS_UNSUB(x) ((x) && ((x)[0] == '#'))
#define ALL_ARTICLES 1
int options = 0;
string restrict_poster;
object who;
mapping read = ([]);
string *groups;
int *ids;
/* The following code is imported from the first newsreader I ever
wrote, which is also one of the first LPC objects I ever wrote :) */
/* It's hideously inefficient and in need of a rewrite, but it will
do for now */
int included(int id,string read) {
string *ranges;
int i,begin,end;
if (!read) return 0;
ranges=explode(read,",");
for (i=0;i<sizeof(ranges);i++) {
if (sscanf(ranges[i],"%d-%d",begin,end)==2) {
if ((begin<=id) && (id<=end)) return 1;
} else {
sscanf(ranges[i],"%d",begin);
if (id==begin) return 1;
}
}
return 0;
}
int GetRight(string range) {
int begin,end;
if (sscanf(range,"%d-%d",begin,end)==2) return end;
sscanf(range,"%d",begin);
return begin;
}
int GetLeft(string range) {
int begin,end;
if (sscanf(range,"%d-%d",begin,end)==2) return begin;
sscanf(range,"%d",begin);
return begin;
}
string AddToList(string list,int num) {
string *ranges;
int i,left,right;
if (list == "") list = 0;
if (included(num,list)) return list;
if (!list) return sprintf("%d",num);
ranges=explode(list,",");
for (i=0;i<sizeof(ranges);i++) {
left=GetLeft(ranges[i]);
if (num<left) {
right=-1;
if (i) right=GetRight(ranges[i-1]);
if ((right+1==num) && (num==left-1)) {
ranges[i-1]=sprintf("%d-%d",GetLeft(ranges[i-1]),GetRight(ranges[i]));
ranges=ranges[0..i-1]+ranges[i+1..(sizeof(ranges)-1)];
} else if (right+1==num) {
ranges[i-1]=sprintf("%d-%d",GetLeft(ranges[i-1]),num);
} else if (num==left-1) {
ranges[i]=sprintf("%d-%d",num,GetRight(ranges[i]));
} else {
ranges=ranges[0..i-1]+({sprintf("%d",num)})+ranges[i..(sizeof(ranges)-1)];
}
return implode(ranges,",");
}
}
if (GetRight(ranges[sizeof(ranges)-1])==num-1) {
ranges[sizeof(ranges)-1]=sprintf("%d-%d",GetLeft(ranges[sizeof(ranges)-1]),num);
} else ranges+=({sprintf("%d",num)});
return implode(ranges,",");
}
/* end imported code */
private void
add_read(string group, int id) {
if (read[group] && read[group][0] == '#') {
read[group] = "#" + AddToList(read[group][1..strlen(read[group])], id);
} else {
read[group] = AddToList(read[group], id);
}
}
private void
exit(int aborted) {
string *groups;
int i, n;
if (!aborted)
write("End of news.");
unguarded( (: write_file, NEWSRC_SAVE_DIR+who->GetKeyName(), save_variable(read), 1 :) );
destruct(this_object());
}
private void
end_follow(string group, int id, function when_done) {
string file;
string message;
file = "/tmp/rn."+who->GetKeyName();
message = read_file(file);
rm(file);
NEWSSERVER->followup(group, id, message);
evaluate(when_done);
}
private void
abort_post(function when_done) {
string file;
file = "/tmp/rn."+who->GetKeyName();
if (file && file_exists(file)) rm (file);
write("Post aborted.");
evaluate(when_done);
}
private void
follow(string group, int id, string insert) {
string fname = "/tmp/rn."+who->GetKeyName();
write_file(fname, insert, 1);
who->eventEdit(fname,
(: end_follow, group, id, (: ask_what_next, 0 :) :),
(: abort_post, (: ask_what_next, 0 :) :) );
}
private void
end_article() {
printf("End of article -- ");
ask_what_next(0);
}
private void
show_article(int id) {
string *tmp;
mapping article;
article = NEWSSERVER->get_message(groups[0], id);
if (!article["MESSAGE"]) article["MESSAGE"] = "";
tmp = ({ sprintf("Post %i (%i more) in %s:",
ids[0], sizeof(ids)-1, groups[0])
, "Poster: " + article["POSTER"]
, "Subject: %^BOLD%^" + article["SUBJECT"] + "%^RESET%^"
, "Date: " + ctime(article["TIME"])
, "" })
+ explode(article["MESSAGE"], "\n");
add_read(groups[0], id);
who->more(tmp, "news", (: end_article :));
}
private string quote_text(string orig) {
if (orig[strlen(orig)-1] == '\n') orig = orig[0..strlen(orig)-2];
return "> " + replace_string(orig, "\n", "\n> ") + "\n";
}
private void
next_group() {
groups = groups[1..sizeof(groups)];
main_loop();
}
static void
ask_what_next(string response) {
int num;
switch (response) {
case "u": /* unsubscribe */
unsubscribe(groups[0]);
next_group();
return;
case "p":
post(groups[0], (: ask_what_next, 0 :));
return;
case "c": /* catch up */
{
int i,n;
for (i=0, n=sizeof(ids); i<n; i++)
add_read(groups[0], ids[i]);
}
next_group();
return;
case "q": /* quit group */
next_group();
return;
case "=": /* list headers */
show_headers();
break;
case "F":
case "f": /* followup */
{
string insert;
if (ids[0] == -1) {
write("No current article");
break;
}
insert = "";
if (response == "F") {
mapping article = NEWSSERVER->get_message(groups[0], ids[0]);
insert = sprintf("%s writes:\n%s", article["POSTER"],
quote_text(article["MESSAGE"]));
}
follow(groups[0], ids[0], insert);
return;
}
case "r":
if (ids[0] == -1) {
write("No current article");
break;
}
if (NEWSSERVER->remove_post(groups[0], ids[0])) {
write("Removed.");
ids = ids[1..sizeof(ids)];
if (!sizeof(ids)) {
write("End of group " + groups[0] + "\n");
next_group();
return;
}
break;
} else {
write("Failed.");
}
break;
case "?":
case "h":
write(@ENDHELP
c - mark all articles in this group as read
f - post followup to last article
F - same as 'f' but include article preceded by "> "
h or ? - this help
n - read next article
p - post an article to this group
q - go back to newsgroup mode
u - unsubscribe to this group
= - list articles remaining in group
a number - read a specified article
ENDHELP);
break;
case "":
case "n":
if (ids[0] == -1)
ids = ids[1..sizeof(ids)];
if (sizeof(ids) > 1) {
ids = ids[1..sizeof(ids)];
show_article(ids[0]);
} else {
write("End of group " + groups[0] + "\n");
next_group();
}
return;
default:
if (response && sscanf(response, "%d", num)) {
if (member_array(num, NEWSSERVER->get_messages(groups[0])) == -1) {
write("No such message.");
} else {
if (ids[0] != -1) ids = ids[1..sizeof(ids)];
ids -= ({ num, -1 });
ids = ({ num }) + ids;
show_article(ids[0]);
return;
}
}
}
printf("What next? [cfFhnpqru?=] ");
input_to("ask_what_next");
}
private void
show_headers() {
int i = 1, n;
mapping article;
for (n=sizeof(ids); i<n; i++) {
article = NEWSSERVER->get_message(groups[0], ids[i]);
printf("[%3d] %-15s %s\n", ids[i], article["POSTER"], article["SUBJECT"]);
}
}
private void
unsubscribe(string group) {
if (read[group])
read[group] = "#" + read[group];
else
read[group] = "#";
}
private void
subscribe(string group) {
read[group] = STRIP_UNSUB(read[group]);
}
private string temp_kludge;
private int *
get_unread(string group) {
int *ret;
int *deleted;
function filter;
int i;
if (options & ALL_ARTICLES) {
if (restrict_poster) filter = (: $2["POSTER"] == restrict_poster :);
else filter = (: 1 :);
} else {
temp_kludge = STRIP_UNSUB(read[group]);
if (restrict_poster) filter = (: !included($1, temp_kludge) && $2["POSTER"] == restrict_poster :);
else filter = (: !included($1, temp_kludge) :);
}
ret = NEWSSERVER->get_messages(group, filter);
if (!sizeof(ret)) return 0;
deleted = NEWSSERVER->query_removed(group, ret);
i = sizeof(deleted);
while (i--) {
add_read(group, deleted[i]);
}
ret -= deleted;
if (!sizeof(ret)) return 0;
return ret;
}
private void
main_loop() {
if (!sizeof(groups)) {
exit(0);
return;
}
ids = get_unread(groups[0]);
while (!ids) {
groups = groups[1..sizeof(groups)];
if (sizeof(groups))
ids = get_unread(groups[0]);
else break;
}
if (!ids) {
exit(0);
return;
}
ask_about_group(0);
}
private void
start_main_loop() {
if (!groups)
groups = NEWSSERVER->get_groups();
/* remove unsubscribed groups */
groups = filter_array( groups, (: !read[$1] || read[$1][0] != '#' :) );
if (!sizeof(groups)) {
exit(0);
return;
}
main_loop();
}
private void
list_groups() {
string *groups = NEWSSERVER->get_groups();
int i,n;
int ur;
string urs;
for (i=0, n=sizeof(groups); i<n; i++) {
ur = sizeof(get_unread(groups[i]));
urs = (ur ? "(" + ur + ")" : "(READ)");
if (IS_UNSUB(read[groups[i]]))
urs = "(UNSUB)";
printf("%7s %s\n", urs, groups[i]);
}
}
static void
ask_subscribe(string response, string group) {
switch (response) {
case "y":
subscribe(group);
groups = ({ group }) + groups;
main_loop();
return;
case "n":
main_loop();
return;
default:
printf("%s is currently unsubscribed. Resubscribe? [yn] ", group);
input_to("ask_subscribe", 0, group);
}
}
private void
goto_group( string group ) {
if (member_array(group, NEWSSERVER->get_groups()) == -1) {
write("No such group.");
ask_about_group(0);
return;
}
if (IS_UNSUB(read[group])) {
ask_subscribe(0, group);
} else {
groups = ({ group }) + groups;
main_loop();
}
}
static void
ask_about_group(string response) {
switch (response) { /* break; instead of return; goes to next group */
case "q": /* quit */
exit(1);
return;
case "c": /* catch up */
{
int i,n;
for (i=0, n=sizeof(ids); i<n; i++)
add_read(groups[0], ids[i]);
break;
}
case "u": /* unsubscribe */
unsubscribe(groups[0]);
break;
case "p":
post(groups[0], (: ask_about_group, 0 :));
return;
case "n": /* next group */
break;
case "=": /* show headers */
ids = ({ -1 }) + ids;
show_headers();
ask_what_next(0);
return;
case "L": /* list groups */
list_groups();
ask_about_group(0);
return;
case "h":
case "?":
write(@ENDHELP
c - mark all articles in this group as read
h or ? - this help
L - show all groups
n - go on to next group
p - post an article to this group
q - quit rn
u - unsubscribe to this group
y - read first article
= - show articles in group, and start reading
ENDHELP);
ask_about_group(0);
return;
case "y":
case "": /* show article */
show_article(ids[0]);
return;
default:
if (response && response[0..1]=="g ") { /* group */
goto_group(response[2..strlen(response)]);
return;
}
printf("******* %i unread posts in %s -- read now? [chLnpquy?=] ", sizeof(ids), groups[0]);
input_to("ask_about_group", 0);
return;
}
next_group();
}
static void
ask_about_new_groups(string response, string *groups_left) {
int n;
if (response) {
read[groups_left[0]] = (response == "y" ? 0 : "#");
if ((n=sizeof(groups_left)) <= 1) {
start_main_loop();
return;
}
groups_left = groups_left[1..n];
}
printf("group '%s' is new; subscribe? (y/n) >", groups_left[0]);
input_to("ask_about_new_groups", 0, groups_left);
}
private void
load_newsrc(string file) {
file = read_file(file);
if (file)
read = restore_variable(file);
}
private void
start_reading(string group) {
if (group) {
if (member_array(group, NEWSSERVER->get_groups()) == -1) {
write("No such group.");
return;
}
if (IS_UNSUB(read[group])) {
groups = ({});
ask_subscribe(0, group);
return;
} else {
groups = ({ group });
}
} else {
string *new_groups;
new_groups = (string *)NEWSSERVER->get_groups() - keys(read);
if (sizeof(new_groups)) {
ask_about_new_groups(0, new_groups);
return;
}
}
start_main_loop();
}
private void
post(string group, function when_done) {
if (member_array(group, NEWSSERVER->get_groups()) == -1) {
write("No such group.");
return;
}
printf("Subject: ");
input_to("get_subject", 0, group, when_done);
}
private void
end_post(string subj, string group, function when_done) {
string file;
string message;
message = read_file("/tmp/rn."+who->GetKeyName());
rm("/tmp/rn."+who->GetKeyName());
NEWSSERVER->post(group, subj, message);
evaluate(when_done);
}
static void
get_subject(string subj, string group, function when_done) {
who->eventEdit("/tmp/rn."+who->GetKeyName(),
(: end_post, subj, group, when_done :),
(: abort_post, when_done :));
}
int syntax_error() {
return notify_fail("rn [-all] [-post] [newsgroup]\n");
}
#define READING 0
#define POSTING 1
#define LISTING 2
int
start_up( string str ) {
string *args;
string gname;
int n;
int what;
who = this_player();
if (str) {
args = explode(str, " ");
n = sizeof(args);
while (n && args[0][0]=='-') {
switch (args[0]) {
case "-all":
options |= ALL_ARTICLES;
break;
case "-post":
what = POSTING;
break;
case "-list":
what = LISTING;
break;
case "-poster":
if (n == 1) {
write("rn: must specify poster after -poster");
return 1;
}
restrict_poster = args[1];
args = args[1..n--];
break;
default:
write("rn: unknown flag '"+args[0]+"'");
return 1;
}
args = args[1..n--];
}
if (n>1)
return syntax_error();
if (n)
gname = args[0];
}
switch(what) {
case POSTING:
if (!gname)
return notify_fail("Must specify group to post to.\n");
post(gname, 0);
break;
case LISTING:
load_newsrc(NEWSRC_SAVE_DIR + this_player()->GetKeyName());
list_groups();
break;
case READING:
load_newsrc(NEWSRC_SAVE_DIR + this_player()->GetKeyName());
start_reading(gname);
break;
}
return 1;
}