lima-1.0b5/
lima-1.0b5/driver/
lima-1.0b5/driver/ChangeLog.old/
lima-1.0b5/driver/Win32/
lima-1.0b5/driver/compat/
lima-1.0b5/driver/include/
lima-1.0b5/driver/testsuite/
lima-1.0b5/driver/testsuite/clone/
lima-1.0b5/driver/testsuite/command/
lima-1.0b5/driver/testsuite/data/
lima-1.0b5/driver/testsuite/etc/
lima-1.0b5/driver/testsuite/include/
lima-1.0b5/driver/testsuite/inherit/
lima-1.0b5/driver/testsuite/inherit/master/
lima-1.0b5/driver/testsuite/log/
lima-1.0b5/driver/testsuite/single/
lima-1.0b5/driver/testsuite/single/tests/compiler/
lima-1.0b5/driver/testsuite/single/tests/efuns/
lima-1.0b5/driver/testsuite/single/tests/operators/
lima-1.0b5/driver/testsuite/u/
lima-1.0b5/driver/tmp/
lima-1.0b5/etc/
lima-1.0b5/lib/WWW/help/
lima-1.0b5/lib/cmds/
lima-1.0b5/lib/cmds/create/
lima-1.0b5/lib/cmds/player/attic/
lima-1.0b5/lib/contrib/bboard/
lima-1.0b5/lib/contrib/boards/
lima-1.0b5/lib/contrib/marriage/
lima-1.0b5/lib/contrib/roommaker/
lima-1.0b5/lib/contrib/transient_effect/
lima-1.0b5/lib/daemons/channel/
lima-1.0b5/lib/daemons/imud/
lima-1.0b5/lib/data/
lima-1.0b5/lib/data/config/
lima-1.0b5/lib/data/links/
lima-1.0b5/lib/data/news/
lima-1.0b5/lib/data/players/
lima-1.0b5/lib/data/secure/
lima-1.0b5/lib/domains/
lima-1.0b5/lib/domains/std/2.4.5/maze1/
lima-1.0b5/lib/domains/std/2.4.5/npc/
lima-1.0b5/lib/domains/std/2.4.5/post_dir/
lima-1.0b5/lib/domains/std/2.4.5/sub/
lima-1.0b5/lib/domains/std/camera/
lima-1.0b5/lib/domains/std/config/
lima-1.0b5/lib/domains/std/cult/
lima-1.0b5/lib/domains/std/effects/
lima-1.0b5/lib/domains/std/misc/
lima-1.0b5/lib/domains/std/monsters/
lima-1.0b5/lib/domains/std/recorder/
lima-1.0b5/lib/domains/std/rooms/
lima-1.0b5/lib/domains/std/rooms/beach/
lima-1.0b5/lib/domains/std/rooms/labyrinth/
lima-1.0b5/lib/domains/std/school/
lima-1.0b5/lib/domains/std/school/O/
lima-1.0b5/lib/domains/std/spells/
lima-1.0b5/lib/domains/std/spells/stock-mage/
lima-1.0b5/lib/domains/std/spells/stock-priest/
lima-1.0b5/lib/help/
lima-1.0b5/lib/help/admin/
lima-1.0b5/lib/help/hints/General_Questions/
lima-1.0b5/lib/help/hints/Pirate_Quest/
lima-1.0b5/lib/help/player/
lima-1.0b5/lib/help/player/bin/
lima-1.0b5/lib/help/player/quests/
lima-1.0b5/lib/help/wizard/
lima-1.0b5/lib/help/wizard/coding/guilds/
lima-1.0b5/lib/help/wizard/coding/rooms/
lima-1.0b5/lib/help/wizard/lib/daemons/
lima-1.0b5/lib/help/wizard/lib/lfun/
lima-1.0b5/lib/help/wizard/lib/std/
lima-1.0b5/lib/help/wizard/mudos_doc/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/interactive/
lima-1.0b5/lib/help/wizard/mudos_doc/applies/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/concepts/
lima-1.0b5/lib/help/wizard/mudos_doc/driver/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/arrays/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/buffers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/compile/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/filesystem/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/floats/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/functions/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/general/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mappings/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/mixed/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/numbers/
lima-1.0b5/lib/help/wizard/mudos_doc/efuns/parsing/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/constructs/
lima-1.0b5/lib/help/wizard/mudos_doc/lpc/types/
lima-1.0b5/lib/include/driver/
lima-1.0b5/lib/log/
lima-1.0b5/lib/obj/admtool/
lima-1.0b5/lib/obj/admtool/internal/
lima-1.0b5/lib/obj/admtool/mudinfo/
lima-1.0b5/lib/obj/admtool/secure/
lima-1.0b5/lib/obj/secure/
lima-1.0b5/lib/obj/secure/cmd/
lima-1.0b5/lib/obj/secure/mailers/
lima-1.0b5/lib/obj/secure/shell/
lima-1.0b5/lib/obj/secure/shell/classes/
lima-1.0b5/lib/obj/tasktool/
lima-1.0b5/lib/obj/tasktool/internal/
lima-1.0b5/lib/open/
lima-1.0b5/lib/secure/
lima-1.0b5/lib/secure/cgi/
lima-1.0b5/lib/secure/modules/
lima-1.0b5/lib/secure/simul_efun/
lima-1.0b5/lib/std/adversary/
lima-1.0b5/lib/std/adversary/advancement/
lima-1.0b5/lib/std/adversary/armor/
lima-1.0b5/lib/std/adversary/blows/
lima-1.0b5/lib/std/adversary/death/
lima-1.0b5/lib/std/adversary/formula/
lima-1.0b5/lib/std/adversary/health/
lima-1.0b5/lib/std/adversary/pulse/
lima-1.0b5/lib/std/adversary/wield/
lima-1.0b5/lib/std/classes/event_info/
lima-1.0b5/lib/std/container/
lima-1.0b5/lib/std/living/
lima-1.0b5/lib/std/modules/contrib/
lima-1.0b5/lib/std/patterns/
lima-1.0b5/lib/std/race/
lima-1.0b5/lib/std/race/restricted/
lima-1.0b5/lib/std/room/
lima-1.0b5/lib/tmp/
lima-1.0b5/lib/trans/
lima-1.0b5/lib/trans/admincmds/
lima-1.0b5/lib/trans/obj/
lima-1.0b5/lib/wiz/
/* Do not remove the headers from this file! see /USAGE for more info. */

