/************************************************************************** * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, * * Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. * * * * Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael * * Chastain, Michael Quan, and Mitchell Tse. * * * * In order to use any part of this Merc Diku Mud, you must comply with * * both the original Diku license in 'license.doc' as well the Merc * * license in 'license.txt'. In particular, you may not remove either of * * these copyright notices. * * * * Much time and thought has gone into this software and you are * * benefiting. We hope that you share your changes too. What goes * * around, comes around. * *************************************************************************** * ROM 2.4 is copyright 1993-1998 Russ Taylor * * ROM has been brought to you by the ROM consortium * * Russ Taylor (rtaylor@hypercube.org) * * Gabrielle Taylor (gtaylor@hypercube.org) * * Brian Moore (zump@rom.org) * * By using this code, you have agreed to follow the terms of the * * ROM license, in the file Rom24/doc/rom.license * *************************************************************************** * Note Board system, (c) 1995-96 Erwin S. Andreasen, erwin@andreasen.org * * Basically, the notes are split up into several boards. The boards do * * not exist physically, they can be read anywhere and in any position. * * Each of the note boards has its own file. Each of the boards can have * * its own "rights": who can read/write. * * Each character has an extra field added, namele the timestamp of the * * last note read by him/her on a certain board. * * The note entering system is changed too, making it more interactive. * * entering a note, a character is put AFK and into a special CON_ state. * * Everything typed goes into the note. * * For the immortals it is possible to purge notes based on age. An * * archive options is available which moves the notes older than X days * * into a special board. The file of this board should then be moved into * * some other directory during e.g. the startup script and perhaps renamed * * depending on date. * * Note that write_level MUST be >= read_level or else there will be * * strange output in certain functions. * * Board DEFAULT_BOARD must be at least readable by *everyone*. * *************************************************************************** * 1stMud ROM Derivative (c) 2001-2004 by Markanth * * http://www.firstmud.com/ <markanth@firstmud.com> * * By using this code you have agreed to follow the term of * * the 1stMud license in ../doc/1stMud/LICENSE * ***************************************************************************/ #include "merc.h" #include "interp.h" #include "recycle.h" #include "tables.h" #include "data_table.h" #define L_SUP (MAX_LEVEL - 1) Proto(const char *format_sender, (NoteData *)); Proto(const char *format_to_list, (NoteData *)); BoardData boards[MAX_BOARD] = { {"Announce", "Announcements from Immortals", 0, L_SUP, "all", DEF_NORMAL, 60, NULL, NULL, BOARD_NONE} , {"General", "General discussion", 0, 2, "all", DEF_INCLUDE, 21, NULL, NULL, BOARD_NONE} , {"Ideas", "Suggestion for improvement", 0, 2, "all", DEF_NORMAL, 60, NULL, NULL, BOARD_NONE} , {"Bugs", "Typos, bugs, errors", 0, 1, "imm", DEF_NORMAL, 60, NULL, NULL, BOARD_NONE} , {"Games", "Mud Games, Global Quests, ect.", 0, 1, "all", DEF_NORMAL, 10, NULL, NULL, BOARD_NONE} , {"Personal", "Personal messages", 0, 1, "all", DEF_EXCLUDE, 28, NULL, NULL, BOARD_NOWEB} , {"Immortal", "Immortals only", LEVEL_IMMORTAL, LEVEL_IMMORTAL, "imm", DEF_INCLUDE, 21, NULL, NULL, BOARD_NOWEB} }; const char *szFinishPrompt = "({WC{x)ontinue, ({WV{x)iew, ({WP{x)ost or ({WF{x)orget it?"; long last_note_stamp = 0; void finish_note(BoardData * board, NoteData * note) { if (!NullStr(note->reply_text)) replace_strf(¬e->text, "%s" NEWLINE "%s", note->reply_text, note->text); if (last_note_stamp >= current_time) note->date_stamp = ++last_note_stamp; else { note->date_stamp = current_time; last_note_stamp = current_time; } Link(note, board->note, next, prev); SetBit(board->flags, BOARD_CHANGED); #ifdef HAVE_SENDMAIL if (!NullStr(note->email_addr)) { FILE *mail; if ((mail = popen("sendmail -t", "w")) != NULL) { fprintf(mail, "To: %s\n", format_to_list(note)); fprintf(mail, "From: %s\n", format_sender(note)); if (!NullStr(note->reply_addr)) fprintf(mail, "Reply-to: %s\n", note->reply_addr); fprintf(mail, "X-Mailer: %s Mailer\n", mud_info.name); fprintf(mail, "Subject: %s\n\n", note->subject); fprintf(mail, note->text); pclose(mail); } } #endif } int board_number(const BoardData * board) { int i; for (i = 0; i < MAX_BOARD; i++) if (board == &boards[i]) return i; return -1; } Lookup_Fun(board_lookup) { int i; for (i = 0; i < MAX_BOARD; i++) if (!str_cmp(boards[i].short_name, name)) return i; return -1; } static void unlink_note(BoardData * board, NoteData * note) { UnLink(note, board->note, next, prev); } static NoteData *find_note(CharData * ch, BoardData * board, int num) { int count = 0; NoteData *p; for (p = board->note_first; p; p = p->next) if (++count == num) break; if ((count == num) && is_note_to(ch, p)) return p; else return NULL; } const char *format_to_list(NoteData * note) { if (!NullStr(note->email_addr)) { if (!NullStr(note->to_list)) { static char buf[MSL]; sprintf(buf, "%s <%s>", note->to_list, note->email_addr); return buf; } else return note->email_addr; } return note->to_list; } const char *format_sender(NoteData * note) { if (!NullStr(note->reply_addr)) { if (!NullStr(note->sender)) { static char buf[MSL]; sprintf(buf, "%s <%s>", note->sender, note->reply_addr); return buf; } else return note->reply_addr; } return note->sender; } static void show_note_to_char(CharData * ch, NoteData * note, int num) { Buffer *buffer; buffer = new_buf(); bprintlnf(buffer, NEWLINE "{YBoard{x: %s" NEWLINE "[{x%4d{x] {Y%s{x: {g%s{x" NEWLINE "{YDate{x: %s" NEWLINE "{YTo{x: %s", ch->pcdata->board->short_name, num, format_sender(note), note->subject, note->date, format_to_list(note)); bprintlnf(buffer, "{g%s{x" NEWLINE "%s{g%s{x", draw_line(ch, "{W={w=", 0), note->text, draw_line(ch, "{W={w=", 0)); sendpage(ch, buf_string(buffer)); free_buf(buffer); } static void check_notes(BoardData * b) { NoteData *p, *p_next; for (p = b->note_first; p; p = p_next) { p_next = p->next; if (p->expire < current_time) { UnLink(p, b->note, next, prev); free_note(p); SetBit(b->flags, BOARD_CHANGED); } } } TableSave_Fun(rw_note_data) { char file[MIL]; BoardData *board; int i; for (i = 0; i < MAX_BOARD; i++) { board = &boards[i]; if (type == act_write && !IsSet(board->flags, BOARD_CHANGED)) continue; sprintf(file, NOTE_DIR "%s", board->short_name); RemBit(board->flags, BOARD_CHANGED); logf("%s %s data...", type == act_read ? "Loading" : "Saving", board->short_name); rw_sublist(type, file, NoteData, board, note); if (type == act_read) check_notes(board); } } bool is_note_to(CharData * ch, NoteData * note) { if (!str_cmp(ch->name, note->sender)) return true; if (is_ignoring(ch, note->to_list, IGNORE_NOTES)) return false; if (is_full_name("all", note->to_list)) return true; if (IsImmortal(ch) && (is_full_name("imm", note->to_list) || is_full_name("imms", note->to_list) || is_full_name("immortal", note->to_list) || is_full_name("god", note->to_list) || is_full_name("gods", note->to_list) || is_full_name("immortals", note->to_list))) return true; if ((get_trust(ch) == MAX_LEVEL) && (is_full_name("imp", note->to_list) || is_full_name("imps", note->to_list) || is_full_name("implementor", note->to_list) || is_full_name("implementors", note->to_list))) return true; if (is_clan(ch) && (is_exact_name("clan", note->to_list) || is_exact_name(CharClan(ch)->name, note->to_list))) return true; if (is_full_name(ch->name, note->to_list)) return true; if (!NullStr(ch->pcdata->email)) { if (!NullStr(note->email_addr) && is_name(ch->pcdata->email, note->email_addr)) return true; if (!NullStr(note->reply_addr) && is_name(ch->pcdata->email, note->reply_addr)) return true; } if (is_number(note->to_list) && get_trust(ch) >= atoi(note->to_list)) return true; return false; } int unread_notes(CharData * ch, BoardData * board) { NoteData *note; time_t last_read; int count = 0; if (board->read_level > get_trust(ch)) return BOARD_NOACCESS; last_read = ch->pcdata->last_note[board_number(board)]; for (note = board->note_first; note; note = note->next) if (is_note_to(ch, note) && ((long) last_read < (long) note->date_stamp)) count++; return count; } NoteData *last_note(CharData * ch, BoardData * board) { NoteData *note; if (board->read_level > get_trust(ch)) return NULL; for (note = board->note_last; note; note = note->prev) if (is_note_to(ch, note)) return note; return NULL; } Do_Fun(do_ncheck) { int i, count = 0, unread = 0; for (i = 0; i < MAX_BOARD; i++) { unread = unread_notes(ch, &boards[i]); if (unread != BOARD_NOACCESS && !ch->pcdata->unsubscribed[i]) count += unread; } if (count < 1) chprintln(ch, "You have no new notes on the board."); else chprintlnf(ch, "You have %s on the board. Type 'board'.", intstr(count, "note")); if (ch->pcdata->in_progress) chprintln(ch, "You have a note in progress. Type 'NOTE WRITE' to continue it."); } const char *format_recipient(CharData * ch, const char *names) { if (is_exact_name("clan", names) && is_clan(ch)) return str_rep(names, "clan", CharClan(ch)->name); return names; } static void begin_note(CharData * ch) { chprintlnf(ch, "Enter text. Type {W%cq{x or {W@{x on an empty line to end the note, or {W%ch{x for help.", StrEdKey(ch), StrEdKey(ch)); chprintln(ch, draw_line(ch, "{W={w=", 0)); } static Do_Fun(do_nwrite) { if (IsNPC(ch)) return; if (get_trust(ch) < ch->pcdata->board->write_level) { chprintln(ch, "You cannot post notes on this board."); return; } if (get_trust(ch) < ch->pcdata->board->write_level || ch->pcdata->board->force_type == DEF_READONLY) { chprintln(ch, "You cannot post notes on this board."); return; } if (ch->pcdata->in_progress && (!ch->pcdata->in_progress->text)) { chprintln(ch, "Note in progress cancelled because you did not manage to write any text " NEWLINE "before losing link."); free_note(ch->pcdata->in_progress); ch->pcdata->in_progress = NULL; } if (!ch->pcdata->in_progress) { ch->pcdata->in_progress = new_note(); replace_str(&ch->pcdata->in_progress->sender, ch->name); replace_str(&ch->pcdata->in_progress->date, str_time(-1, -1, NULL)); } act("{G$n starts writing a note.{x", ch, NULL, NULL, TO_ROOM); chprintlnf(ch, "You are now %s a new note on the {W%s{x board.", ch->pcdata->in_progress->text ? "continuing" : "posting", ch->pcdata->board->short_name); chprintlnf(ch, "{YFrom{x: %s", ch->name); if (!ch->pcdata->in_progress->text) { switch (ch->pcdata->board->force_type) { case DEF_NORMAL: chprintlnf(ch, "If you press Return, default recipient \"{W%s{x\" will be chosen.", ch->pcdata->board->names); break; case DEF_INCLUDE: chprintlnf(ch, "The recipient list MUST include \"{W%s{x\". If not, it will be added automatically.", ch->pcdata->board->names); break; case DEF_EXCLUDE: chprintlnf(ch, "The recipient of this note must NOT include: \"{W%s{x\".", ch->pcdata->board->names); chprintln(ch, "You may use a valid email address on this board to send this note as an email."); break; default: break; } chprintln(ch, "{YTo{x: "); ch->desc->connected = CON_NOTE_TO; } else { chprintlnf(ch, "{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE "{YSubject{x: %s", format_to_list(ch->pcdata->in_progress), str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL), ch->pcdata->in_progress->subject); chprintln(ch, "{GYour note so far:{x"); chprint(ch, ch->pcdata->in_progress->text); begin_note(ch); ch->desc->connected = CON_NOTE_TEXT; } } static Do_Fun(do_nforward) { NoteData *p; char buf[MSL]; char arg[MIL]; const char *names; if (IsNPC(ch)) return; if (get_trust(ch) < 2) { chprintln(ch, "You can't seem to write a note."); return; } argument = one_argument(argument, arg); if (NullStr(arg) || !is_number(arg) || atoi(arg) < 1) { chprintln(ch, "Forward which note?"); return; } p = find_note(ch, ch->pcdata->board, atoi(arg)); if (!p) { chprintln(ch, "No such note."); return; } if (NullStr(argument)) { chprintln(ch, "Forward the note to who?"); return; } if (get_trust(ch) < ch->pcdata->board->write_level) { chprintln(ch, "You cannot forward notes on this board."); return; } if (ch->pcdata->in_progress) { if (!ch->pcdata->in_progress->text) { chprintln(ch, "Note in progress cancelled because you did not manage to write any text " NEWLINE "before losing link."); free_note(ch->pcdata->in_progress); ch->pcdata->in_progress = NULL; } else { chprintlnf(ch, "You already have a note in progress. type 'note write' to continue it."); return; } } ch->pcdata->in_progress = new_note(); replace_str(&ch->pcdata->in_progress->sender, ch->name); replace_str(&ch->pcdata->in_progress->date, str_time(-1, -1, NULL)); act("{G$n starts writing a note.{x", ch, NULL, NULL, TO_ROOM); chprintlnf(ch, "{YFrom{x: %s", GetName(ch)); names = format_recipient(ch, ch->pcdata->board->names); switch (ch->pcdata->board->force_type) { case DEF_INCLUDE: if (!is_full_name(argument, names)) { sprintf(buf, "%s %s", names, argument); chprintlnf(ch, "You did not include the default recipient '%s' and was included automatically.", names); } else strcpy(buf, argument); break; default: case DEF_NORMAL: strcpy(buf, argument); break; case DEF_EXCLUDE: if (is_full_name(names, argument)) { chprintlnf(ch, "You are not allowed to send notes to %s on this board. Try again.", names); return; } if (strchr(argument, '@')) { const char *invalid = is_invalid_email(argument); if (invalid) { chprintln(ch, invalid); chprint(ch, "You must specify a valid email address. (ie. bob@aol.com). Try again."); return; } replace_str(&ch->pcdata->in_progress->email_addr, argument); if (!NullStr(ch->pcdata->email)) replace_str(&ch->pcdata->in_progress->reply_addr, ch->pcdata->email); } break; } replace_str(&ch->pcdata->in_progress->to_list, buf); replace_strf(&ch->pcdata->in_progress->subject, "FWD: %s", p->subject); ch->pcdata->in_progress->expire = current_time + ch->pcdata->board->purge_days * DAY; chprintlnf(ch, "{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE "{YSubject{x: %s", format_to_list(ch->pcdata->in_progress), str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL), ch->pcdata->in_progress->subject); replace_str(&ch->pcdata->in_progress->reply_text, replines(p->text)); sendpage(ch, ch->pcdata->in_progress->reply_text); begin_note(ch); ch->desc->connected = CON_NOTE_TEXT; } static Do_Fun(do_nreply) { NoteData *p; char buf[MSL]; char arg[MIL]; const char *names; if (IsNPC(ch)) return; if (get_trust(ch) < 2) { chprintln(ch, "You can't seem to write a note."); return; } argument = one_argument(argument, arg); if (NullStr(arg) || !is_number(arg) || atoi(arg) < 1) { chprintln(ch, "Reply to which note?"); return; } p = find_note(ch, ch->pcdata->board, atoi(arg)); if (!p) { chprintln(ch, "No such note."); return; } if (get_trust(ch) < ch->pcdata->board->write_level) { chprintln(ch, "You cannot reply to notes on this board."); return; } if (ch->pcdata->in_progress) { if (!ch->pcdata->in_progress->text) { chprintln(ch, "Note in progress cancelled because you did not manage to write any text " NEWLINE "before losing link."); free_note(ch->pcdata->in_progress); ch->pcdata->in_progress = NULL; } else { chprintln(ch, "You already have a note in progress. type 'note write' to continue it."); return; } } ch->pcdata->in_progress = new_note(); replace_str(&ch->pcdata->in_progress->sender, ch->name); replace_str(&ch->pcdata->in_progress->date, str_time(-1, GetTzone(ch), NULL)); act("{G$n starts writing a note.{x", ch, NULL, NULL, TO_ROOM); chprintlnf(ch, "{YFrom{x: %s", GetName(ch)); names = format_recipient(ch, ch->pcdata->board->names); switch (ch->pcdata->board->force_type) { case DEF_INCLUDE: if (!is_full_name(p->sender, names)) { sprintf(buf, "%s %s", p->sender, names); chprintlnf(ch, "You did not include the default recipient '%s' and was automatically added.", names); } break; default: case DEF_NORMAL: strcpy(buf, p->sender); break; case DEF_EXCLUDE: if (is_full_name(names, argument)) { chprintlnf(ch, "You are not allowed to send notes to %s on this board. Try again.", names); return; } strcpy(buf, p->sender); break; } replace_str(&ch->pcdata->in_progress->to_list, buf); replace_strf(&ch->pcdata->in_progress->subject, "RE: %s", p->subject); ch->pcdata->in_progress->expire = current_time + ch->pcdata->board->purge_days * DAY; chprintlnf(ch, "{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE "{YSubject{x: %s", format_to_list(ch->pcdata->in_progress), str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL), ch->pcdata->in_progress->subject); replace_str(&ch->pcdata->in_progress->reply_text, replines(p->text)); sendpage(ch, ch->pcdata->in_progress->reply_text); begin_note(ch); ch->desc->connected = CON_NOTE_TEXT; } static void next_board(CharData * ch) { int i = board_number(ch->pcdata->board) + 1; while ((i < MAX_BOARD) && (unread_notes(ch, &boards[i]) == BOARD_NOACCESS || ch->pcdata->unsubscribed[i])) i++; if (i == MAX_BOARD) { chprint(ch, "End of Boards. "); i = 0; } ch->pcdata->board = &boards[i]; return; } static Do_Fun(do_nread) { NoteData *p; int count = 0, number; time_t *last_note = &ch->pcdata->last_note[board_number(ch->pcdata->board)]; if (!str_cmp(argument, "again")) { count = 1; for (p = ch->pcdata->board->note_first; p; p = p->next, count++) if (p == ch->pcdata->last_read) break; if (!p || !is_note_to(ch, p)) { chprintln(ch, "No such note."); } else show_note_to_char(ch, p, count); } else if (is_number(argument)) { number = atoi(argument); for (p = ch->pcdata->board->note_first; p; p = p->next) if (++count == number) break; if (!p || !is_note_to(ch, p)) chprintln(ch, "No such note."); else { show_note_to_char(ch, p, count); *last_note = Max(*last_note, p->date_stamp); ch->pcdata->last_read = p; } } else { count = 1; for (p = ch->pcdata->board->note_first; p; p = p->next, count++) if ((p->date_stamp > *last_note) && is_note_to(ch, p)) { show_note_to_char(ch, p, count); *last_note = Max(*last_note, p->date_stamp); ch->pcdata->last_read = p; return; } chprintln(ch, "No new notes in this board."); next_board(ch); chprintlnf(ch, "Changed to next subscribed board, %s.", ch->pcdata->board->short_name); } } static Do_Fun(do_nremove) { NoteData *p; if (!str_cmp(argument, "all") && IsImmortal(ch)) { NoteData *p_next; for (p = ch->pcdata->board->note_first; p; p = p_next) { p_next = p->next; if (str_cmp(ch->name, p->sender) && (get_trust(ch) < MAX_LEVEL)) continue; unlink_note(ch->pcdata->board, p); free_note(p); } chprintln(ch, "ALL Notes removed!"); } else { if (!is_number(argument)) { chprintln(ch, "Remove which note?"); return; } p = find_note(ch, ch->pcdata->board, atoi(argument)); if (!p) { chprintln(ch, "No such note."); return; } if (str_cmp(ch->name, p->sender) && (get_trust(ch) < MAX_LEVEL)) { chprintln(ch, "You are not authorized to remove this note."); return; } unlink_note(ch->pcdata->board, p); free_note(p); chprintln(ch, "Note removed!"); } SetBit(ch->pcdata->board->flags, BOARD_CHANGED); } static Do_Fun(do_nlist) { int count = 0, show = 0, num = 0, has_shown = 0; time_t last_note; NoteData *p; Column Cd; Buffer *buf; if (is_number(argument)) { show = atoi(argument); for (p = ch->pcdata->board->note_first; p; p = p->next) if (is_note_to(ch, p)) count++; } buf = new_buf(); set_cols(&Cd, ch, 2, COLS_BUF, buf); bprintln(buf, "{WNotes on this board:{x"); cols_header(&Cd, "{rNum> Author Subject"); last_note = ch->pcdata->last_note[board_number(ch->pcdata->board)]; for (p = ch->pcdata->board->note_first; p; p = p->next) { num++; if (is_note_to(ch, p)) { has_shown++; if (!show || ((count - show) < has_shown)) { print_cols(&Cd, "{W%3d{x>{B%c{%c%-12.12s{Y %s{x ", num, last_note < p->date_stamp ? '*' : ' ', !NullStr(p->email_addr) ? 'M' : 'G', p->sender, p->subject); } } } cols_nl(&Cd); sendpage(ch, buf_string(buf)); free_buf(buf); } static Do_Fun(do_ncatchup) { NoteData *p; if (argument[0] == '\0') { for (p = ch->pcdata->board->note_first; p && p->next; p = p->next) ; if (!p) chprintln(ch, "Alas, there are no notes in that board."); else { ch->pcdata->last_note[board_number(ch->pcdata->board)] = p->date_stamp; chprintln(ch, "All messages skipped."); } } else { if (is_name("all", argument)) { int i, c = 0; BoardData *board; for (i = 0; i < MAX_BOARD; i++) { board = &boards[i]; if (unread_notes(ch, board) == BOARD_NOACCESS) continue; if (unread_notes(ch, board) == 0 || ch->pcdata->unsubscribed[i]) continue; c++; for (p = board->note_first; p && p->next; p = p->next) ; if (p) { ch->pcdata->last_note[board_number(board)] = p->date_stamp; chprintlnf(ch, "All notes in {W%s{x board skipped.", board->short_name); } } if (c > 0) chprintlnf(ch, "All notes in {W%s{x were skipped.", intstr(c, "minute")); else chprintln(ch, "There is no new notes to skip."); } else { chprintlnf(ch, "Only argument supported after '%s' is 'all'.", n_fun); } } } static Do_Fun(do_nclear) { if (ch->pcdata->in_progress) { free_note(ch->pcdata->in_progress); chprintln(ch, "Note cleared."); } else chprintln(ch, "You dont have any notes in progress."); } static Do_Fun(do_npurge) { int i; if (!IsImmortal(ch)) return; for (i = 0; i < MAX_BOARD; i++) { check_notes(&boards[i]); } chprintln(ch, "Old notes cleaned."); return; } static Do_Fun(do_nreset) { int pos; if (IsNPC(ch)) return; for (pos = 0; pos < MAX_BOARD; pos++) ch->pcdata->last_note[pos] = 0; chprintln(ch, "All notes marked as unread."); return; } static Do_Fun(do_nhelp) { cmd_syntax(ch, n_fun, "read [again] - read all notes 1 board at a time.", "write - write a note on your current board.", "list - list all notes on your current board.", "remove [number] - remove a note.", "print - print a note to a usable object.", "reply [number] - reply to a note.", "forward [number] [arg] - forward a note to a specific person.", "catchup - mark all notes on current board as read.", "reset - mark all notes on all boards as unread.", "check - count how many unread notes you have on all boards.", "clear - clear current note in progress.", NULL); if (IsImmortal(ch)) cmd_syntax(ch, n_fun, "purge - purges expired notes from current board.", NULL); } Do_Fun(do_note) { if (IsNPC(ch)) return; vinterpret(ch, n_fun, argument, "read", do_nread, "list", do_nlist, "write", do_nwrite, "to", do_nwrite, "remove", do_nremove, "reply", do_nreply, "forward", do_nforward, "purge", do_npurge, "reset", do_nreset, "clear", do_nclear, "check", do_ncheck, "catchup", do_ncatchup, "help", do_nhelp, NULL, do_nread); } Do_Fun(do_subscribe) { int i, count, number; if (IsNPC(ch)) return; if (NullStr(argument)) { count = 1; chprintln(ch, "{RNum Name Subscribed Description{x" NEWLINE "{R=== ============ ========== ==========={x"); for (i = 0; i < MAX_BOARD; i++) { if (unread_notes(ch, &boards[i]) == BOARD_NOACCESS) continue; chprintlnf(ch, "{W%2d{x> {g%12s{x [ %-8s{x] %s{x", count, boards[i].short_name, ch->pcdata->unsubscribed[i] ? "{rNO" : "{gYES", boards[i].long_name); count++; } return; } if (ch->pcdata->in_progress) { chprintln(ch, "Please finish your interrupted note first."); return; } if (is_number(argument)) { count = 0; number = atoi(argument); for (i = 0; i < MAX_BOARD; i++) if (unread_notes(ch, &boards[i]) != BOARD_NOACCESS) if (++count == number) break; if (i == board_lookup("Announce") || i == board_lookup("Personal")) { chprintln(ch, "You cannot un-subscribe from the Announce or Personal boards."); return; } if (count == number) { if (ch->pcdata->unsubscribed[i]) { ch->pcdata->unsubscribed[i] = false; chprintlnf(ch, "You are now subscribed to the {W%s{x board.", boards[i].short_name); } else { ch->pcdata->unsubscribed[i] = true; chprintlnf(ch, "You are no longer subscribed to the {W%s{x board.", boards[i].short_name); } } else chprintln(ch, "No such board."); return; } for (i = 0; i < MAX_BOARD; i++) if (!str_prefix(argument, boards[i].short_name)) break; if (i == MAX_BOARD) { chprintln(ch, "No such board."); return; } if (unread_notes(ch, &boards[i]) == BOARD_NOACCESS) { chprintln(ch, "No such board."); return; } if (i == board_lookup("Announce") || i == board_lookup("Personal")) { chprintln(ch, "You cannot un-subscribe from the Announce or Personal boards."); return; } if (ch->pcdata->unsubscribed[i]) { ch->pcdata->unsubscribed[i] = false; chprintlnf(ch, "You are now subscribed to the {W%s{x board.", boards[i].short_name); } else { ch->pcdata->unsubscribed[i] = true; chprintlnf(ch, "You are no longer subscribed to the {W%s{x board.", boards[i].short_name); } } void show_board(CharData * ch, bool fAll) { int unread, count, i, last; NoteData *p; BoardData *b; bool found = true; count = 0; if (IsImmortal(ch)) chprintln(ch, "{RNum Name Flags Unread Last Description{x" NEWLINE "{R=== ============ =========== ====== ==== ==========={x"); else chprintln(ch, "{RNum Name Unread Last Description{x" NEWLINE "{R=== ============ ====== ==== ==========={x"); for (i = 0; i < MAX_BOARD; i++) { unread = unread_notes(ch, &boards[i]); if (unread == BOARD_NOACCESS) continue; count++; if (ch->pcdata->unsubscribed[i]) continue; if (unread == 0 && fAll == true) continue; last = 0; b = &boards[i]; for (p = b->note_first; p; p = p->next) if (is_note_to(ch, p)) last++; found = false; chprintlnf(ch, "{W%2d{x> {G%12s{x [{%c%4d{x] {G%4d {Y%s{x", count, boards[i].short_name, unread == 0 ? 'r' : 'R', unread, last, boards[i].long_name); } if (!found) chprintln(ch, "You have no unread notes on any subscribed board." NEWLINE "(Use 'board all' to see a list of boards.)"); chprintf(ch, NEWLINE "Your current board is {W%s{x", ch->pcdata->board->short_name); if ((p = last_note(ch, ch->pcdata->board)) != NULL) chprintlnf(ch, ". Last message was from {W%s{x.", p->sender); else chprintln(ch, "."); if (ch->pcdata->board->read_level > get_trust(ch)) chprintln(ch, "You cannot read nor write notes on this board."); else if (ch->pcdata->board->write_level > get_trust(ch)) chprintln(ch, "You can only read notes from this board."); else chprintln(ch, "You can both read and write on this board."); chprintln(ch, "Use 'board all' to see all subscribed boards."); chprintln(ch, "Use 'subscribe' to see what boards you are subscribed to."); return; } Do_Fun(do_board) { int i, number, count; NoteData *p; if (IsNPC(ch)) return; if (NullStr(argument)) { show_board(ch, true); return; } else if (!str_cmp(argument, "all")) { show_board(ch, false); return; } else if (IsImmortal(ch) && !str_cmp(argument, "save")) { rw_note_data(act_write); chprintln(ch, "Notes saved."); return; } if (ch->pcdata->in_progress) { chprintln(ch, "Please finish your interrupted note first."); return; } if (is_number(argument)) { count = 0; number = atoi(argument); for (i = 0; i < MAX_BOARD; i++) if (unread_notes(ch, &boards[i]) != BOARD_NOACCESS) if (++count == number) break; if (count == number) { ch->pcdata->board = &boards[i]; chprintlnf(ch, "Current board changed to {W%s{x. %s.", boards[i].short_name, (get_trust(ch) < boards[i].write_level) ? "You can only read here" : "You can both read and write here"); if ((p = last_note(ch, &boards[i])) != NULL) chprintlnf(ch, "Last message was from {W%s{x concerning {W%s{x.", p->sender, p->subject); } else chprintln(ch, "No such board."); return; } for (i = 0; i < MAX_BOARD; i++) if (!str_prefix(argument, boards[i].short_name)) break; if (i == MAX_BOARD) { chprintln(ch, "No such board."); return; } if (unread_notes(ch, &boards[i]) == BOARD_NOACCESS) { chprintln(ch, "No such board."); return; } ch->pcdata->board = &boards[i]; chprintlnf(ch, "Current board changed to {W%s{x. %s.", boards[i].short_name, (get_trust(ch) < boards[i].write_level) ? "You can only read here" : "You can both read and write here"); if ((p = last_note(ch, &boards[i])) != NULL) chprintlnf(ch, "Last message was from {W%s{x concerning {W%s{x.", p->sender, p->subject); } void personal_message(const char *sender, const char *to, const char *subject, const int expire_days, const char *text) { make_note("Personal", sender, to, subject, expire_days, text, NULL, NULL); } void email_note(const char *sender, const char *email, const char *subject, const int expire_days, const char *text) { make_note("Personal", sender, email, subject, expire_days, text, email, NULL); } void make_note(const char *board_name, const char *sender, const char *to, const char *subject, const int expire_days, const char *text, const char *email, const char *reply) { int board_index = board_lookup(board_name); BoardData *board; NoteData *note; if (board_index == BOARD_NOTFOUND) { bug("make_note: board not found"); return; } if (strlen(text) > MAX_NOTE_TEXT) { bugf("make_note: text too long (%d bytes)", strlen(text)); return; } board = &boards[board_index]; note = new_note(); note->sender = str_dup(sender); note->to_list = str_dup(to); note->subject = str_dup(subject); note->expire = current_time + expire_days * 60 * 60 * 24; note->text = str_dup(text); note->email_addr = str_dup(email); note->reply_addr = str_dup(reply); note->date = str_dup(str_time(-1, -1, NULL)); finish_note(board, note); } void append_to_note(CharData * ch, const char *board_name, const char *sender, const char *to, const char *subject, const int expire_days, const char *text) { int board_index = board_lookup(board_name); BoardData *board; if (board_index == BOARD_NOTFOUND) { bug("board not found"); return; } if (strlen(text) > (size_t) MAX_NOTE_TEXT) { bugf("text too long (%d bytes)", strlen(text)); return; } board = &boards[board_index]; ch->pcdata->in_progress = new_note(); replace_str(&ch->pcdata->in_progress->sender, sender); replace_str(&ch->pcdata->in_progress->to_list, to); replace_str(&ch->pcdata->in_progress->subject, subject); if (!is_invalid_email(to)) { replace_str(&ch->pcdata->in_progress->email_addr, to); if (!NullStr(ch->pcdata->email)) replace_str(&ch->pcdata->in_progress->reply_addr, ch->pcdata->email); } ch->pcdata->in_progress->expire = current_time + (time_t) (expire_days * DAY); replace_str(&ch->pcdata->in_progress->date, str_time(-1, -1, NULL)); replace_str(&ch->pcdata->in_progress->text, text); chprintlnf(ch, "{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE "{YSubject{x: %s" NEWLINE "%s", format_to_list(ch->pcdata->in_progress), (ch->pcdata->board->purge_days == -1) ? "Never" : str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL), ch->pcdata->in_progress->subject, ch->pcdata->in_progress->text); begin_note(ch); ch->desc->connected = CON_NOTE_TEXT; } Nanny_Fun(HANDLE_CON_NOTE_TO) { CharData *ch = d->character; const char *names; if (!ch->pcdata->in_progress) { d->connected = CON_PLAYING; bugf("nanny: In CON_NOTE_TO, but no note in progress"); return; } names = format_recipient(ch, ch->pcdata->board->names); switch (ch->pcdata->board->force_type) { case DEF_NORMAL: if (NullStr(argument)) { replace_str(&ch->pcdata->in_progress->to_list, names); d_printlnf(d, "Assumed default recipient: {W%s{x", names); } else replace_str(&ch->pcdata->in_progress->to_list, argument); break; default: break; case DEF_INCLUDE: if (!is_full_name(names, argument)) { replace_strf(&ch->pcdata->in_progress->to_list, "%s %s", argument, names); d_printlnf(d, NEWLINE "You did not specify %s as recipient, so it was automatically added." NEWLINE "{YNew To{x : %s", names, ch->pcdata->in_progress->to_list); } else replace_str(&ch->pcdata->in_progress->to_list, argument); break; case DEF_EXCLUDE: if (NullStr(argument)) { d_print(d, "You must specify a recipient." NEWLINE "{YTo{x: "); return; } if (is_full_name(names, argument)) { d_printf(d, "You are not allowed to send notes to %s on this board. Try again." NEWLINE "{YTo{x: ", names); return; } if (strchr(argument, '@')) { const char *invalid = is_invalid_email(argument); if (invalid) { d_println(d, invalid); d_print(d, "You must specify a valid email address. (ie. bob@aol.com)." NEWLINE "{YTo{x: "); return; } replace_str(&ch->pcdata->in_progress->email_addr, argument); } replace_str(&ch->pcdata->in_progress->to_list, argument); break; } d_print(d, NEWLINE "{YSubject{x: "); d->connected = CON_NOTE_SUBJECT; } Nanny_Fun(HANDLE_CON_NOTE_SUBJECT) { CharData *ch = d->character; if (!ch->pcdata->in_progress) { d->connected = CON_PLAYING; bugf("nanny: In CON_NOTE_SUBJECT, but no note in progress"); return; } if (NullStr(argument)) { d_println(d, "Please find a meaningful subject!"); d_print(d, "{YSubject{x: "); } else if (strlen(argument) > 60) { d_println(d, "No, no. This is just the Subject. You're not writing the note yet. Twit."); } else { replace_str(&ch->pcdata->in_progress->subject, argument); if (IsImmortal(ch)) { d_printf(d, NEWLINE "How many days do you want this note to expire in?" NEWLINE "Press Enter for default value for this board, {W%d{x days." NEWLINE "{YExpire{x: ", ch->pcdata->board->purge_days); d->connected = CON_NOTE_EXPIRE; } else { ch->pcdata->in_progress->expire = current_time + ch->pcdata->board->purge_days * DAY; d_printlnf(d, "This note will expire %s", str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL)); d_printlnf(d, NEWLINE "Enter text. Type {W%cq{x or {W@{x on an empty line to end the note, or {W%ch{x for help.", StrEdKey(ch), StrEdKey(ch)); d_println(d, draw_line(ch, "{W={w=", 0)); d->connected = CON_NOTE_TEXT; } } } Nanny_Fun(HANDLE_CON_NOTE_EXPIRE) { CharData *ch = d->character; time_t expire; int days; if (!ch->pcdata->in_progress) { d->connected = CON_PLAYING; bugf("nanny: In CON_NOTE_EXPIRE, but no note in progress"); return; } if (NullStr(argument)) days = ch->pcdata->board->purge_days; else if (!is_number(argument)) { d_println(d, "Write the number of days!"); d_print(d, "{YExpire{x: "); return; } else { days = atoi(argument); if (days <= 0) { d_println(d, "This is a positive Mud. Use positive numbers only! :)"); d_print(d, "{YExpire{x: "); return; } } expire = current_time + (days * DAY); ch->pcdata->in_progress->expire = expire; d_printlnf(d, NEWLINE "Enter text. Type {W%cq{x or {W@{x on an empty line to end the note, or {W%ch{x for help.", StrEdKey(ch), StrEdKey(ch)); d_println(d, draw_line(ch, "{W={w=", 0)); d->connected = CON_NOTE_TEXT; } Nanny_Fun(HANDLE_CON_NOTE_TEXT) { strshow_t action; CharData *ch = d->character; char buf[MAX_STRING_LENGTH * 5]; if (!ch->pcdata->in_progress) { d->connected = CON_PLAYING; bugf("nanny: In CON_NOTE_TEXT, but no note in progress"); return; } action = parse_string_command(&ch->pcdata->in_progress->text, argument, ch); switch (action) { case STRING_END: d_printf(d, NEWLINE "%s", szFinishPrompt); d->connected = CON_NOTE_FINISH; return; case STRING_FOUND: return; default: case STRING_NONE: if (ch->pcdata->in_progress->text) { strcpy(buf, ch->pcdata->in_progress->text); replace_str(&ch->pcdata->in_progress->text, ""); } else strcpy(buf, ""); if ((strlen(argument) + strlen(buf)) > MAX_NOTE_TEXT) { d_println(d, "Note too long, bailing out!"); free_note(ch->pcdata->in_progress); ch->pcdata->in_progress = NULL; d->connected = CON_PLAYING; return; } strcat(buf, argument); strcat(buf, NEWLINE); replace_str(&ch->pcdata->in_progress->text, buf); return; } } Nanny_Fun(HANDLE_CON_NOTE_FINISH) { CharData *ch = d->character; if (!ch->pcdata->in_progress) { d->connected = CON_PLAYING; bugf("nanny: In CON_NOTE_FINISH, but no note in progress"); return; } switch (tolower(argument[0])) { case 'c': d_println(d, "Continuing note..."); d->connected = CON_NOTE_TEXT; break; case 'v': if (ch->pcdata->in_progress->text) { d_println(d, "{gText of your note so far:{x"); d_print(d, ch->pcdata->in_progress->text); } else d_println(d, "You haven't written a thing!" NEWLINE); d_println(d, szFinishPrompt); break; case 'p': announce(ch, INFO_NOTE, "New note on %s board from $n. Subj: %s", ch->pcdata->board->short_name, ch->pcdata->in_progress->subject); finish_note(ch->pcdata->board, ch->pcdata->in_progress); d_print(d, "Note posted"); if (!NullStr(ch->pcdata->in_progress->email_addr)) d_printlnf(d, " and sent to %s.", ch->pcdata->in_progress->email_addr); else d_println(d, "."); mud_info.stats.notes++; d->connected = CON_PLAYING; ch->pcdata->in_progress = NULL; act("{G$n finishes $s note.{x", ch, NULL, NULL, TO_ROOM); break; case 'f': d_println(d, "Note cancelled!"); free_note(ch->pcdata->in_progress); ch->pcdata->in_progress = NULL; d->connected = CON_PLAYING; break; default: d_println(d, "Huh? Valid answers are:" NEWLINE); d_println(d, szFinishPrompt); } }