/* * /std/board/bboard.c * * generic bulletin board object * * * Truilkan, ???? allow read/write permissions and assignment of * projects, rewritten to use a mapping. * Zak, 930119 added 'followup', 'mailnote' * Shemp, 930224 added maker stuff * Zak, 930225 made it range from 1..x instead of 0..x-1 * Shemp, 930226 made the note removed message reflect the * actually note removed instead of note-1 * Shemp, 930228 added function to enable an admin to "lock" a * note so that it doesn't get removed with the * automatic removal of aged notes. * Zak, 930309 changed to new output format. Optimised a bit * too. Also checks before mkdir({save,attic}_dir). * Store all `set'able things in temp vars, so * they're not lost during the restore_object() * in set_save_file * Zak, 930314 enhanced locking/unlocking of notes, won't * archive locked notes. * Zak, 930320 only messages[] is non-static, num_messages * is calculated at each restore_object() * Zak, 930321 set_default_long_text() to update your * bboard's default long.text. * Watcher, 930322 Switched name recognition to use link to * prevent impersonation with monster bodies * Zak, 930413 hacked to use my new mailer. * Telsin, 930526 added archive_board(), does what you think. * several other mods to have the board archive * at each reset. * Zak, 930530 unique_name() now calls get_oid(). Put in * define for IVORY - undefine if using new TMI * mudlib. isghost() prints a message & returns * 0 if player is a ghost. Archiving occurs at * each post now - not on reset. * Zak, 930618 added 'read next' capabilities, as well as * `date of newest note in short()'. Reformatted * code * Zak, 930620 implemented parse_num - given appropriate args * will automagically do `current', `next', etc * parsing for you. It calls isghost() as well... * Zak, 930621 added `note <num', `help board', save_obj & * set_cur_note in each operation, show curnote * in short(). Moved help text to separate file. * removed curnote from short... too much hassle. * Zak, 930623 current note is now just a temp variable. It * only `permanently' stores the newest note that * you've read. Added `read new'. Modified short() * to reflect `x new notes of'. removed `last mod' * tackyness. (Thanx to Telsin for ideas...) * Zak, 930625 changed _CUR & _NEW to different variables, to * avoid a bug in /std/objects/prop.c. * Also fixed initial setting of cur note * in init(). * Zak, 930626 installed on TMI-2, removed kludge for prop bug. * reformatted short ([x notes, y new]) now.. * Pallando, 940722 Stopped chain of Re: Re: Re: Re: Re: Re: * Blue, 940908 Fixed mapping passed to send_mail by mailnote, * changed time() key from "time" to "date". Thanks * to Baccara for noticing the problem. //Fixed the followup on MAX_NOTES problem Leto somewhere 1194 * Blue, 941128 No longer archives locked notes. */ /* * Please mail all bugs, suggestions, ideas to zak@tmi-2, or * internet mail <zak@rmit.edu.au> */ #include <move.h> #include <mudlib.h> #include <daemons.h> #include <mailer.h> #include <board.h> inherit OBJECT; // undef for TMI 1.0 based mudlibs (with link_data()) #undef IVORY // various defines. #define BBOARD_HELP "/std/board/board.help" #define VERSION "bboard v3.0.2z, 930626" // these 2 are overridden by set_max_messages & set_carryover #define MAX_MESG 30 // max # notes before archiving #define MIN_MESG 10 // min # notes to leave on board #ifdef IVORY # define QNAME query("name") # define QCAPNAME query("cap_name") #else # define QNAME link_data("name") # define QCAPNAME link_data("cap_name") #endif // ([messages]) contains "title", "poster", "time", "doer", "body", // "locked", "id". (Don't maps make great substitutes for structures? :) mapping *messages; int id_ref; static string attic_dir, save_dir; static mapping title; static string file, location, maker, board_set, second_arg; static int num_messages, max_messages, min_messages, carryover; int query_prevent_shadow() { return 1; } // query_prevent_shadow string query_info(int i, string field) { if (i < num_messages && i >= 0) return messages[i][field]; else return ""; } // // these valid_* functions are designed to be masked by the one inheriting // int valid_note_read(int i) { return 1; } int valid_post() { if (getuid(this_player())=="guest") { notify_fail("Due to abuse, Guest is no longer allowed to post.\n"); return 0; } return 1; } int valid_assign() { return 0; } int valid_remove(int i) { string poster; if ( messages[i]["locked"] != geteuid(this_player()) && !adminp(geteuid(this_player())) && (string)this_player()->QCAPNAME != messages[i]["poster"] && (string)this_player()->QNAME != maker ) { notify_fail("You can only remove your own notes.\n"); return 0; } return 1; } // valid_remove int valid_lock() { return (adminp(geteuid(this_player()))); } // // end valid_* maskable code. // void set_carryover(int t) { if (t < 0) t = 0; carryover = t; } int query_carryover() { if (!carryover) carryover = MIN_MESG; return carryover; } int query_max_messages() { if (!max_messages) max_messages = MAX_MESG; return max_messages; } void set_max_messages(int num) { if (num < 0) num = 0; max_messages = num; } private static void set_maker(string o) { maker = lower_case(o); } int query_num_messages() { return num_messages; } string query_maker() { return maker; } void debug_info() { printf("board_set is %s\n", board_set); printf("cur note is %d\n", QUERY_CUR); printf("new note is %d\n", QUERY_NEW); } int convert_id( int idx ) { int i; i = num_messages; while( i-- ) { if( messages[i]["id"] == idx ) return i; if( messages[i]["id"] < idx ) return -1; } return 0; } string get_time(int t) { return extract(ctime(t), 0, 9); } string headers() { string tmp; int i, c; if (!num_messages) return ""; tmp = ""; i = -1; c = QUERY_CUR; if (!c) c = -1; while ( ++i < num_messages ) { tmp += sprintf("%3d %s %-12s (%s)%s %s%s\n", (i + 1), c == messages[i]["id"] ? ">" : " ", messages[i]["poster"], get_time(messages[i]["time"]), (messages[i]["locked"] ? "*" : " "), messages[i]["title"], (messages[i]["doer"] ? " <" + messages[i]["doer"] + ">" : "" )); } return tmp; } // headers string query_long() { return ( sprintf("%sThe %s has %s note%s:\n%s", (string)query("long.text"), (string)query("short.text"), (num_messages == 0 ? "no" : "" + num_messages), (num_messages == 1 ? "" : "s"), headers() )); } // query_long int query_num_new() { int i, n, c; n = QUERY_NEW; i = num_messages; c = 0; if( !intp( n ) ) return c; while (i--) { if (messages[i]["id"] <= n) break; c++; } return c; } string query_short() { int c; string ns; c = query_num_new(); if (c) ns = sprintf(", %d new", c); else ns = ""; return (sprintf("%s [%d note%s%s]", (string)query("short.text"), num_messages, (num_messages == 1 ? "" : "s"), ns)); } // query_short void set_save_dir(string s) { save_dir = s + "/"; if (file_size(save_dir) == -1) mkdir(save_dir); } string query_save_dir(string s) { return save_dir; } void set_attic_dir(string s) { attic_dir = s + "/"; if (file_size(attic_dir) == -1) mkdir(attic_dir); } string query_attic_dir() { return attic_dir; } void set_location(string l) { location = l; #ifdef IVORY base::move(l); #else ob::move(l); #endif } int move(mixed dest) { return MOVE_NOT_ALLOWED; } void archive_board() { int i,m; string *tmpa, temp_file; mapping *temp_messages, *save_messages; tmpa = explode(file,"/"); temp_file = attic_dir + tmpa[sizeof(tmpa) - 1] + "_" + time(); temp_messages = messages; messages = ({ }); save_messages = ({ }); m = num_messages-query_carryover(); for (i = 0; i < m; i++) { if (temp_messages[i]["locked"]) { save_messages += ({ temp_messages[i] }); } else { messages += ({ temp_messages[i] }); } } save_messages += temp_messages[m..num_messages-1]; save_object(temp_file); messages = save_messages; num_messages = sizeof(messages); save_object(file); } // archive_board void set_save_file(string s) { int i; file = save_dir + s; #ifndef IVORY seteuid(geteuid(previous_object())); #endif restore_object(file); num_messages = sizeof(messages); if (num_messages>=query_max_messages()) archive_board(); for (i = 0; i < num_messages; i++) { if (!messages[i]["id"]) messages[i]["id"] = ++id_ref; } board_set = replace_string(file, "/", "_"); } // set_save_file mixed query_save_file() { return file; } void set_default_long_text() { set("long.text", @LONG This is a bulletin board. For information on how to use it, use `help board'. LONG ); set("silent_look", 1); } void create() { #ifndef IVORY seteuid(getuid(this_object())); #endif set_max_messages(MAX_MESG); set_carryover(MIN_MESG); set_attic_dir("/attic/boards"); set("short", "@@query_short"); set("long", "@@query_long"); set("short.text", "A bulletin board"); set_default_long_text(); set("id",({"board","bulletin board"})); set("silent_look", 1); messages = ({ }); num_messages = 0; id_ref = 0; title = ([]); board_set = "board/default"; // in case you forget set_save_file() } // create void init() { add_action("new_post","post"); add_action("read", "read"); add_action("remove_msg", "remove"); add_action("edit_note","edit"); add_action("assign","assign"); add_action("followup_note", "followup"); add_action("mail_note", "mailnote"); add_action("lock_note", "mlock"); add_action("unlock_note", "munlock"); add_action("note", "note"); add_action("help", "help"); // add_action("comment", "comment"); if (!QUERY_CUR) SET_CURID(QUERY_NEW); } // init string unique_name() { return "/tmp/bb_" + getoid(this_player()); } int isghost() { if (this_player()->query("ghost")) { notify_fail("Your ghostly hands pass right through the board.\n"); return 1; } } // command argument parsing routine below #define PN_OPT 1 // 2nd arg is optional #define PN_NEED 2 // 2nd arg is necessary #define PN_CUR 4 // `current' is a valid note num #define PN_NEXT 8 // `next' is a valid note num #define PN_NEW 16 // `new' is a valid note num varargs int parse_num(string str, string usage, int arg2type, string usg2) { int i, c; string t; usage = "Usage: " + usage + " <num> " + (usg2 ? usg2 : "") + "\n"; second_arg = 0; if (isghost()) return -1; if (!str) { notify_fail(usage); return -1; } if (str != "board" && (present(str, environment(this_object())) || !undefinedp(environment(this_object())->query("item_desc/"+str)))) { notify_fail("Maybe you should look at it?\n"); return -1; } if ((arg2type & PN_NEED) && sscanf(str,"%s %s", t, second_arg) != 2) { notify_fail(usage); // compulsory 2nd arg return -1; } if ((arg2type & PN_OPT) && (sscanf(str, "%s %s", t, second_arg) != 2)) t = str; if (!t) t = str; if ( (t == "next" && !(arg2type & PN_NEXT)) ||(t == "current" && !(arg2type & PN_CUR)) ||(t == "new" && !(arg2type & PN_NEW)) ) { notify_fail(usage); return -1; } if (t == "next" || t == "current" || t == "new") { if (t == "new") c = QUERY_NEW; else c = QUERY_CUR; if (!c) { // Added by Inspiral. write( "Reading note 1.\n" ); // Below commented out by Inspiral. } if (!num_messages) { notify_fail("There aren't any messages on this board.\n"); return -1; } i = num_messages; while (i--) if (messages[i]["id"] <= c) break; if (t == "current") { if (messages[i]["id"] != c) { notify_fail("Current note invalid.\n"); return -1; } } else { // t == `new' or `next' if (++i == num_messages) { if (t == "next") notify_fail("At last note.\n"); else notify_fail("No new notes.\n"); return -1; } } return i; } else { // just a number if (sscanf(t, "%d", i) != 1) { notify_fail(usage); return -1; } if (i < 1 || i > num_messages) { notify_fail("No note with that number.\n"); return -1; } return i-1; } } // parse_num // // actual commands follow // int help(string str) { if (str != "board") return 0; this_player()->more( BBOARD_HELP ); return 1; } int assign(string str) { int i; i = parse_num(str, "assign", PN_NEED + PN_CUR, "<name>"); if (i == -1) return 0; if (!valid_assign()) return 0; messages[i]["doer"] = capitalize(second_arg); SET_CUR(i); save_object(file); return 1; } // assign int lock_note(string str) { int i; i = parse_num(str, "mlock", PN_CUR); if (i == -1) return 0; if (!valid_lock()) { notify_fail("Permission denied - you may not lock that note.\n"); return 0; } messages[i]["locked"] = geteuid(this_player()); printf("Note %s locked.\n", str); // XXX: could check if already locked. SET_CUR(i); save_object(file); return 1; } // lock_note int unlock_note(string str) { int i; i = parse_num(str, "munlock", PN_CUR); if (i == -1) return 0; if (!valid_lock()) { notify_fail("Permission denied - you may not unlock that note.\n"); return 0; } messages[i]["locked"] = 0; // XXX: should check if locked or not printf("Note %s unlocked.\n", str); SET_CUR(i); save_object(file); return 1; } // unlock_note int comment(string str) { int i; i = parse_num(str, "comment", PN_CUR); if (i == -1) return 0; printf("This function does nothing at the moment.\n"); return 1; } // comment int new_post(string str) { string hed; if (isghost()) return 0; if (!valid_post()) return 0; if (!str) str = "< no title >"; if(title[this_player()]) printf("Warning! You are already marked as posting!\n"); if (num_messages >= query_max_messages()) { archive_board(); /* check if space for new messages was made by archive_board() */ if (num_messages >= query_max_messages()) { printf("Too many messages. You must remove some first.\n"); return 1; } } if (strlen(str) > 50) { notify_fail("Title is too long.\n"); return 0; } title[this_player()] = str; printf("Editing note.\n"); hed = unique_name(); rm(hed); this_player()->edit(hed, "postit", this_object()); return 1; } // new_post void abort() { rm ((string)this_player()->query_edit_filename()); map_delete(title,this_player()); } varargs int postit(string mtitle) { string filename; mapping msg; filename = (string)this_player()->query_edit_filename(); msg = allocate_mapping(4); msg["title"] = mtitle ? mtitle : title[this_player()]; msg["poster"] = capitalize((string)this_player()->QCAPNAME); msg["time"] = time(); msg["body"] = ( read_file( filename ) ); msg["id"] = ++id_ref; rm(filename); this_player()->set_edit_filename(""); if (!msg["body"]) return 0; messages += ({ msg }); printf("Posted.\n"); num_messages++; map_delete(title,this_player()); // XXX: don't set current as your own post... save_object(file); return 1; } // postit int read(string str) { int i; string tmp; if (id(str)) { write(query_long()); return 1; } if (str == "version") { printf("BBoard version: %s\n", VERSION); return 1; } i = parse_num(str, "read", PN_CUR + PN_NEXT + PN_NEW); if (i == -1) return 0; if (this_object()->valid_note_read(i)) { printf("Note %3d %-12s (%s) %s%s\n", (i + 1), messages[i]["poster"], get_time(messages[i]["time"]), messages[i]["title"], (messages[i]["doer"] ? " <" + messages[i]["doer"] + ">" : "" )); if (QUERY_NEW < messages[i]["id"]) SET_NEW(i); SET_CUR(i); this_player()->more(({""}) + explode(messages[i]["body"],"\n")); return 1; } return 0; } // read mixed read_text(string str) { int i; string tmp; if (id(str)) { tmp = query_long(); return tmp; } if (str == "version") { tmp = sprintf("BBoard version: %s\n", VERSION); return tmp; } i = parse_num(str, "read", PN_CUR + PN_NEXT + PN_NEW); if (i == -1) return 0; if (this_object()->valid_note_read(i)) { tmp = sprintf("Note %3d %-12s (%s) %s%s\n", (i + 1), messages[i]["poster"], get_time(messages[i]["time"]), messages[i]["title"], (messages[i]["doer"] ? " <" + messages[i]["doer"] + ">" : "" )); if (QUERY_NEW < messages[i]["id"]) SET_NEW(i); SET_CUR(i); tmp += messages[i]["body"]; return tmp; } return 0; } // read int remove_msg(string str) { string poster,tmp, *tmpmsg; int i,j; i = parse_num(str, "remove", PN_CUR); if (i == -1) return 0; if (!valid_remove(i)) return 0; messages = messages[0..(i-1)]+messages[(i+1) .. (sizeof(messages) - 1)]; printf("Note %d removed.\n", i+1); num_messages--; save_object(file); return 1; } // remove_msg static int orig_number; int edit_note(string str) { int i, tmp, j, m; string *lines, fl; i = parse_num(str, "edit", PN_CUR); if (i == -1) return 0; if (messages[i]["locked"]) { notify_fail("That message is locked\n"); return 0; } if (!adminp(geteuid(this_player())) && messages[i]["poster"] != capitalize((string)this_player()->QCAPNAME)) { notify_fail("You may only edit your own messages.\n"); return 0; } fl = unique_name(); rm(fl); write_file(fl, messages[i]["body"]); orig_number = i; printf("Editing: %s\n", messages[i]["title"]); this_player()->edit(fl,"edit_stop_ed",this_object()); return 1; } // edit_note void edit_stop_ed() { SET_CUR(orig_number); messages[orig_number]["body"] = ( read_file((string)this_player()->query_edit_filename()) ); printf("Note %d edited.\n", orig_number + 1); abort(); save_object(file); } // edit_stop_ed int followup_note(string str) { string *lines; int i; if (!valid_post()) return 0; if ( to_int(str) == MAX_MESG -1 ) { archive_board(); } i = parse_num(str, "followup", PN_CUR + PN_OPT, "[<title>]"); if (i == -1) return 0; if (num_messages >= query_max_messages()) { archive_board(); /* check if space for new messages was made by archive_board() */ if (num_messages >= query_max_messages()) { printf("Too many messages. You must remove some first.\n"); return 1; } } if(title[this_player()]) printf("Warning! You are already marked as posting!\n"); if (!second_arg) { second_arg = messages[i]["title"]; if (second_arg[0..3] != "Re: ") second_arg = "Re: " + second_arg; } if (strlen(second_arg) > 50) { notify_fail("Title is too long.\n"); return 0; } title[this_player()] = second_arg; printf("Following up to note %d as '%s'\n", i + 1, second_arg); lines = explode(messages[i]["body"],"\n"); second_arg = unique_name(); rm(second_arg); write_file(second_arg, sprintf("On %s, %s wrote:\n> %s\n", get_time(messages[i]["time"]), messages[i]["poster"], implode(lines, "\n> "))); this_player()->edit(second_arg,"postit",this_object()); SET_CUR(i); return 1; } // followup_note int mail_note(string str) { int i; string myname, msg, *r; i = parse_num(str, "mailnote", PN_CUR); if (i == -1) return 0; msg = sprintf("Title: %s\nFrom: %s\nDate: %s\n%s\n%s\n", messages[i]["title"], messages[i]["poster"], get_time(messages[i]["time"]), (messages[i]["doer"] ? "Doer: " + messages[i]["doer"] + "\n" : "" ), messages[i]["body"] ); myname = (string)this_player()->QNAME; #if 1 // XXX: change the following if using the old mailer r = (string *)MAILER_D->send_mail( ([ "from" : myname, "to" : ({ myname }), "subject" : "Note '" + messages[i]["title"] + "'", "date" : time(), "message" : msg ]) ); #else r = (string *)MAILER_D->send_mail( ({myname}), ({}), (string) this_player()->QNAME, "Note: " + messages[i]["title"], time(), msg ); #endif if (sizeof(r) != 1) printf("Something went wrong with the mailing\n"); else { printf("Note '%s' mailed to you.\n", messages[i]["title"]); MAILER_D->flush_files(); } SET_CUR(i); return 1; } // mail_note int note(string str) { int i; if (!str) str = "current"; i = parse_num(str, "note", PN_CUR + PN_NEXT); if (i == -1) return 0; SET_CUR(i); printf("Current note set to %d.\n", i+1); return 1; } // note void clean_up() { return; } // clean_up void set_id_ref(int n) { if (!adminp(previous_object())) return; id_ref = n; } int query_id_ref() { return id_ref; }