/* Copyright 1994 - 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.
 */
/* 
 *  General news daemon, by Beek
 *  Oct 10, 1994
 *
 * Interface:
 *
 * id NEWS_D->post(group, subject, message)
 * id NEWS_D->followup(group, id, message)
 * message NEWS_D->get_message(group, id)
 * id array NEWS_D->get_messages(group)
 * id array NEWS_D->get_thread(group, thread)
 * NEWS_D->get_groups()
 * varargs id NEWS_D->system_post(group, subject, message, poster)  (poster optional)
 *
 *
 * 941224, Deathblade: GUElib conversion
 * 950707, Rust: added system_post on July 7, 1995
 * ??????, Beek: modified the system to be automatic based on group prefixes.
 * 950811, Deathblade: converted to classes. added remove_post().
 * 9606xx, Ohara: added move_post() and reply_post.
 * 961205, Ohara: added change_subject at DB's grovel^H^H^H^H^H^Hrequest.

 * 
 */

#include <mudlib.h>
#include <security.h>
#include <commands.h>
#include <classes.h>

inherit M_ACCESS;

inherit CLASS_NEWSMSG;

#define SAVE_FILE "/data/news/news_d"
#define RECENT_FILE "/data/news/recent"

#define ARCHIVE_DIR "/data/news/archive"

void archive_posts();
nomask string * get_groups();

private mapping data = ([]);
private nosave mapping recent_changes = ([]);
private int new_format;

// No info on a group means never archive.
private mapping archive_info = ([ ]);

private nosave mapping restrictions = 
([
  "wiz" : ({ (: wizardp :), (: wizardp :) }),
  "admin" : ({ (: adminp :), (: adminp :) }),
  "." : ({ 0, (: adminp :) }),
]);

#define is_group(x) (member_array(x,get_groups()) != -1)

nomask void save_recent() {
    unguarded(1, (: rm, RECENT_FILE :));
    unguarded(1, (: write_file, RECENT_FILE, save_variable(recent_changes) :));
}

