1stMud4.5.3/
1stMud4.5.3/backup/
1stMud4.5.3/bin/
1stMud4.5.3/bin/extras/
1stMud4.5.3/data/i3/
1stMud4.5.3/doc/1stMud/
1stMud4.5.3/doc/Diku/
1stMud4.5.3/doc/MPDocs/
1stMud4.5.3/doc/Rom/
1stMud4.5.3/notes/
/**************************************************************************
*  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(&note->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);
	}
}