nomask void save_me()
{
    unguarded(1, (: save_object, SAVE_FILE :));
    foreach (string key in keys(recent_changes))
    recent_changes[key] = ([]);
    save_recent();
}

/*
** Set the archive time on a group.
*/
nomask void set_archive_time(string group, int numDays)
{
    if (!check_previous_privilege(1))
    {
	error("Insufficient privs");
    }
    if (!is_group(group))
    {
	error("First arg not a valid newsgroup");
    }
    if(!intp(numDays))
    {
	error("Number of days not an integer.");
    }
    if(numDays < 0)
    {
	error("Number of days must be greater than or equal to 0.");
    }
    if(!numDays) 
    {
	/* Never expire */
	map_delete(archive_info, group);
    }
    else
    {
	archive_info[group] = numDays;
    }
    save_me();
}





/*
** Convert the storage format from (old-style) mappings to classes
*/
private nomask void convert_news()
{
    string group;
    mapping contents;

    foreach ( group, contents in data )
    {
	mixed id;
	mapping post;

	foreach ( id, post in contents )
	{
	    class news_msg msg;

	    if ( id == "next_id" )
		continue;

#define MSG_TIME	1
#define MSG_THREAD	2
#define MSG_SUBJECT	3
#define MSG_MESSAGE	4
#define MSG_POSTER	5

	    msg = new(class news_msg,
	      time : post[MSG_TIME],
	      thread_id : post[MSG_THREAD],
	      subject : post[MSG_SUBJECT],
	      poster : post[MSG_POSTER],
	      body : post[MSG_MESSAGE]);
	    msg->userid		= lower_case(msg->poster);

	    contents[id] = msg;
	}
    }

    new_format = 1;
    save_me();
}

nomask void create()
{
    string rec;

    set_privilege(1);
    if ( clonep(this_object()) )
    {
	destruct(this_object());
	return;
    }

    restore_object(SAVE_FILE, 1);
    if (rec = read_file(RECENT_FILE))
	recent_changes = restore_variable(rec);
    else
	recent_changes = 0;

    if ( !new_format )
	convert_news();
    if (!recent_changes) {
	recent_changes = ([]);
	foreach (string key in keys(data))
	    recent_changes[key] = ([]);
    } else {
	int changed = 0;

	foreach (string key, mixed value in recent_changes) {
	    if (value == "#removed#") {
		map_delete(data, key);
		changed = 1;
	    } else {
		/* possible for new groups */
		if (!data[key]) data[key] = ([]);
		foreach (string key2 in keys(value)) {
		    data[key][key2] = value[key2];
		    changed = 1;
		}
	    }
	    recent_changes[key] = ([]);
	}
    }
    foreach (string key, mixed value in data) {
	if (mapp(value)) {
	    foreach (string key2, class news_msg msg in value) {
		if (classp(msg) && !msg->body)
		    map_delete(value, key2);
	    }
	}
    }
    save_me();
    archive_posts();
}

private nomask int get_new_id(string group)
{
    int id = data[group]["next_id"]++;

    recent_changes[group]["next_id"] = data[group]["next_id"];

    return id;
}

private nomask void notify_users(string group, class news_msg msg)
{
    CHANNEL_D->deliver_channel("news",
      sprintf("%s: %s [%s]",
	group,
	msg->subject[0..39],
	msg->poster));
}

nomask int post(string group, string subject, string message)
{
    int post_id;
    class news_msg msg;

    if (!data[group]) return 0;
    post_id = get_new_id(group);
    msg = new(class news_msg,
      time : time(),
      thread_id : post_id,
      subject : subject,
      userid : this_user()->query_userid(),
      body : message);
    msg->poster          = capitalize( msg->userid );

    data[group][post_id] = msg;
    recent_changes[group][post_id] = msg;
    save_recent();

    notify_users(group, msg);

    return post_id;
}

varargs nomask int system_post(string group,
  string subject,
  string message,
  string poster)
{
    int post_id;
    class news_msg msg;

    //### need to think on this. I don't think we want to require priv 1
    //### ... especially since even *this* object doesn't have that.  And
    //### just checking the previous object is a no-no.
    //    if ( get_privilege(previous_object()) != 1 )
    //	return 0;
    if ( !data[group] )
	return 0;
    post_id = get_new_id(group);
    msg = new(class news_msg,
      time : time(),
      thread_id : post_id,
      subject : subject);
    if ( poster )
    {
	msg->poster = poster;
	msg->userid = base_name(previous_object());
    }
    else if ( this_body() )
    {
	msg->userid = this_user()->query_userid();
	msg->poster = capitalize( msg->userid );
    }
    else if ( this_user() )
    {
	msg->userid = this_user()->query_userid();
	msg->poster = capitalize( msg->userid);
    }
    else
    {
	msg->poster = msg->userid = mud_name();
    }
    msg->body		= message;

    data[group][post_id] = msg;
    recent_changes[group][post_id] = msg;
    save_recent();

    notify_users(group, msg);

    return post_id;
}

varargs nomask void add_group(string group)
{
    string fn;

    fn = base_name(previous_object());
    if (fn != ADMTOOL) return;

    data[group] = (["next_id":1]);
    recent_changes[group] = (["next_id":1]);
    save_recent();
}

nomask void remove_group(string group)
{
    string fn;

    fn = base_name(previous_object());
    if (fn != ADMTOOL) return;

    map_delete(data, group);
    recent_changes[group] = "#removed#";
    save_recent();
}

nomask int followup(string group, int id, string message)
{
    string subject;
    int post_id;
    class news_msg msg;

    if (!data[group]) return 0;
    if (!data[group][id]) return 0;
    post_id = get_new_id(group);
    subject = ">" + ((class news_msg)data[group][id])->subject;

    id = ((class news_msg)data[group][id])->thread_id;

    msg = new(class news_msg,
      time : time(),
      thread_id : id, /* link to original thread_id */
      subject : subject,
      userid : this_user()->query_userid(),
      body : message);
    msg->poster       = capitalize( msg->userid );

    data[group][post_id] = msg;
    recent_changes[group][post_id] = msg;
    save_recent();

    notify_users(group, msg);

    return post_id;
}

nomask class news_msg get_message(string group, int id)
{
    class news_msg msg;

    if ( !data[group] )
	return 0;

    msg = data[group][id];
    if ( !msg )
	return 0;

    /* sigh */
    //### oops... can't do this yet... leave this unsafe for now
    //    return copy(msg);
    return msg;
}

nomask int remove_post(string group, int id)
{
    class news_msg msg;

    if ( !data[group] )
	return 0;

    msg = data[group][id];
    if ( !msg || !msg->body )
	return 0;

    if ((this_user() && !adminp(this_user()) && msg->userid != this_user()->query_userid()) &&
      (msg->userid != base_name(previous_object())))
    {
	return 0;
    }

    msg->body = 0;
    recent_changes[group][id] = msg;
    save_recent();
    return 1;
}

nomask int * get_messages(string group)
{
    return keys(data[group]) - ({ "next_id" });
}

nomask int * get_thread(string group, int thread)
{
    return filter_array(keys(data[group]),
      (: $1 != "next_id" &&
	((class news_msg)data[$(group)][$1])->thread_id
	== $(thread) :) );
}

nomask string * get_groups()
{
    string array ret;

    // filter before sorting; the func is typically pretty cheap, and
    // and calling them all is O(n).  Sorting the list first is more
    // expensive
    ret = filter(keys(data), function(string group)
      {
	  array a;
	  string prefix;
	  function f;
	  int i = member_array('.', group, 1) - 1;
	  if( i == -2) i = sizeof(group);

	  prefix = group[0..i];
	  a = restrictions[prefix];

	  if( !sizeof(a)) return 1;
	  f = a[0];
    if(!f) return 1;
	  return evaluate(f, this_user());
      }
    );
    return sort_array(ret, 1);
}

nomask int query_write_to_group( string group )
{
    function f;
    array a;

    string prefix;
    int i = member_array('.', group, 1) - 1;
    if( i == -2) i = sizeof(group);

    prefix = group[0..i];

    a = restrictions[prefix];
    if( !sizeof(a)) return 1;
    f = a[1];
    if( !f) return 1;
    return evaluate(f, this_user());
}


nomask int get_group_last_id(string group)
{
    return data[group]["next_id"] - 1;
}

nomask void dump_to_file(string group, string fname)
{
    mapping contents = data[group];
    int id;

    foreach ( id in sort_array(keys(contents) - ({ "next_id" }), 1) )
    {
	class news_msg msg = contents[id];

	if ( !msg->body )
	    continue;

	write_file(fname,
	  sprintf("---\nposter: %s\nsubject: %s\ndate: %s\n\n%s\n",
	    msg->poster, msg->subject,
	    intp(msg->time) ? ctime(msg->time) : msg->time,
	    msg->body));
    }
}

private nomask void archive_post(string group, int id)
{
    class news_msg msg = data[group][id];

    unguarded(1, (: write_file, 
	sprintf("%s/%s", ARCHIVE_DIR, group),
	sprintf("---\nposter: %s\nsubject: %s\ndate: %s\n%s\n\n",
	  msg->poster, msg->subject,
	  intp(msg->time) ? ctime(msg->time) : msg->time,
	  msg->body)
      :) );

    /* This doesn't give w/ the newsreader. */
#if 0
    map_delete(data[group], id);
#else
    /* Do this instead. */
    remove_post(group, id);
#endif
}

nomask void archive_posts()
{
    int archive_days;
    int archive_time;
    string group;
    mapping contents;



    foreach ( group, contents in data )
    {
	mixed id;
	class news_msg msg;

	archive_days = archive_info[group];
	if(!archive_days)
	{
	    continue;
	}
	archive_time = time() - archive_days * 24 * 60 * 60;
	foreach ( id, msg in contents )
	{
	    if ( id == "next_id" )
		continue;
	    //### how to archive posts with strings as times?
	    //There don't seem to be any.
	    if ( intp(msg->time) && msg->time < archive_time ) 
		archive_post(group, id);
	}
    }
}


// Blame --OH. for this code :)
//### the write()'s in this code are wrong; printing error messages is the
//### newsreader's job, not the daemons.  This should return error messages
//### if it needs to.
	    
nomask void move_post( string curr_group, int curr_id, string to_group )
{
    class  news_msg msg;
    int new_id;

    msg = copy( data[curr_group][curr_id]);
    if( !adminp(this_user()) && msg->userid != this_user()->query_userid())
    {
	write( "You cannot move posts which don't belong to you.\n");
	return;
    }
    if( curr_group == to_group )
    {
	write( "Same group. Post not moved.\n");
	return;
    }
    if( !query_write_to_group( to_group ))
    {
	write( "You don't have permission to move posts to " + to_group + "\n");
	return;
    }
    new_id = get_new_id(to_group);
    msg->body = "(Originally in " + curr_group + ")\n" + msg->body;
    data[to_group][new_id] = msg;
    recent_changes[to_group][new_id] = msg;
    remove_post( curr_group, curr_id );
    write( "Post moved.\n");
    save_recent();
}

void remove() {
    save_me();
}


nomask void change_header( string group, int id, string header )
{
    class news_msg msg;
    object tu = this_user();

    msg = data[group][id];
    if( !adminp( tu) && tu->query_userid() != msg->userid)
    {
	write( "You cannot change the subjects of posts that don't belong to you\n");
    }
    else if( sizeof( header ))
	((class news_msg)data[group][id])->subject = header;
    else
	write( "Subject not changed.\n");
    save_recent();
    return;
}

array search_for(string what) {
    array ret = ({});
    
    foreach (string group, mapping contents in data) {
	foreach (mixed id, class news_msg post in contents) {
	    if (id == "next_id" || !post->body)
		continue;

	    if (regexp(post->body, what) && query_write_to_group(group))
		ret += ({ ({ group, id }) });
	}
    }

    return ret;
}

array search_for_author(string who) {
    array ret = ({});
    
    foreach (string group, mapping contents in data) {
	foreach (mixed id, class news_msg post in contents) {
	    if (id == "next_id" || !post->body)
		continue;

	    if (lower_case(post->poster) == lower_case(who) && query_write_to_group(group))
		ret += ({ ({ group, id }) });
	}
    }

    return ret;
}