mirror of
https://github.com/brazilofmux/tinymux
synced 2026-08-13 00:23:11 -04:00
Found verifying #783: the SQLite loaders are gated on metadata keys -- sqlite_load_comsys() on has_comsys, sqlite_load_mail() on mail_db_top -- but ONLY the DbConvert -C/-m import path ever set them. Channel and mail mutations write through to the tables row-by-row, so a channel created with @ccreate (or mail sent in-game) on a game that never went through a -C/-m import landed in the tables and was then ignored forever: the next warm boot found no gate, fell back to the legacy comsys.db/mail.db flatfiles, and the runtime came up without them while the orphaned rows lingered. The write-throughs now maintain the gates: sqlite_wt_channel() sets has_comsys, and sqlite_wt_mail_body() keeps mail_db_top current (it sizes the loader's body array). One cheap metadata upsert alongside mutations that are already rare. Verified: a channel @ccreate'd and mail sent in one muxscript session now warm-load in a fresh process -- db_unload -C/-m exports both, where before it exported nothing ("has no comsys/mail") despite the rows sitting in the tables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6126 lines
156 KiB
C++
6126 lines
156 KiB
C++
/*! \file mail.cpp
|
|
* \brief In-game \@mail system.
|
|
*
|
|
* This code was taken from Kalkin's DarkZone code, which was
|
|
* originally taken from PennMUSH 1.50 p10, and has been heavily modified
|
|
* since being included in MUX.
|
|
*/
|
|
|
|
#include "copyright.h"
|
|
#include "autoconf.h"
|
|
#include "config.h"
|
|
#include "externs.h"
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
#include "color_ops.h"
|
|
}
|
|
|
|
const UTF8 *DASH_LINE =
|
|
T("\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93");
|
|
|
|
const char *MAIL_LINE =
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93"
|
|
" MAIL: %s "
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93";
|
|
|
|
const char *FOLDER_LINE =
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
" MAIL: Folder %d "
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93"
|
|
"\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93\xE2\x80\x93";
|
|
|
|
#define SIZEOF_MALIAS 13
|
|
#define WIDTHOF_MALIASDESC 40
|
|
#define SIZEOF_MALIASDESC (WIDTHOF_MALIASDESC*2)
|
|
|
|
typedef struct malias
|
|
{
|
|
int owner;
|
|
UTF8 *name;
|
|
UTF8 *desc;
|
|
size_t desc_width; // The visual width of the Mail Alias Description.
|
|
std::vector<dbref> list;
|
|
} malias_t;
|
|
|
|
static int ma_size = 0;
|
|
static int ma_top = 0;
|
|
|
|
static malias_t **malias = nullptr;
|
|
static MAILBODY *mail_list = nullptr;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SQLite write-through helpers for mail mutations.
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#include "sqlite_backend.h"
|
|
|
|
#define SQLITE_MAIL_WRITABLE() (!mudstate.bSQLiteLoading)
|
|
|
|
static void sqlite_wt_insert_mail(struct mail *mp)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
mp->sqlite_id = sqldb.InsertMailHeaderReturningId(
|
|
mp->to, mp->from, mp->number,
|
|
mp->tolist, mp->time, mp->subject, mp->read);
|
|
if (mp->sqlite_id < 0)
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_insert_mail failed for to=#%d from=#%d body=%d" ENDLINE),
|
|
mp->to, mp->from, mp->number);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_update_mail_flags(struct mail *mp)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE() || mp->sqlite_id < 0) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.UpdateMailReadFlags(mp->sqlite_id, mp->read))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_update_mail_flags failed for rowid=%lld" ENDLINE),
|
|
static_cast<long long>(mp->sqlite_id));
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_delete_mail(struct mail *mp)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE() || mp->sqlite_id < 0) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.DeleteMailHeader(mp->sqlite_id))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_delete_mail failed for rowid=%lld" ENDLINE),
|
|
static_cast<long long>(mp->sqlite_id));
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_delete_all_mail(int to_player)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.DeleteAllMailHeaders(to_player))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_delete_all_mail failed for to=#%d" ENDLINE),
|
|
to_player);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_mail_body(int number, const UTF8 *message)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.SyncMailBody(number, message))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_mail_body failed for body=%d" ENDLINE), number);
|
|
}
|
|
|
|
// Keep the loader's gate current: sqlite_load_mail() only reads the
|
|
// mail tables when the mail_db_top meta exists (it sizes the body
|
|
// array), but until now only a DbConvert -m import set it -- mail
|
|
// sent in-game wrote through to the tables and was then ignored on
|
|
// the next boot (fallback to the legacy mail.db flatfile). (#783)
|
|
//
|
|
if (!sqldb.PutMeta("mail_db_top", mudstate.mail_db_top))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_mail_body: mail_db_top meta update failed" ENDLINE));
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_delete_mail_body(int number)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.DeleteMailBody(number))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_delete_mail_body failed for body=%d" ENDLINE), number);
|
|
}
|
|
}
|
|
|
|
static void sqlite_wt_sync_all_aliases(void)
|
|
{
|
|
if (!SQLITE_MAIL_WRITABLE()) return;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.ClearMailAliases())
|
|
{
|
|
Log.WriteString(T("mail sqlite_wt_sync_all_aliases failed to clear aliases." ENDLINE));
|
|
return;
|
|
}
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
malias_t *m = malias[i];
|
|
|
|
LBuf members_buf = LBuf_Src("wt_sync_alias");
|
|
UTF8 *bp = members_buf;
|
|
for (size_t j = 0; j < m->list.size(); j++)
|
|
{
|
|
if (j > 0)
|
|
{
|
|
safe_chr(' ', members_buf, &bp);
|
|
}
|
|
safe_str(tprintf(T("%d"), m->list[j]), members_buf, &bp);
|
|
}
|
|
*bp = '\0';
|
|
|
|
if (!sqldb.SyncMailAlias(m->owner, m->name, m->desc,
|
|
static_cast<int>(m->desc_width), members_buf))
|
|
{
|
|
Log.tinyprintf(T("mail sqlite_wt_sync_all_aliases failed for owner=#%d alias=%s" ENDLINE),
|
|
m->owner, m->name);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Handling functions for the database of mail messages.
|
|
//
|
|
|
|
// mail_db_grow - We keep a database of mail text, so if we send a
|
|
// message to more than one player, we won't have to duplicate the
|
|
// text.
|
|
//
|
|
#define MAIL_FUDGE 1
|
|
static void mail_db_grow(int newtop)
|
|
{
|
|
if (newtop <= mudstate.mail_db_top)
|
|
{
|
|
return;
|
|
}
|
|
if (mudstate.mail_db_size <= newtop)
|
|
{
|
|
// We need to make the mail bag bigger.
|
|
//
|
|
int newsize = mudstate.mail_db_size + 100;
|
|
if (newtop > newsize)
|
|
{
|
|
newsize = newtop;
|
|
}
|
|
|
|
MAILBODY *newdb = static_cast<MAILBODY *>(MEMALLOC((newsize + MAIL_FUDGE) * sizeof(MAILBODY)));
|
|
if (nullptr == newdb)
|
|
{
|
|
STARTLOG(LOG_PROBLEMS, "MAIL", "MEM");
|
|
log_printf(T("mail_db_grow: out of memory growing mail body array to %d."), newsize);
|
|
ENDLOG;
|
|
return;
|
|
}
|
|
if (mail_list)
|
|
{
|
|
mail_list -= MAIL_FUDGE;
|
|
memcpy( newdb,
|
|
mail_list,
|
|
(mudstate.mail_db_top + MAIL_FUDGE) * sizeof(MAILBODY));
|
|
MEMFREE(mail_list);
|
|
mail_list = nullptr;
|
|
}
|
|
mail_list = newdb + MAIL_FUDGE;
|
|
newdb = nullptr;
|
|
mudstate.mail_db_size = newsize;
|
|
}
|
|
|
|
// Initialize new parts of the mail bag.
|
|
//
|
|
for (int i = mudstate.mail_db_top; i < newtop; i++)
|
|
{
|
|
mail_list[i].m_nRefs = 0;
|
|
mail_list[i].m_nMessage = 0;
|
|
mail_list[i].m_pMessage = nullptr;
|
|
}
|
|
mudstate.mail_db_top = newtop;
|
|
}
|
|
|
|
// MessageReferenceInc - Increments the reference count for any
|
|
// particular message.
|
|
//
|
|
static inline void MessageReferenceInc(int number)
|
|
{
|
|
mail_list[number].m_nRefs++;
|
|
}
|
|
|
|
// MessageReferenceCheck - Checks whether the reference count for
|
|
// any particular message indicates that the message body should be
|
|
// freed. Also checks that if a message pointer is null, that the
|
|
// reference count is zero.
|
|
//
|
|
static void MessageReferenceCheck(int number)
|
|
{
|
|
MAILBODY &m = mail_list[number];
|
|
if (m.m_nRefs <= 0)
|
|
{
|
|
if (m.m_pMessage)
|
|
{
|
|
MEMFREE(m.m_pMessage);
|
|
m.m_pMessage = nullptr;
|
|
m.m_nMessage = 0;
|
|
}
|
|
}
|
|
|
|
if (m.m_pMessage == nullptr)
|
|
{
|
|
m.m_nRefs = 0;
|
|
m.m_nMessage = 0;
|
|
}
|
|
}
|
|
|
|
// MessageReferenceDec - Decrements the reference count for a message, and
|
|
// will also delete the message if the counter reaches 0.
|
|
//
|
|
static void MessageReferenceDec(int number)
|
|
{
|
|
mail_list[number].m_nRefs--;
|
|
if (mail_list[number].m_nRefs <= 0)
|
|
{
|
|
sqlite_wt_delete_mail_body(number);
|
|
}
|
|
MessageReferenceCheck(number);
|
|
}
|
|
|
|
// MessageFetch - returns the text for a particular message number. This
|
|
// text should not be modified.
|
|
//
|
|
const UTF8 *MessageFetch(int number)
|
|
{
|
|
MessageReferenceCheck(number);
|
|
if (mail_list[number].m_pMessage)
|
|
{
|
|
return mail_list[number].m_pMessage;
|
|
}
|
|
else
|
|
{
|
|
return T("MAIL: This mail message does not exist in the database. Please alert your admin.");
|
|
}
|
|
}
|
|
|
|
size_t MessageFetchSize(int number)
|
|
{
|
|
MessageReferenceCheck(number);
|
|
if (mail_list[number].m_pMessage)
|
|
{
|
|
return mail_list[number].m_nMessage;
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// This function returns a reference to the message and the the
|
|
// reference count is increased to reflect that.
|
|
//
|
|
static int MessageAdd(UTF8 *pMessage)
|
|
{
|
|
int i;
|
|
MAILBODY *pm;
|
|
bool bFound = false;
|
|
for (i = 0; i < mudstate.mail_db_top; i++)
|
|
{
|
|
pm = &mail_list[i];
|
|
if (nullptr == pm->m_pMessage)
|
|
{
|
|
pm->m_nRefs = 0;
|
|
bFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!bFound)
|
|
{
|
|
mail_db_grow(i + 1);
|
|
}
|
|
|
|
pm = &mail_list[i];
|
|
pm->m_nMessage = strlen(reinterpret_cast<char *>(pMessage));
|
|
pm->m_pMessage = StringCloneLen(pMessage, pm->m_nMessage);
|
|
MessageReferenceInc(i);
|
|
sqlite_wt_mail_body(i, pMessage);
|
|
return i;
|
|
}
|
|
|
|
// add_mail_message - adds a new text message to the mail database, and returns
|
|
// a unique number for that message.
|
|
//
|
|
// IF return value is !NOTHING, you have a reference to the message,
|
|
// and the reference count reflects that.
|
|
//
|
|
static int add_mail_message(dbref player, UTF8 *message)
|
|
{
|
|
if (!mux_stricmp(message, T("clear")))
|
|
{
|
|
// Using \230 instead \x98 because \x98c is making gcc choke.
|
|
//
|
|
raw_notify(player, T("MAIL: You probably did not intend to send a @mail saying \xE2\x80\230clear\xE2\x80\x99."));
|
|
return NOTHING;
|
|
}
|
|
|
|
// Evaluate signature.
|
|
//
|
|
int aflags;
|
|
dbref aowner;
|
|
UTF8 *bp = alloc_lbuf("add_mail_message");
|
|
UTF8 *atrstr = atr_get("add_mail_message.216", player, A_SIGNATURE, &aowner, &aflags);
|
|
UTF8 *execstr = bp;
|
|
mux_exec(atrstr, LBUF_SIZE-1, execstr, &bp, player, player, player,
|
|
AttrTrace(aflags, EV_STRIP_CURLY|EV_FCHECK|EV_EVAL),
|
|
nullptr, 0);
|
|
*bp = '\0';
|
|
|
|
// Save message body and return a reference to it.
|
|
//
|
|
int number = MessageAdd(tprintf(T("%s %s"), message, execstr));
|
|
free_lbuf(atrstr);
|
|
free_lbuf(execstr);
|
|
return number;
|
|
}
|
|
|
|
// This function is -only- used from reading from the disk, and so
|
|
// it does -not- manage the reference counts.
|
|
//
|
|
static bool MessageAddWithNumber(int i, UTF8 *pMessage)
|
|
{
|
|
mail_db_grow(i+1);
|
|
|
|
MAILBODY *pm = &mail_list[i];
|
|
pm->m_nMessage = strlen(reinterpret_cast<char *>(pMessage));
|
|
pm->m_pMessage = StringCloneLen(pMessage, pm->m_nMessage);
|
|
return true;
|
|
}
|
|
|
|
// new_mail_message - used for reading messages in from disk which
|
|
// already have a number assigned to them.
|
|
//
|
|
// This function is -only- used from reading from the disk, and so
|
|
// it does -not- manage the reference counts.
|
|
//
|
|
static void new_mail_message(UTF8 *message, int number)
|
|
{
|
|
bool bTruncated = false;
|
|
if (strlen(reinterpret_cast<char *>(message)) > LBUF_SIZE-1)
|
|
{
|
|
bTruncated = true;
|
|
message[LBUF_SIZE-1] = '\0';
|
|
}
|
|
MessageAddWithNumber(number, message);
|
|
if (bTruncated)
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "MAIL");
|
|
log_printf(T("new_mail_message: Mail message %d truncated."), number);
|
|
ENDLOG;
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* User mail functions (these are called from game.c)
|
|
*
|
|
* do_mail - cases
|
|
* do_mail_read - read messages
|
|
* do_mail_list - list messages
|
|
* do_mail_flags - tagging, untagging, clearing, unclearing of messages
|
|
* do_mail_file - files messages into a new folder
|
|
* do_mail_fwd - forward messages to another player(s)
|
|
* do_mail_reply - reply to a message
|
|
* do_mail_count - count messages
|
|
* do_mail_purge - purge cleared messages
|
|
* do_mail_change_folder - change current folder
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
static void set_player_folder(dbref player, int fnum)
|
|
{
|
|
// Set a player's folder to fnum.
|
|
//
|
|
UTF8 *tbuf1 = alloc_sbuf("set_player_folder");
|
|
mux_ltoa(fnum, tbuf1);
|
|
ATTR *a = atr_num(A_MAILCURF);
|
|
if (a)
|
|
{
|
|
atr_add(player, A_MAILCURF, tbuf1, GOD, a->flags);
|
|
}
|
|
else
|
|
{
|
|
// Shouldn't happen, but...
|
|
//
|
|
atr_add(player, A_MAILCURF, tbuf1, GOD, AF_ODARK | AF_WIZARD | AF_NOPROG | AF_LOCK);
|
|
}
|
|
free_sbuf(tbuf1);
|
|
}
|
|
|
|
static void add_folder_name(dbref player, int fld, UTF8 *name)
|
|
{
|
|
// Fetch current list of folders
|
|
//
|
|
int aflags;
|
|
size_t nFolders;
|
|
dbref aowner;
|
|
UTF8 *aFolders = alloc_lbuf("add_folder_name.str");
|
|
atr_get_str_LEN(aFolders, player, A_MAILFOLDERS, &aowner, &aflags,
|
|
&nFolders);
|
|
|
|
// Build new record ("%d:%s:%d", fld, uppercase(name), fld) upper-casing
|
|
// the provided folder name.
|
|
//
|
|
UTF8 *aNew = alloc_lbuf("add_folder_name.new");
|
|
snprintf(reinterpret_cast<char *>(aNew), LBUF_SIZE,
|
|
"%d:%s:%d", fld, reinterpret_cast<const char *>(name), fld);
|
|
size_t nNew = strlen(reinterpret_cast<char *>(aNew));
|
|
{
|
|
LBuf tmp = LBuf_Src("add_folder_name");
|
|
nNew = co_toupper(tmp, aNew, nNew);
|
|
memcpy(aNew, tmp, nNew + 1);
|
|
}
|
|
|
|
UTF8 *p, *q;
|
|
if (0 != nFolders)
|
|
{
|
|
// Build pattern ("%d:", fld)
|
|
//
|
|
UTF8 *aPattern = alloc_lbuf("add_folder_name.pat");
|
|
q = aPattern;
|
|
q += mux_ltoa(fld, q);
|
|
safe_chr(':', aPattern, &q);
|
|
*q = '\0';
|
|
size_t nPattern = q - aPattern;
|
|
|
|
BMH_State bmhs;
|
|
BMH_Prepare(&bmhs, nPattern, aPattern);
|
|
for (;;)
|
|
{
|
|
size_t i;
|
|
if (!BMH_Execute(&bmhs, &i, nPattern, aPattern, nFolders, aFolders))
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Remove old record.
|
|
//
|
|
q = aFolders + i;
|
|
p = q + nPattern;
|
|
|
|
// Eat leading spaces.
|
|
//
|
|
while ( aFolders < q
|
|
&& mux_isspace(q[-1]))
|
|
{
|
|
q--;
|
|
}
|
|
|
|
// Skip past old record and trailing spaces.
|
|
//
|
|
while ( *p
|
|
&& *p != ':')
|
|
{
|
|
p++;
|
|
}
|
|
while ( *p
|
|
&& !mux_isspace(*p))
|
|
{
|
|
p++;
|
|
}
|
|
while (mux_isspace(*p))
|
|
{
|
|
p++;
|
|
}
|
|
|
|
if (q != aFolders)
|
|
{
|
|
*q++ = ' ';
|
|
}
|
|
while (*p)
|
|
{
|
|
safe_chr(*p, aFolders, &q);
|
|
p++;
|
|
}
|
|
*q = '\0';
|
|
nFolders = q - aFolders;
|
|
}
|
|
free_lbuf(aPattern);
|
|
}
|
|
|
|
if (nFolders + 1 + nNew < LBUF_SIZE)
|
|
{
|
|
// It will fit. Append new record.
|
|
//
|
|
q = aFolders + nFolders;
|
|
if (nFolders)
|
|
{
|
|
*q++ = ' ';
|
|
}
|
|
memcpy(q, aNew, nNew);
|
|
q += nNew;
|
|
*q = '\0';
|
|
|
|
atr_add(player, A_MAILFOLDERS, aFolders, player,
|
|
AF_MDARK | AF_WIZARD | AF_NOPROG | AF_LOCK);
|
|
}
|
|
free_lbuf(aFolders);
|
|
free_lbuf(aNew);
|
|
}
|
|
|
|
static const UTF8 *get_folder_name(dbref player, int fld)
|
|
{
|
|
// Get the name of the folder, or return "unnamed".
|
|
//
|
|
int aflags;
|
|
size_t nFolders;
|
|
dbref aowner;
|
|
thread_local UTF8 aFolders[LBUF_SIZE];
|
|
atr_get_str_LEN(aFolders, player, A_MAILFOLDERS, &aowner, &aflags,
|
|
&nFolders);
|
|
if (nFolders != 0)
|
|
{
|
|
UTF8 *aPattern = alloc_lbuf("get_folder_name");
|
|
UTF8 *p = aPattern;
|
|
p += mux_ltoa(fld, p);
|
|
*p++ = ':';
|
|
*p = '\0';
|
|
size_t nPattern = p - aPattern;
|
|
|
|
size_t i;
|
|
bool bSucceeded = BMH_StringSearch(&i, nPattern, aPattern, nFolders, aFolders);
|
|
free_lbuf(aPattern);
|
|
|
|
if (bSucceeded)
|
|
{
|
|
UTF8 *pFolder = aFolders + i + nPattern;
|
|
UTF8 *q = pFolder;
|
|
while ( *q
|
|
&& *q != ':')
|
|
{
|
|
q++;
|
|
}
|
|
*q = '\0';
|
|
return pFolder;
|
|
}
|
|
}
|
|
return T("unnamed");
|
|
}
|
|
|
|
static int get_folder_number(dbref player, UTF8 *name)
|
|
{
|
|
// Look up a folder name and return the corresponding folder number.
|
|
//
|
|
int aflags;
|
|
size_t nFolders;
|
|
dbref aowner;
|
|
UTF8 *aFolders = alloc_lbuf("get_folder_num_str");
|
|
atr_get_str_LEN(aFolders, player, A_MAILFOLDERS, &aowner, &aflags,
|
|
&nFolders);
|
|
if (nFolders != 0)
|
|
{
|
|
// Convert the folder name provided into upper-case characters.
|
|
//
|
|
UTF8 *aPattern = alloc_lbuf("add_folder_num_pat");
|
|
snprintf(reinterpret_cast<char *>(aPattern), LBUF_SIZE,
|
|
":%s:", reinterpret_cast<const char *>(name));
|
|
size_t nPattern = strlen(reinterpret_cast<char *>(aPattern));
|
|
{
|
|
LBuf tmp = LBuf_Src("get_folder_number");
|
|
nPattern = co_toupper(tmp, aPattern, nPattern);
|
|
memcpy(aPattern, tmp, nPattern + 1);
|
|
}
|
|
|
|
size_t i;
|
|
bool bSucceeded = BMH_StringSearch(&i, nPattern, aPattern, nFolders, aFolders);
|
|
free_lbuf(aPattern);
|
|
|
|
UTF8 *p, *q;
|
|
if (bSucceeded)
|
|
{
|
|
p = aFolders + i + nPattern;
|
|
q = p;
|
|
while ( *q
|
|
&& !mux_isspace(*q))
|
|
{
|
|
q++;
|
|
}
|
|
*q = '\0';
|
|
|
|
// A bug in TinyMUX 2.7 (Jan 22, 2008 through Feb 1, 2009)
|
|
// generated a leading '#' in folder numbers. The following
|
|
// workaround can eventually be removed.
|
|
//
|
|
if ('#' == *p)
|
|
{
|
|
p++;
|
|
}
|
|
|
|
int iFolderNumber = mux_atol(p);
|
|
free_lbuf(aFolders);
|
|
return iFolderNumber;
|
|
}
|
|
}
|
|
free_lbuf(aFolders);
|
|
return -1;
|
|
}
|
|
|
|
static int parse_folder(dbref player, UTF8 *folder_string)
|
|
{
|
|
// Given a string, return a folder #, or -1.
|
|
//
|
|
if ( !folder_string
|
|
|| !*folder_string)
|
|
{
|
|
return -1;
|
|
}
|
|
if (mux_isdigit(*folder_string))
|
|
{
|
|
int fnum = mux_atol(folder_string);
|
|
if ( fnum < 0
|
|
|| fnum > MAX_FOLDERS)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return fnum;
|
|
}
|
|
}
|
|
|
|
// Handle named folders here
|
|
//
|
|
return get_folder_number(player, folder_string);
|
|
}
|
|
|
|
#define MAIL_INVALID_RANGE 0
|
|
#define MAIL_INVALID_NUMBER 1
|
|
#define MAIL_INVALID_AGE 2
|
|
#define MAIL_INVALID_DBREF 3
|
|
#define MAIL_INVALID_PLAYER 4
|
|
#define MAIL_INVALID_SPEC 5
|
|
#define MAIL_INVALID_PLAYER_OR_USING_MALIAS 6
|
|
|
|
static const UTF8 *mailmsg[] =
|
|
{
|
|
T("MAIL: Invalid message range"),
|
|
T("MAIL: Invalid message number"),
|
|
T("MAIL: Invalid age"),
|
|
T("MAIL: Invalid dbref #"),
|
|
T("MAIL: Invalid player"),
|
|
T("MAIL: Invalid message specification"),
|
|
T("MAIL: Invalid player or trying to send @mail to a @malias without a subject"),
|
|
};
|
|
|
|
static bool parse_msglist(UTF8 *msglist, struct mail_selector *ms, dbref player)
|
|
{
|
|
// Take a message list, and return the appropriate mail_selector setup.
|
|
// For now, msglists are quite restricted. That'll change once all this
|
|
// is working. Returns 0 if couldn't parse, and also notifies the player
|
|
// why.
|
|
|
|
// Initialize the mail selector - this matches all messages.
|
|
//
|
|
ms->low = 0;
|
|
ms->high = 0;
|
|
ms->flags = 0x0FFF | M_MSUNREAD;
|
|
ms->player = 0;
|
|
ms->days = -1;
|
|
ms->day_comp = 0;
|
|
|
|
// Now, parse the message list.
|
|
//
|
|
if (!msglist || !*msglist)
|
|
{
|
|
// All messages
|
|
//
|
|
return true;
|
|
}
|
|
|
|
UTF8 *p = msglist;
|
|
while (mux_isspace(*p))
|
|
{
|
|
p++;
|
|
}
|
|
|
|
if (*p == '\0')
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (mux_isdigit(*p))
|
|
{
|
|
// Message or range.
|
|
//
|
|
UTF8 *q = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(p), '-'));
|
|
if (q)
|
|
{
|
|
// We have a subrange, split it up and test to see if it is valid.
|
|
//
|
|
q++;
|
|
ms->low = mux_atol(p);
|
|
if (ms->low <= 0)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_RANGE]);
|
|
return false;
|
|
}
|
|
if (*q == '\0')
|
|
{
|
|
// Unbounded range.
|
|
//
|
|
ms->high = 0;
|
|
}
|
|
else
|
|
{
|
|
ms->high = mux_atol(q);
|
|
if (ms->low > ms->high)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_RANGE]);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// A single message.
|
|
//
|
|
ms->low = ms->high = mux_atol(p);
|
|
if (ms->low <= 0)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_NUMBER]);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (*p)
|
|
{
|
|
case '-':
|
|
|
|
// Range with no start.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_RANGE]);
|
|
return false;
|
|
}
|
|
ms->high = mux_atol(p);
|
|
if (ms->high <= 0)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_RANGE]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case '~':
|
|
|
|
// Exact # of days old.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_AGE]);
|
|
return false;
|
|
}
|
|
ms->day_comp = 0;
|
|
ms->days = mux_atol(p);
|
|
if (ms->days < 0)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_AGE]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case '<':
|
|
|
|
// Less than # of days old.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_AGE]);
|
|
return false;
|
|
}
|
|
ms->day_comp = -1;
|
|
ms->days = mux_atol(p);
|
|
if (ms->days < 0)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_AGE]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case '>':
|
|
|
|
// Greater than # of days old.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_AGE]);
|
|
return false;
|
|
}
|
|
ms->day_comp = 1;
|
|
ms->days = mux_atol(p);
|
|
if (ms->days < 0)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_AGE]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case '#':
|
|
|
|
// From db#.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_DBREF]);
|
|
return false;
|
|
}
|
|
ms->player = mux_atol(p);
|
|
if (!Good_obj(ms->player) || !(ms->player))
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_DBREF]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case '*':
|
|
|
|
// From player name.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_PLAYER]);
|
|
return false;
|
|
}
|
|
ms->player = lookup_player(player, p, true);
|
|
if (ms->player == NOTHING)
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_PLAYER_OR_USING_MALIAS]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'a':
|
|
case 'A':
|
|
|
|
// All messages, all folders
|
|
//
|
|
p++;
|
|
switch (*p)
|
|
{
|
|
case '\0':
|
|
raw_notify(player, T("MAIL: A isn\xE2\x80\x99t enough (all?)"));
|
|
return false;
|
|
|
|
case 'l':
|
|
case 'L':
|
|
|
|
// All messages, all folders
|
|
//
|
|
p++;
|
|
switch (*p)
|
|
{
|
|
case '\0':
|
|
raw_notify(player, T("MAIL: AL isn\xE2\x80\x99t enough (all?)"));
|
|
return false;
|
|
|
|
case 'l':
|
|
case 'L':
|
|
|
|
// All messages, all folders
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
ms->flags = M_ALL;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, mailmsg[MAIL_INVALID_SPEC]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
// Bad
|
|
//
|
|
raw_notify(player, mailmsg[MAIL_INVALID_SPEC]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
// Bad
|
|
//
|
|
raw_notify(player, mailmsg[MAIL_INVALID_SPEC]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'u':
|
|
case 'U':
|
|
|
|
// Urgent, Unread
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, T("MAIL: U is ambiguous (urgent or unread?)"));
|
|
return false;
|
|
}
|
|
|
|
switch (*p)
|
|
{
|
|
case 'r':
|
|
case 'R':
|
|
|
|
// Urgent
|
|
//
|
|
ms->flags = M_URGENT;
|
|
break;
|
|
|
|
case 'n':
|
|
case 'N':
|
|
|
|
// Unread
|
|
//
|
|
ms->flags = M_MSUNREAD;
|
|
break;
|
|
|
|
default:
|
|
|
|
// Bad
|
|
//
|
|
raw_notify(player, mailmsg[MAIL_INVALID_SPEC]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
case 'r':
|
|
case 'R':
|
|
|
|
// Read
|
|
//
|
|
ms->flags = M_ISREAD;
|
|
break;
|
|
|
|
case 'c':
|
|
case 'C':
|
|
|
|
// Cleared.
|
|
//
|
|
ms->flags = M_CLEARED;
|
|
break;
|
|
|
|
case 't':
|
|
case 'T':
|
|
|
|
// Tagged.
|
|
//
|
|
ms->flags = M_TAG;
|
|
break;
|
|
|
|
case 'm':
|
|
case 'M':
|
|
|
|
// Mass, me.
|
|
//
|
|
p++;
|
|
if (*p == '\0')
|
|
{
|
|
raw_notify(player, T("MAIL: M is ambiguous (mass or me?)"));
|
|
return false;
|
|
}
|
|
|
|
switch (*p)
|
|
{
|
|
case 'a':
|
|
case 'A':
|
|
|
|
ms->flags = M_MASS;
|
|
break;
|
|
|
|
case 'e':
|
|
case 'E':
|
|
|
|
ms->player = player;
|
|
break;
|
|
|
|
default:
|
|
|
|
raw_notify(player, mailmsg[MAIL_INVALID_SPEC]);
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
|
|
// Bad news.
|
|
//
|
|
raw_notify(player, mailmsg[MAIL_INVALID_SPEC]);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int player_folder(dbref player)
|
|
{
|
|
// Return the player's current folder number. If they don't have one, set
|
|
// it to 0.
|
|
//
|
|
int flags;
|
|
UTF8 *atrstr = atr_pget(player, A_MAILCURF, &player, &flags);
|
|
if (!*atrstr)
|
|
{
|
|
free_lbuf(atrstr);
|
|
set_player_folder(player, 0);
|
|
return 0;
|
|
}
|
|
int number = mux_atol(atrstr);
|
|
free_lbuf(atrstr);
|
|
return number;
|
|
}
|
|
|
|
|
|
// List mail stats for all current folders
|
|
//
|
|
static void DoListMailBrief(dbref player)
|
|
{
|
|
for (int folder = 0; folder < MAX_FOLDERS; folder++)
|
|
{
|
|
check_mail(player, folder, true);
|
|
|
|
// Show named but empty folders that check_mail skips.
|
|
//
|
|
int rc, uc, cc;
|
|
count_mail(player, folder, &rc, &uc, &cc);
|
|
if ( rc + uc == 0
|
|
&& cc == 0)
|
|
{
|
|
const UTF8 *fname = get_folder_name(player, folder);
|
|
if (strcmp(reinterpret_cast<const char *>(fname), "unnamed") != 0)
|
|
{
|
|
raw_notify(player, tprintf(
|
|
T("MAIL: 0 messages in folder %d [%s]."),
|
|
folder, fname));
|
|
}
|
|
}
|
|
}
|
|
|
|
int current_folder = player_folder(player);
|
|
|
|
raw_notify(player, tprintf(T("MAIL: Current folder is %d [%s]."),
|
|
current_folder, get_folder_name(player, current_folder)));
|
|
}
|
|
|
|
|
|
// Change or rename a folder
|
|
//
|
|
static void do_mail_change_folder(dbref player, UTF8 *fld, UTF8 *newname)
|
|
{
|
|
int pfld;
|
|
|
|
if (!fld || !*fld)
|
|
{
|
|
// Check mail in all folders
|
|
//
|
|
DoListMailBrief(player);
|
|
return;
|
|
}
|
|
|
|
pfld = parse_folder(player, fld);
|
|
if (pfld < 0)
|
|
{
|
|
raw_notify(player, T("MAIL: What folder is that?"));
|
|
return;
|
|
}
|
|
if (newname && *newname)
|
|
{
|
|
// We're changing a folder name here
|
|
//
|
|
if (strlen(reinterpret_cast<char *>(newname)) > FOLDER_NAME_LEN)
|
|
{
|
|
raw_notify(player, T("MAIL: Folder name too long"));
|
|
return;
|
|
}
|
|
UTF8 *p;
|
|
for (p = newname; mux_isalnum(*p); p++) ;
|
|
if (*p != '\0')
|
|
{
|
|
raw_notify(player, T("MAIL: Illegal folder name"));
|
|
return;
|
|
}
|
|
|
|
add_folder_name(player, pfld, newname);
|
|
raw_notify(player, tprintf(T("MAIL: Folder %d now named \xE2\x80\x98%s\xE2\x80\x99"), pfld,
|
|
newname));
|
|
}
|
|
else
|
|
{
|
|
// Set a new folder
|
|
//
|
|
set_player_folder(player, pfld);
|
|
raw_notify(player, tprintf(T("MAIL: Current folder set to %d [%s]."),
|
|
pfld, get_folder_name(player, pfld)));
|
|
}
|
|
}
|
|
|
|
static int sign(int x)
|
|
{
|
|
if (x == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
else if (x < 0)
|
|
{
|
|
return -1;
|
|
}
|
|
else
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
static bool mail_match(struct mail *mp, struct mail_selector ms, int num)
|
|
{
|
|
// Does a piece of mail match the mail_selector?
|
|
//
|
|
if (ms.low && num < ms.low)
|
|
{
|
|
return false;
|
|
}
|
|
if (ms.high && ms.high < num)
|
|
{
|
|
return false;
|
|
}
|
|
if (ms.player && mp->from != ms.player)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
mail_flag mpflag = Read(mp)
|
|
? (mp->read | M_ALL)
|
|
: (mp->read | M_ALL | M_MSUNREAD);
|
|
|
|
if ((ms.flags & mpflag) == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (ms.days == -1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Get the time now, subtract mp->time, and compare the results with
|
|
// ms.days (in manner of ms.day_comp)
|
|
//
|
|
CLinearTimeAbsolute ltaNow;
|
|
ltaNow.GetLocal();
|
|
|
|
const UTF8 *pMailTimeStr = mp->time;
|
|
|
|
CLinearTimeAbsolute ltaMail;
|
|
if (ltaMail.SetString(pMailTimeStr))
|
|
{
|
|
CLinearTimeDelta ltd(ltaMail, ltaNow);
|
|
int iDiffDays = ltd.ReturnDays();
|
|
if (sign(iDiffDays - ms.days) == ms.day_comp)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Adjust the flags of a set of messages.
|
|
// If negate is true, clear the flag.
|
|
static void do_mail_flags(dbref player, UTF8 *msglist, mail_flag flag, bool negate)
|
|
{
|
|
struct mail_selector ms;
|
|
|
|
if (!parse_msglist(msglist, &ms, player))
|
|
{
|
|
return;
|
|
}
|
|
int i = 0, j = 0;
|
|
int folder = player_folder(player);
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( All(ms)
|
|
|| Folder(mp) == folder)
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
j++;
|
|
if (negate)
|
|
{
|
|
mp->read &= ~flag;
|
|
}
|
|
else
|
|
{
|
|
mp->read |= flag;
|
|
}
|
|
sqlite_wt_update_mail_flags(mp);
|
|
|
|
switch (flag)
|
|
{
|
|
case M_TAG:
|
|
raw_notify(player, tprintf(T("MAIL: Msg #%d %s."), i,
|
|
negate ? "untagged" : "tagged"));
|
|
break;
|
|
|
|
case M_CLEARED:
|
|
if (Unread(mp) && !negate)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Unread Msg #%d cleared! Use @mail/unclear %d to recover."), i, i));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Msg #%d %s."), i, negate ? "uncleared" : "cleared"));
|
|
}
|
|
break;
|
|
|
|
case M_SAFE:
|
|
raw_notify(player, tprintf(T("MAIL: Msg #%d %s."), i,
|
|
negate ? "marked unsafe" : "marked safe"));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!j)
|
|
{
|
|
// Ran off the end of the list without finding anything.
|
|
//
|
|
raw_notify(player, T("MAIL: You don\xE2\x80\x99t have any matching messages!"));
|
|
}
|
|
}
|
|
|
|
static void do_mail_tag(dbref player, UTF8 *msglist)
|
|
{
|
|
do_mail_flags(player, msglist, M_TAG, false);
|
|
}
|
|
|
|
static void do_mail_safe(dbref player, UTF8 *msglist)
|
|
{
|
|
do_mail_flags(player, msglist, M_SAFE, false);
|
|
}
|
|
|
|
static void do_mail_unsafe(dbref player, UTF8 *msglist)
|
|
{
|
|
do_mail_flags(player, msglist, M_SAFE, true);
|
|
}
|
|
|
|
void do_mail_clear(dbref player, UTF8 *msglist)
|
|
{
|
|
do_mail_flags(player, msglist, M_CLEARED, false);
|
|
}
|
|
|
|
static void do_mail_untag(dbref player, UTF8 *msglist)
|
|
{
|
|
do_mail_flags(player, msglist, M_TAG, true);
|
|
}
|
|
|
|
static void do_mail_unclear(dbref player, UTF8 *msglist)
|
|
{
|
|
do_mail_flags(player, msglist, M_CLEARED, true);
|
|
}
|
|
|
|
// Change a message's folder.
|
|
//
|
|
static void do_mail_file(dbref player, UTF8 *msglist, UTF8 *folder)
|
|
{
|
|
struct mail_selector ms;
|
|
if (!parse_msglist(msglist, &ms, player))
|
|
{
|
|
return;
|
|
}
|
|
int foldernum;
|
|
if ((foldernum = parse_folder(player, folder)) == -1)
|
|
{
|
|
raw_notify(player, T("MAIL: Invalid folder specification"));
|
|
return;
|
|
}
|
|
int i = 0, j = 0;
|
|
int origfold = player_folder(player);
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( All(ms)
|
|
|| (Folder(mp) == origfold))
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
j++;
|
|
|
|
// Clear the folder.
|
|
//
|
|
mp->read &= M_FMASK;
|
|
mp->read |= FolderBit(foldernum);
|
|
sqlite_wt_update_mail_flags(mp);
|
|
raw_notify(player, tprintf(T("MAIL: Msg %d filed in folder %d"), i,
|
|
foldernum));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!j)
|
|
{
|
|
// Ran off the end of the list without finding anything.
|
|
//
|
|
raw_notify(player, T("MAIL: You don\xE2\x80\x99t have any matching messages!"));
|
|
}
|
|
}
|
|
|
|
// A mail alias can be any combination of upper-case letters, lower-case
|
|
// letters, and digits. No leading digits. No symbols. No ANSI. Length is
|
|
// limited to SIZEOF_MALIAS-1. Case is preserved.
|
|
//
|
|
UTF8 *MakeCanonicalMailAlias
|
|
(
|
|
UTF8 *pMailAlias,
|
|
size_t *pnValidMailAlias,
|
|
bool *pbValidMailAlias
|
|
)
|
|
{
|
|
thread_local UTF8 Buffer[SIZEOF_MALIAS];
|
|
size_t nLeft = sizeof(Buffer)-1;
|
|
UTF8 *q = Buffer;
|
|
UTF8 *p = pMailAlias;
|
|
|
|
if ( !p
|
|
|| !mux_isalpha(*p))
|
|
{
|
|
*pnValidMailAlias = 0;
|
|
*pbValidMailAlias = false;
|
|
return nullptr;
|
|
}
|
|
*q++ = *p++;
|
|
nLeft--;
|
|
|
|
while ( *p
|
|
&& nLeft)
|
|
{
|
|
if ( !mux_isalpha(*p)
|
|
&& !mux_isdigit(*p)
|
|
&& *p != '_')
|
|
{
|
|
break;
|
|
}
|
|
*q++ = *p++;
|
|
nLeft--;
|
|
}
|
|
*q = '\0';
|
|
|
|
*pnValidMailAlias = q - Buffer;
|
|
*pbValidMailAlias = true;
|
|
return Buffer;
|
|
}
|
|
|
|
#define GMA_NOTFOUND 1
|
|
#define GMA_FOUND 2
|
|
#define GMA_INVALIDFORM 3
|
|
|
|
static malias_t *get_malias(dbref player, UTF8 *alias, int *pnResult)
|
|
{
|
|
*pnResult = GMA_INVALIDFORM;
|
|
if (!alias)
|
|
{
|
|
return nullptr;
|
|
}
|
|
if (alias[0] == '#')
|
|
{
|
|
if (ExpMail(player))
|
|
{
|
|
int x = mux_atol(alias + 1);
|
|
if (x < 0 || x >= ma_top)
|
|
{
|
|
*pnResult = GMA_NOTFOUND;
|
|
return nullptr;
|
|
}
|
|
*pnResult = GMA_FOUND;
|
|
return malias[x];
|
|
}
|
|
}
|
|
else if (alias[0] == '*')
|
|
{
|
|
size_t nValidMailAlias;
|
|
bool bValidMailAlias;
|
|
UTF8 *pValidMailAlias = MakeCanonicalMailAlias
|
|
( alias+1,
|
|
&nValidMailAlias,
|
|
&bValidMailAlias
|
|
);
|
|
|
|
if (bValidMailAlias)
|
|
{
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
malias_t *m = malias[i];
|
|
if ( m->owner == player
|
|
|| m->owner == GOD
|
|
|| ExpMail(player))
|
|
{
|
|
if (!strcmp(reinterpret_cast<char *>(pValidMailAlias), reinterpret_cast<char *>(m->name)))
|
|
{
|
|
// Found it!
|
|
//
|
|
*pnResult = GMA_FOUND;
|
|
return m;
|
|
}
|
|
}
|
|
}
|
|
*pnResult = GMA_NOTFOUND;
|
|
}
|
|
}
|
|
if (*pnResult == GMA_INVALIDFORM)
|
|
{
|
|
if (ExpMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Mail aliases must be of the form *<name> or #<num>."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("MAIL: Mail aliases must be of the form *<name>."));
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static UTF8 *make_namelist(dbref player, UTF8 *arg)
|
|
{
|
|
UNUSED_PARAMETER(player);
|
|
|
|
UTF8 *p;
|
|
UTF8 *oldarg = alloc_lbuf("make_namelist.oldarg");
|
|
UTF8 *names = alloc_lbuf("make_namelist.names");
|
|
UTF8 *bp = names;
|
|
|
|
mux_strncpy(oldarg, arg, LBUF_SIZE-1);
|
|
|
|
string_token st(oldarg, T(" "));
|
|
bool bFirst = true;
|
|
for (p = st.parse(); p; p = st.parse())
|
|
{
|
|
if (!bFirst)
|
|
{
|
|
safe_str(T(", "), names, &bp);
|
|
}
|
|
bFirst = false;
|
|
|
|
if ( mux_isdigit(p[0])
|
|
|| ( p[0] == '!'
|
|
&& mux_isdigit(p[1])))
|
|
{
|
|
UTF8 ch = p[0];
|
|
if (ch == '!')
|
|
{
|
|
p++;
|
|
}
|
|
dbref target = mux_atol(p);
|
|
if ( Good_obj(target)
|
|
&& isPlayer(target))
|
|
{
|
|
if (ch == '!')
|
|
{
|
|
safe_chr('!', names, &bp);
|
|
}
|
|
safe_str(Moniker(target), names, &bp);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
safe_str(p, names, &bp);
|
|
}
|
|
}
|
|
*bp = '\0';
|
|
free_lbuf(oldarg);
|
|
return names;
|
|
}
|
|
|
|
#define NUM_MAILSTATUSTABLE 7
|
|
static struct tag_mailstatusentry
|
|
{
|
|
int nMask;
|
|
const UTF8 *pYes;
|
|
int nYes;
|
|
const UTF8 *pNo;
|
|
int nNo;
|
|
}
|
|
aMailStatusTable[NUM_MAILSTATUSTABLE] =
|
|
{
|
|
{ M_ISREAD, T("Read"), 4, T("Unread"), 6 },
|
|
{ M_CLEARED, T("Cleared"), 7, 0, 0 },
|
|
{ M_URGENT, T("Urgent"), 6, 0, 0 },
|
|
{ M_MASS, T("Mass"), 4, 0, 0 },
|
|
{ M_FORWARD, T("Fwd"), 3, 0, 0 },
|
|
{ M_TAG, T("Tagged"), 6, 0, 0 },
|
|
{ M_SAFE, T("Safe"), 4, 0, 0 }
|
|
};
|
|
|
|
static UTF8 *status_string(struct mail *mp)
|
|
{
|
|
// Return a longer description of message flags.
|
|
//
|
|
UTF8 *tbuf1 = alloc_lbuf("status_string");
|
|
UTF8 *p = tbuf1;
|
|
struct tag_mailstatusentry *mse = aMailStatusTable;
|
|
for (int i = 0; i < NUM_MAILSTATUSTABLE; i++, mse++)
|
|
{
|
|
if (mp->read & mse->nMask)
|
|
{
|
|
if (p != tbuf1) *p++ = ' ';
|
|
memcpy(p, mse->pYes, mse->nYes);
|
|
p += mse->nYes;
|
|
}
|
|
else if (mse->pNo)
|
|
{
|
|
if (p != tbuf1) *p++ = ' ';
|
|
memcpy(p, mse->pNo, mse->nNo);
|
|
p += mse->nNo;
|
|
}
|
|
}
|
|
*p++ = '\0';
|
|
return tbuf1;
|
|
}
|
|
|
|
static void do_mail_read(dbref player, UTF8 *arg1, UTF8 *arg2)
|
|
{
|
|
UTF8 *msglist;
|
|
int folder = player_folder(player);
|
|
int original_folder = folder;
|
|
|
|
// Check the argument list, if arg2 is present and valid, then lookup
|
|
// mail in the arg1 folder rather than the default folder.
|
|
//
|
|
if ( nullptr == arg2
|
|
|| '\0' == arg2[0])
|
|
{
|
|
msglist = arg1;
|
|
}
|
|
else
|
|
{
|
|
folder = parse_folder(player, arg1);
|
|
|
|
if (-1 == folder)
|
|
{
|
|
raw_notify(player, T("MAIL: No such folder."));
|
|
return;
|
|
}
|
|
set_player_folder(player, folder);
|
|
msglist = arg2;
|
|
}
|
|
|
|
struct mail_selector ms;
|
|
if (!parse_msglist(msglist, &ms, player))
|
|
{
|
|
return;
|
|
}
|
|
|
|
UTF8 *status, *names;
|
|
int i = 0, j = 0;
|
|
UTF8 *buff = alloc_lbuf("do_mail_read.1");
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( Folder(mp) == folder
|
|
&& mail_to_player(player, mp))
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
// Read it.
|
|
//
|
|
j++;
|
|
|
|
UTF8 *bp = buff;
|
|
safe_str(MessageFetch(mp->number), buff, &bp);
|
|
*bp = '\0';
|
|
|
|
raw_notify(player, DASH_LINE);
|
|
status = status_string(mp);
|
|
names = make_namelist(player, mp->tolist);
|
|
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 65);
|
|
|
|
raw_notify(player, tprintf(T("%-3d From: %s At: %-25s %s\r\nFldr : %-2d Status: %s\r\nTo : %-65s\r\nSubject: %s"),
|
|
i, szFromName,
|
|
mp->time,
|
|
(Connected(mp->from) &&
|
|
(!Hidden(mp->from) || See_Hidden(player))) ?
|
|
" (Conn)" : " ", folder,
|
|
status,
|
|
names,
|
|
szSubjectBuffer));
|
|
free_lbuf(names);
|
|
free_lbuf(status);
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, buff);
|
|
raw_notify(player, DASH_LINE);
|
|
if (Unread(mp))
|
|
{
|
|
// Mark message as read.
|
|
//
|
|
mp->read |= M_ISREAD;
|
|
sqlite_wt_update_mail_flags(mp);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free_lbuf(buff);
|
|
|
|
// If the folder was changed, restore the original folder setting
|
|
//
|
|
if (folder != original_folder)
|
|
{
|
|
set_player_folder(player, original_folder);
|
|
}
|
|
|
|
if (!j)
|
|
{
|
|
// Ran off the end of the list without finding anything.
|
|
//
|
|
raw_notify(player,
|
|
T("MAIL: You don\xE2\x80\x99t have that many matching messages!"));
|
|
}
|
|
}
|
|
|
|
static void do_mail_next(dbref player)
|
|
{
|
|
int folder = player_folder(player);
|
|
int i = 0;
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( Folder(mp) == folder
|
|
&& mail_to_player(player, mp))
|
|
{
|
|
i++;
|
|
if (Unread(mp))
|
|
{
|
|
// Display the message using the same format as do_mail_read.
|
|
//
|
|
UTF8 *buff = alloc_lbuf("do_mail_next.1");
|
|
UTF8 *bp = buff;
|
|
safe_str(MessageFetch(mp->number), buff, &bp);
|
|
*bp = '\0';
|
|
|
|
raw_notify(player, DASH_LINE);
|
|
UTF8 *status = status_string(mp);
|
|
UTF8 *names = make_namelist(player, mp->tolist);
|
|
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 65);
|
|
|
|
raw_notify(player, tprintf(T("%-3d From: %s At: %-25s %s\r\nFldr : %-2d Status: %s\r\nTo : %-65s\r\nSubject: %s"),
|
|
i, szFromName,
|
|
mp->time,
|
|
(Connected(mp->from) &&
|
|
(!Hidden(mp->from) || See_Hidden(player))) ?
|
|
" (Conn)" : " ", folder,
|
|
status,
|
|
names,
|
|
szSubjectBuffer));
|
|
free_lbuf(names);
|
|
free_lbuf(status);
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, buff);
|
|
raw_notify(player, DASH_LINE);
|
|
free_lbuf(buff);
|
|
|
|
// Mark message as read.
|
|
//
|
|
mp->read |= M_ISREAD;
|
|
sqlite_wt_update_mail_flags(mp);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
raw_notify(player,
|
|
T("MAIL: You have no unread messages in that folder."));
|
|
}
|
|
|
|
static UTF8 *status_chars(struct mail *mp)
|
|
{
|
|
// Return a short description of message flags.
|
|
//
|
|
thread_local UTF8 res[10];
|
|
|
|
UTF8 *p = res;
|
|
*p++ = Read(mp) ? '-' : 'N';
|
|
*p++ = M_Safe(mp) ? 'S' : '-';
|
|
*p++ = Cleared(mp) ? 'C' : '-';
|
|
*p++ = Urgent(mp) ? 'U' : '-';
|
|
*p++ = Mass(mp) ? 'M' : '-';
|
|
*p++ = Forward(mp) ? 'F' : '-';
|
|
*p++ = Tagged(mp) ? '+' : '-';
|
|
*p = '\0';
|
|
return res;
|
|
}
|
|
|
|
// Returns true if mp was sent by the current incarnation of player.
|
|
// Guards against recycled dbref: if the mail predates the player's
|
|
// creation, a previous player held this dbref.
|
|
//
|
|
bool mail_from_player(dbref player, struct mail *mp)
|
|
{
|
|
if (mp->from != player)
|
|
{
|
|
return false;
|
|
}
|
|
const UTF8 *pCreated = atr_get_raw(player, A_CREATED);
|
|
if (nullptr == pCreated)
|
|
{
|
|
return false;
|
|
}
|
|
CLinearTimeAbsolute ltaCreated, ltaMail;
|
|
if ( ltaCreated.SetString(pCreated)
|
|
&& ltaMail.SetString(mp->time))
|
|
{
|
|
return ltaCreated <= ltaMail;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Returns true if mp was received by the current incarnation of player.
|
|
// Guards against recycled dbref: if the mail predates the player's
|
|
// creation, a previous player held this dbref.
|
|
//
|
|
bool mail_to_player(dbref player, struct mail *mp)
|
|
{
|
|
if (mp->to != player)
|
|
{
|
|
return false;
|
|
}
|
|
const UTF8 *pCreated = atr_get_raw(player, A_CREATED);
|
|
if (nullptr == pCreated)
|
|
{
|
|
return false;
|
|
}
|
|
CLinearTimeAbsolute ltaCreated, ltaMail;
|
|
if ( ltaCreated.SetString(pCreated)
|
|
&& ltaMail.SetString(mp->time))
|
|
{
|
|
return ltaCreated <= ltaMail;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void do_mail_review_all(dbref player, UTF8 *msglist)
|
|
{
|
|
struct mail *mp;
|
|
struct mail_selector ms;
|
|
int i = 0, j = 0;
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
|
|
if ( !msglist
|
|
|| !*msglist)
|
|
{
|
|
// Summary mode: list all sent messages grouped by recipient.
|
|
//
|
|
for (auto &kv : mudstate.mail_htab)
|
|
{
|
|
dbref target = kv.first;
|
|
bool bHeader = false;
|
|
|
|
MailList ml(target);
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (mail_from_player(player, mp))
|
|
{
|
|
i++;
|
|
|
|
if (!bHeader)
|
|
{
|
|
trimmed_name(target, szFromName, 25, 25, 0);
|
|
raw_notify(player, tprintf(T(MAIL_LINE), szFromName));
|
|
bHeader = true;
|
|
}
|
|
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 25);
|
|
size_t nSize = MessageFetchSize(mp->number);
|
|
raw_notify(player, tprintf(T("[%s] %-3d (%4d) From: %s Sub: %s"),
|
|
status_chars(mp),
|
|
i, nSize,
|
|
szFromName,
|
|
szSubjectBuffer));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (0 == i)
|
|
{
|
|
raw_notify(player, T("MAIL: You have no matching messages."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, DASH_LINE);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Detail mode: show full messages matching msglist.
|
|
//
|
|
if (!parse_msglist(msglist, &ms, player))
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (auto &kv : mudstate.mail_htab)
|
|
{
|
|
dbref target = kv.first;
|
|
|
|
MailList ml(target);
|
|
for (mp = ml.FirstItem(); !ml.IsEnd() && !alarm_clock.alarmed; mp = ml.NextItem())
|
|
{
|
|
if (mail_from_player(player, mp))
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
j++;
|
|
UTF8 *status = status_string(mp);
|
|
const UTF8 *str = MessageFetch(mp->number);
|
|
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 65);
|
|
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, tprintf(T("%-3d From: %s At: %-25s %s\r\nFldr : %-2d Status: %s\r\nSubject: %s"),
|
|
i, szFromName,
|
|
mp->time,
|
|
(Connected(mp->from) &&
|
|
(!Hidden(mp->from) || See_Hidden(player))) ?
|
|
" (Conn)" : " ", 0,
|
|
status, szSubjectBuffer));
|
|
free_lbuf(status);
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, str);
|
|
raw_notify(player, DASH_LINE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!j)
|
|
{
|
|
raw_notify(player,
|
|
T("MAIL: You don\xE2\x80\x99t have that many matching messages!"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_mail_review(dbref player, UTF8 *name, UTF8 *msglist)
|
|
{
|
|
if ( name
|
|
&& '\0' != name[0]
|
|
&& 0 == mux_stricmp(name, T("all")))
|
|
{
|
|
do_mail_review_all(player, msglist);
|
|
return;
|
|
}
|
|
|
|
dbref target = lookup_player(player, name, true);
|
|
if (target == NOTHING)
|
|
{
|
|
raw_notify(player, T("MAIL: No such player."));
|
|
return;
|
|
}
|
|
|
|
struct mail *mp;
|
|
struct mail_selector ms;
|
|
int i = 0, j = 0;
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
|
|
if ( !msglist
|
|
|| !*msglist)
|
|
{
|
|
trimmed_name(target, szFromName, 25, 25, 0);
|
|
raw_notify(player, tprintf(T(MAIL_LINE), szFromName));
|
|
MailList ml(target);
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (mail_from_player(player, mp))
|
|
{
|
|
i++;
|
|
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 25);
|
|
size_t nSize = MessageFetchSize(mp->number);
|
|
raw_notify(player, tprintf(T("[%s] %-3d (%4d) From: %s Sub: %s"),
|
|
status_chars(mp),
|
|
i, nSize,
|
|
szFromName,
|
|
szSubjectBuffer));
|
|
}
|
|
}
|
|
raw_notify(player, DASH_LINE);
|
|
}
|
|
else
|
|
{
|
|
if (!parse_msglist(msglist, &ms, target))
|
|
{
|
|
return;
|
|
}
|
|
MailList ml(target);
|
|
for (mp = ml.FirstItem(); !ml.IsEnd() && !alarm_clock.alarmed; mp = ml.NextItem())
|
|
{
|
|
if (mail_from_player(player, mp))
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
j++;
|
|
UTF8 *status = status_string(mp);
|
|
const UTF8 *str = MessageFetch(mp->number);
|
|
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 65);
|
|
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, tprintf(T("%-3d From: %s At: %-25s %s\r\nFldr : %-2d Status: %s\r\nSubject: %s"),
|
|
i, szFromName,
|
|
mp->time,
|
|
(Connected(mp->from) &&
|
|
(!Hidden(mp->from) || See_Hidden(player))) ?
|
|
" (Conn)" : " ", 0,
|
|
status, szSubjectBuffer));
|
|
free_lbuf(status);
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, str);
|
|
raw_notify(player, DASH_LINE);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!j)
|
|
{
|
|
// Ran off the end of the list without finding anything.
|
|
//
|
|
raw_notify(player,
|
|
T("MAIL: You don\xE2\x80\x99t have that many matching messages!"));
|
|
}
|
|
}
|
|
}
|
|
|
|
static UTF8 *mail_list_time(const UTF8 *the_time)
|
|
{
|
|
const UTF8 *p = the_time;
|
|
UTF8 *new0 = alloc_lbuf("mail_list_time");
|
|
UTF8 *q = new0;
|
|
if (!p || !*p)
|
|
{
|
|
*new0 = '\0';
|
|
return new0;
|
|
}
|
|
|
|
// Format of the_time is: day mon dd hh:mm:ss yyyy
|
|
// Chop out :ss
|
|
//
|
|
int i;
|
|
for (i = 0; i < 16; i++)
|
|
{
|
|
if (*p)
|
|
{
|
|
*q++ = *p++;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
if (*p)
|
|
{
|
|
p++;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
if (*p)
|
|
{
|
|
*q++ = *p++;
|
|
}
|
|
}
|
|
|
|
*q = '\0';
|
|
return new0;
|
|
}
|
|
|
|
static void do_mail_list(dbref player, UTF8 *arg1, UTF8 *arg2, bool sub)
|
|
{
|
|
UTF8 *msglist;
|
|
int folder = player_folder(player);
|
|
int original_folder = folder;
|
|
|
|
// Check the argument list, if arg2 is present and valid, then lookup
|
|
// mail in the arg1 folder rather than the default folder.
|
|
//
|
|
if ( nullptr == arg2
|
|
|| '\0' == arg2[0])
|
|
{
|
|
msglist = arg1;
|
|
}
|
|
else
|
|
{
|
|
folder = parse_folder(player, arg1);
|
|
|
|
if (-1 == folder)
|
|
{
|
|
raw_notify(player, T("MAIL: No such folder."));
|
|
return;
|
|
}
|
|
set_player_folder(player, folder);
|
|
msglist = arg2;
|
|
if ( nullptr != msglist
|
|
&& '*' == msglist[0])
|
|
{
|
|
msglist[0] = '\0';
|
|
}
|
|
sub = true;
|
|
}
|
|
|
|
struct mail_selector ms;
|
|
|
|
if (!parse_msglist(msglist, &ms, player))
|
|
{
|
|
return;
|
|
}
|
|
int i = 0;
|
|
UTF8 *time;
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
|
|
raw_notify(player, tprintf(T(FOLDER_LINE), folder));
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( Folder(mp) == folder
|
|
&& mail_to_player(player, mp))
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
time = mail_list_time(mp->time);
|
|
size_t nSize = MessageFetchSize(mp->number);
|
|
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
if (sub)
|
|
{
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer, MBUF_SIZE-1, 25);
|
|
|
|
raw_notify(player, tprintf(T("[%s] %-3d (%4d) From: %s Sub: %s"),
|
|
status_chars(mp), i, nSize, szFromName, szSubjectBuffer));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("[%s] %-3d (%4d) From: %s At: %s %s"),
|
|
status_chars(mp), i, nSize, szFromName, time,
|
|
((Connected(mp->from) && (!Hidden(mp->from) || See_Hidden(player))) ? "Conn" : " ")));
|
|
}
|
|
free_lbuf(time);
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, DASH_LINE);
|
|
|
|
if (folder != original_folder)
|
|
{
|
|
set_player_folder(player, original_folder);
|
|
}
|
|
}
|
|
|
|
void do_mail_purge(dbref player)
|
|
{
|
|
// Go through player's mail, and remove anything marked cleared.
|
|
//
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (Cleared(mp))
|
|
{
|
|
ml.RemoveItem();
|
|
}
|
|
}
|
|
raw_notify(player, T("MAIL: Mailbox purged."));
|
|
}
|
|
|
|
static UTF8 *make_numlist(dbref player, UTF8 *arg, bool bBlind)
|
|
{
|
|
UTF8 *tail, spot;
|
|
malias_t *m;
|
|
dbref target;
|
|
int nRecip = 0;
|
|
dbref aRecip[(LBUF_SIZE+1)/2];
|
|
|
|
UTF8 *head = arg;
|
|
|
|
while ( head
|
|
&& *head)
|
|
{
|
|
while (*head == ' ')
|
|
{
|
|
head++;
|
|
}
|
|
|
|
tail = head;
|
|
while ( *tail
|
|
&& *tail != ' ')
|
|
{
|
|
if (*tail == '"')
|
|
{
|
|
head++;
|
|
tail++;
|
|
while ( *tail
|
|
&& *tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
if (*tail)
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
tail--;
|
|
if (*tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
spot = *tail;
|
|
*tail = '\0';
|
|
|
|
if (*head == '*')
|
|
{
|
|
int nResult;
|
|
m = get_malias(player, head, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 does not exist."), head));
|
|
return nullptr;
|
|
}
|
|
else if (nResult == GMA_INVALIDFORM)
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: \xE2\x80\x98%s\xE2\x80\x99 is a badly-formed alias."), head));
|
|
return nullptr;
|
|
}
|
|
for (size_t i = 0; i < m->list.size(); i++)
|
|
{
|
|
aRecip[nRecip++] = m->list[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
target = lookup_player(player, head, true);
|
|
if (Good_obj(target))
|
|
{
|
|
aRecip[nRecip++] = target;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: \xE2\x80\x98%s\xE2\x80\x99 does not exist."), head));
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Get the next recip.
|
|
//
|
|
*tail = spot;
|
|
head = tail;
|
|
if (*head == '"')
|
|
{
|
|
head++;
|
|
}
|
|
}
|
|
|
|
if (nRecip <= 0)
|
|
{
|
|
raw_notify(player, T("MAIL: No players specified."));
|
|
return nullptr;
|
|
}
|
|
else
|
|
{
|
|
ITL itl;
|
|
UTF8 *numbuf, *numbp;
|
|
numbp = numbuf = alloc_lbuf("mail.make_numlist");
|
|
ItemToList_Init(&itl, numbuf, &numbp, bBlind ? '!' : '\0');
|
|
int i;
|
|
for (i = 0; i < nRecip; i++)
|
|
{
|
|
if (aRecip[i] != NOTHING)
|
|
{
|
|
for (int j = i + 1; j < nRecip; j++)
|
|
{
|
|
if (aRecip[i] == aRecip[j])
|
|
{
|
|
aRecip[j] = NOTHING;
|
|
}
|
|
}
|
|
if (Good_obj(aRecip[i]))
|
|
{
|
|
ItemToList_AddInteger(&itl, aRecip[i]);
|
|
}
|
|
}
|
|
}
|
|
ItemToList_Final(&itl);
|
|
return numbuf;
|
|
}
|
|
}
|
|
|
|
static void do_expmail_start(dbref player, UTF8 *arg, UTF8 *subject)
|
|
{
|
|
if (!arg || !*arg)
|
|
{
|
|
raw_notify(player, T("MAIL: I do not know whom you want to mail."));
|
|
return;
|
|
}
|
|
if (!subject || !*subject)
|
|
{
|
|
raw_notify(player, T("MAIL: No subject."));
|
|
return;
|
|
}
|
|
if (Flags2(player) & PLAYER_MAILS)
|
|
{
|
|
raw_notify(player, T("MAIL: Mail message already in progress."));
|
|
return;
|
|
}
|
|
if ( !Wizard(player)
|
|
&& ThrottleMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Too much @mail sent recently."));
|
|
return;
|
|
}
|
|
UTF8 *tolist = make_numlist(player, arg, false);
|
|
if (!tolist)
|
|
{
|
|
return;
|
|
}
|
|
|
|
atr_add_raw(player, A_MAILTO, tolist);
|
|
atr_add_raw(player, A_MAILSUB, subject);
|
|
atr_add_raw(player, A_MAILFLAGS, T("0"));
|
|
atr_clr(player, A_MAILMSG);
|
|
Flags2(player) |= PLAYER_MAILS;
|
|
UTF8 *names = make_namelist(player, tolist);
|
|
raw_notify(player, tprintf(T("MAIL: You are sending mail to \xE2\x80\x98%s\xE2\x80\x99."), names));
|
|
free_lbuf(names);
|
|
free_lbuf(tolist);
|
|
}
|
|
|
|
static void do_mail_fwd(dbref player, UTF8 *msg, UTF8 *tolist)
|
|
{
|
|
if (Flags2(player) & PLAYER_MAILS)
|
|
{
|
|
raw_notify(player, T("MAIL: Mail message already in progress."));
|
|
return;
|
|
}
|
|
if (!msg || !*msg)
|
|
{
|
|
raw_notify(player, T("MAIL: No message list."));
|
|
return;
|
|
}
|
|
if (!tolist || !*tolist)
|
|
{
|
|
raw_notify(player, T("MAIL: To whom should I forward?"));
|
|
return;
|
|
}
|
|
if ( !Wizard(player)
|
|
&& ThrottleMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Too much @mail sent recently."));
|
|
return;
|
|
}
|
|
int num = mux_atol(msg);
|
|
if (!num)
|
|
{
|
|
raw_notify(player, T("MAIL: I don\xE2\x80\x99t understand that message number."));
|
|
return;
|
|
}
|
|
struct mail *mp = mail_fetch(player, num);
|
|
if (!mp)
|
|
{
|
|
raw_notify(player, T("MAIL: You can\xE2\x80\x99t forward non-existent messages."));
|
|
return;
|
|
}
|
|
do_expmail_start(player, tolist, tprintf(T("%s (fwd from %s)"), mp->subject, Moniker(mp->from)));
|
|
atr_add_raw(player, A_MAILMSG, MessageFetch(mp->number));
|
|
const UTF8 *pValue = atr_get_raw(player, A_MAILFLAGS);
|
|
int iFlag = M_FORWARD;
|
|
if (pValue)
|
|
{
|
|
iFlag |= mux_atol(pValue);
|
|
}
|
|
atr_add_raw(player, A_MAILFLAGS, mux_ltoa_t(iFlag));
|
|
}
|
|
|
|
static void do_mail_reply(dbref player, UTF8 *msg, bool all, int key)
|
|
{
|
|
if (Flags2(player) & PLAYER_MAILS)
|
|
{
|
|
raw_notify(player, T("MAIL: Mail message already in progress."));
|
|
return;
|
|
}
|
|
if (!msg || !*msg)
|
|
{
|
|
raw_notify(player, T("MAIL: No message list."));
|
|
return;
|
|
}
|
|
if ( !Wizard(player)
|
|
&& ThrottleMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Too much @mail sent recently."));
|
|
return;
|
|
}
|
|
int num = mux_atol(msg);
|
|
if (!num)
|
|
{
|
|
raw_notify(player, T("MAIL: I don\xE2\x80\x99t understand that message number."));
|
|
return;
|
|
}
|
|
struct mail *mp = mail_fetch(player, num);
|
|
if (!mp)
|
|
{
|
|
raw_notify(player, T("MAIL: You can\xE2\x80\x99t reply to non-existent messages."));
|
|
return;
|
|
}
|
|
if (!mail_from_player(mp->from, mp))
|
|
{
|
|
raw_notify(player, T("MAIL: The original sender no longer exists."));
|
|
return;
|
|
}
|
|
UTF8 *tolist = alloc_lbuf("do_mail_reply.tolist");
|
|
UTF8 *bp = tolist;
|
|
if (all)
|
|
{
|
|
UTF8 *names = alloc_lbuf("do_mail_reply.names");
|
|
UTF8 *oldlist = alloc_lbuf("do_mail_reply.oldlist");
|
|
bp = names;
|
|
*bp = '\0';
|
|
|
|
mux_strncpy(oldlist, mp->tolist, LBUF_SIZE-1);
|
|
|
|
string_token st(oldlist, T(" "));
|
|
UTF8 *p;
|
|
for (p = st.parse(); p; p = st.parse())
|
|
{
|
|
if (mux_atol(p) != mp->from)
|
|
{
|
|
safe_chr('#', names, &bp);
|
|
safe_str(p, names, &bp);
|
|
safe_chr(' ', names, &bp);
|
|
}
|
|
}
|
|
free_lbuf(oldlist);
|
|
safe_chr('#', names, &bp);
|
|
safe_ltoa(mp->from, names, &bp);
|
|
*bp = '\0';
|
|
mux_strncpy(tolist, names, LBUF_SIZE-1);
|
|
free_lbuf(names);
|
|
}
|
|
else
|
|
{
|
|
safe_chr('#', tolist, &bp);
|
|
safe_ltoa(mp->from, tolist, &bp);
|
|
*bp = '\0';
|
|
}
|
|
|
|
const UTF8 *pSubject = mp->subject;
|
|
const UTF8 *pMessage = MessageFetch(mp->number);
|
|
const UTF8 *pTime = mp->time;
|
|
if (strncmp(reinterpret_cast<const char *>(pSubject), "Re:", 3))
|
|
{
|
|
do_expmail_start(player, tolist, tprintf(T("Re: %s"), pSubject));
|
|
}
|
|
else
|
|
{
|
|
do_expmail_start(player, tolist, tprintf(T("%s"), pSubject));
|
|
}
|
|
if (key & MAIL_QUOTE)
|
|
{
|
|
const UTF8 *pFromName = Moniker(mp->from);
|
|
UTF8 *pMessageBody =
|
|
tprintf(T("On %s, %s wrote:\r\n\r\n%s\r\n\r\n********** End of included message from %s\r\n"),
|
|
pTime, pFromName, pMessage, pFromName);
|
|
atr_add_raw(player, A_MAILMSG, pMessageBody);
|
|
}
|
|
|
|
// The following combination of atr_get_raw() with atr_add_raw() is OK
|
|
// because we are not passing a pointer to atr_add_raw() that came
|
|
// directly from atr_get_raw().
|
|
//
|
|
const UTF8 *pValue = atr_get_raw(player, A_MAILFLAGS);
|
|
int iFlag = M_REPLY;
|
|
if (pValue)
|
|
{
|
|
iFlag |= mux_atol(pValue);
|
|
}
|
|
atr_add_raw(player, A_MAILFLAGS, mux_ltoa_t(iFlag));
|
|
|
|
free_lbuf(tolist);
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Admin mail functions
|
|
*
|
|
* do_mail_nuke - clear & purge mail for a player, or all mail in db.
|
|
* do_mail_stat - stats on mail for a player, or for all db.
|
|
* do_mail_debug - fix mail with a sledgehammer
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Basic mail functions
|
|
*-------------------------------------------------------------------------*/
|
|
struct mail *mail_fetch(dbref player, int num)
|
|
{
|
|
int i = 0;
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( Folder(mp) == player_folder(player)
|
|
&& mail_to_player(player, mp))
|
|
{
|
|
i++;
|
|
if (i == num)
|
|
{
|
|
return mp;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
const UTF8 *mail_fetch_message(dbref player, int num)
|
|
{
|
|
struct mail *mp = mail_fetch(player, num);
|
|
if (mp)
|
|
{
|
|
return MessageFetch(mp->number);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
int mail_fetch_from(dbref player, int num)
|
|
{
|
|
struct mail *mp = mail_fetch(player, num);
|
|
if (mp)
|
|
{
|
|
return mp->from;
|
|
}
|
|
return NOTHING;
|
|
}
|
|
|
|
// Returns count of read, unread, and cleared messages as rcount, ucount, ccount.
|
|
//
|
|
void count_mail(dbref player, int folder, int *rcount, int *ucount, int *ccount)
|
|
{
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->CountMail(player, folder, rcount, ucount, ccount);
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
int rc = 0;
|
|
int uc = 0;
|
|
int cc = 0;
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( Folder(mp) == folder
|
|
&& mail_to_player(player, mp))
|
|
{
|
|
if (Read(mp))
|
|
{
|
|
rc++;
|
|
}
|
|
else
|
|
{
|
|
uc++;
|
|
}
|
|
|
|
if (Cleared(mp))
|
|
{
|
|
cc++;
|
|
}
|
|
}
|
|
}
|
|
*rcount = rc;
|
|
*ucount = uc;
|
|
*ccount = cc;
|
|
}
|
|
|
|
static void urgent_mail(dbref player, int folder, int *ucount)
|
|
{
|
|
int uc = 0;
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( Folder(mp) == folder
|
|
&& mail_to_player(player, mp))
|
|
{
|
|
if (Unread(mp) && Urgent(mp))
|
|
{
|
|
uc++;
|
|
}
|
|
}
|
|
}
|
|
*ucount = uc;
|
|
}
|
|
|
|
static void mail_return(dbref player, dbref target)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
|
|
UTF8 *str = atr_pget(target, A_MFAIL, &aowner, &aflags);
|
|
if (*str)
|
|
{
|
|
UTF8 *str2, *bp;
|
|
str2 = bp = alloc_lbuf("mail_return");
|
|
mux_exec(str, LBUF_SIZE-1, str2, &bp, target, player, player,
|
|
AttrTrace(aflags, EV_FCHECK|EV_EVAL|EV_TOP|EV_NO_LOCATION),
|
|
nullptr, 0);
|
|
*bp = '\0';
|
|
if (*str2)
|
|
{
|
|
CLinearTimeAbsolute ltaNow;
|
|
ltaNow.GetLocal();
|
|
FIELDEDTIME ft;
|
|
ltaNow.ReturnFields(&ft);
|
|
|
|
raw_notify(player, tprintf(T("MAIL: Reject message from %s: %s"),
|
|
Moniker(target), str2));
|
|
raw_notify(target, tprintf(T("[%d:%02d] MAIL: Reject message sent to %s."),
|
|
ft.iHour, ft.iMinute, Moniker(player)));
|
|
}
|
|
free_lbuf(str2);
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("Sorry, %s is not accepting mail."), Moniker(target)));
|
|
}
|
|
free_lbuf(str);
|
|
}
|
|
|
|
static bool mail_check(dbref player, dbref target)
|
|
{
|
|
if (!could_doit(player, target, A_LMAIL))
|
|
{
|
|
mail_return(player, target);
|
|
}
|
|
else if (!could_doit(target, player, A_LMAIL))
|
|
{
|
|
if (Wizard(player))
|
|
{
|
|
raw_notify(player, tprintf(T("Warning: %s can\xE2\x80\x99t return your mail."), Moniker(target)));
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("Sorry, %s can\xE2\x80\x99t return your mail."), Moniker(target)));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void send_mail
|
|
(
|
|
dbref player,
|
|
dbref target,
|
|
const UTF8 *tolist,
|
|
const UTF8 *subject,
|
|
int number,
|
|
mail_flag flags,
|
|
bool silent
|
|
)
|
|
{
|
|
if (!isPlayer(target))
|
|
{
|
|
raw_notify(player, T("MAIL: You cannot send mail to non-existent people."));
|
|
return;
|
|
}
|
|
if (!mail_check(player, target))
|
|
{
|
|
return;
|
|
}
|
|
CLinearTimeAbsolute ltaNow;
|
|
ltaNow.GetLocal();
|
|
|
|
const UTF8 *pTimeStr = ltaNow.ReturnDateString(0);
|
|
|
|
// Initialize the appropriate fields.
|
|
//
|
|
struct mail *newp = nullptr;
|
|
try
|
|
{
|
|
newp = new struct mail;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == newp)
|
|
{
|
|
raw_notify(player, T("MAIL: Out of memory."));
|
|
return;
|
|
}
|
|
|
|
newp->to = target;
|
|
newp->sqlite_id = -1;
|
|
|
|
// Sender attribution policy for @mail/quick from objects:
|
|
// - If the sender is a player, credit the player.
|
|
// - If the sender is an object owned by a wizard, credit the
|
|
// object itself (wizards can run trusted delivery bots).
|
|
// - Otherwise credit the object's owner (so a non-wizard player
|
|
// can't spoof mail origin by using an intermediate object).
|
|
//
|
|
if (isPlayer(player))
|
|
{
|
|
newp->from = player;
|
|
}
|
|
else
|
|
{
|
|
dbref mailbag = Owner(player);
|
|
if (Wizard(mailbag))
|
|
{
|
|
newp->from = player;
|
|
}
|
|
else
|
|
{
|
|
newp->from = mailbag;
|
|
}
|
|
}
|
|
if ( !tolist
|
|
|| tolist[0] == '\0')
|
|
{
|
|
newp->tolist = StringClone(T("*HIDDEN*"));
|
|
}
|
|
else
|
|
{
|
|
newp->tolist = StringClone(tolist);
|
|
}
|
|
|
|
newp->number = number;
|
|
MessageReferenceInc(number);
|
|
newp->time = StringClone(pTimeStr);
|
|
newp->subject = StringClone(subject);
|
|
|
|
// Send to folder 0
|
|
//
|
|
newp->read = flags & M_FMASK;
|
|
|
|
// Reject if the target's mailbox is full.
|
|
//
|
|
if ( 0 < mudconf.mail_max_per_player
|
|
&& !No_Mail_Expire(target))
|
|
{
|
|
int total = 0;
|
|
MailList ml_count(target);
|
|
struct mail *mp_count;
|
|
for (mp_count = ml_count.FirstItem(); !ml_count.IsEnd();
|
|
mp_count = ml_count.NextItem())
|
|
{
|
|
total++;
|
|
}
|
|
if (total >= mudconf.mail_max_per_player)
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: %s\xE2\x80\x99s mailbox is full (%d messages)."),
|
|
Moniker(target), total));
|
|
MessageReferenceDec(newp->number);
|
|
MEMFREE(newp->subject);
|
|
MEMFREE(newp->tolist);
|
|
MEMFREE(newp->time);
|
|
delete newp;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If this is the first message, it is the head and the tail.
|
|
//
|
|
MailList ml(target);
|
|
ml.AppendItem(newp);
|
|
sqlite_wt_insert_mail(newp);
|
|
|
|
// Notify people.
|
|
//
|
|
if (!silent)
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: You sent your message to %s."), Moniker(target)));
|
|
}
|
|
|
|
raw_notify(target,
|
|
tprintf(T("MAIL: You have a new message from %s. Subject: %s"),
|
|
Moniker(player), subject));
|
|
|
|
did_it(player, target, A_MAIL, nullptr, 0, nullptr, A_AMAIL, 0, nullptr, NOTHING);
|
|
}
|
|
|
|
static void do_mail_nuke(dbref player)
|
|
{
|
|
if (!God(player))
|
|
{
|
|
raw_notify(player,
|
|
T("The postal service issues a warrant for your arrest."));
|
|
return;
|
|
}
|
|
|
|
// Walk the list.
|
|
//
|
|
dbref thing;
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
ml.RemoveAll();
|
|
}
|
|
log_printf(T("** MAIL PURGE ** done by %s(#%d)." ENDLINE), PureName(player), player);
|
|
raw_notify(player, T("You annihilate the post office. All messages cleared."));
|
|
}
|
|
|
|
// Purge a destroyed player's mail presence. Called from destroy_player().
|
|
//
|
|
// 1. Remove all received mail.
|
|
// 2. Orphan sent mail so a recycled dbref cannot reply to the old player.
|
|
// 3. Clean up mail aliases.
|
|
//
|
|
void mail_destroy_player(dbref victim)
|
|
{
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->DestroyPlayerMail(victim);
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Step 1: Purge received mail.
|
|
//
|
|
MailList ml(victim);
|
|
ml.RemoveAll();
|
|
|
|
// Step 2: Orphan sent mail in every other player's mailbox.
|
|
//
|
|
for (auto &kv : mudstate.mail_htab)
|
|
{
|
|
MailList ml2(kv.first);
|
|
struct mail *mp;
|
|
for (mp = ml2.FirstItem(); !ml2.IsEnd(); mp = ml2.NextItem())
|
|
{
|
|
if (mp->from == victim)
|
|
{
|
|
mp->from = NOTHING;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Step 3: Clean up mail aliases.
|
|
//
|
|
malias_cleanup(victim);
|
|
}
|
|
|
|
#ifdef SELFCHECK
|
|
void finish_mail()
|
|
{
|
|
dbref thing;
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
ml.RemoveAll();
|
|
}
|
|
|
|
if (nullptr != mail_list)
|
|
{
|
|
mail_list -= MAIL_FUDGE;
|
|
MEMFREE(mail_list);
|
|
mail_list = nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
static void do_mail_debug(dbref player, UTF8 *action, UTF8 *victim)
|
|
{
|
|
if (!ExpMail(player))
|
|
{
|
|
raw_notify(player, T("Go get some bugspray."));
|
|
return;
|
|
}
|
|
|
|
dbref thing;
|
|
if (string_prefix(T("clear"), action))
|
|
{
|
|
dbref target = lookup_player(player, victim, true);
|
|
if (target == NOTHING)
|
|
{
|
|
init_match(player, victim, NOTYPE);
|
|
match_absolute();
|
|
target = match_result();
|
|
}
|
|
if (target == NOTHING)
|
|
{
|
|
raw_notify(player, tprintf(T("%s: no such player."), victim));
|
|
return;
|
|
}
|
|
if (Wizard(target))
|
|
{
|
|
raw_notify(player, tprintf(T("Let %s clear their own @mail."), Moniker(target)));
|
|
return;
|
|
}
|
|
do_mail_clear(target, nullptr);
|
|
do_mail_purge(target);
|
|
raw_notify(player, tprintf(T("Mail cleared for %s(#%d)."), Moniker(target), target));
|
|
return;
|
|
}
|
|
else if (string_prefix(T("sanity"), action))
|
|
{
|
|
std::vector<int> ai(mudstate.mail_db_top, 0);
|
|
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
bool bGoodReference;
|
|
if (0 <= mp->number && mp->number < mudstate.mail_db_top)
|
|
{
|
|
ai[mp->number]++;
|
|
bGoodReference = true;
|
|
}
|
|
else
|
|
{
|
|
bGoodReference = false;
|
|
}
|
|
if (!Good_obj(mp->to))
|
|
{
|
|
if (bGoodReference)
|
|
{
|
|
raw_notify(player, tprintf(T("Bad object #%d has mail."), mp->to));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("Bad object #%d has mail which refers to a non-existent mailbag item."), mp->to));
|
|
}
|
|
}
|
|
else if (!isPlayer(mp->to))
|
|
{
|
|
if (bGoodReference)
|
|
{
|
|
raw_notify(player, tprintf(T("%s(#%d) has mail, but is not a player."),
|
|
Moniker(mp->to), mp->to));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("%s(#%d) is not a player, but has mail which refers to a non-existent mailbag item."),
|
|
Moniker(mp->to), mp->to));
|
|
}
|
|
}
|
|
else if (!bGoodReference)
|
|
{
|
|
raw_notify(player, tprintf(T("%s(#%d) has mail which refers to a non-existent mailbag item."), Moniker(mp->to), mp->to));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check ref counts.
|
|
//
|
|
if (mail_list)
|
|
{
|
|
int i;
|
|
int nCountHigher = 0;
|
|
int nCountLower = 0;
|
|
for (i = 0; i < mudstate.mail_db_top; i++)
|
|
{
|
|
if (mail_list[i].m_nRefs < ai[i])
|
|
{
|
|
nCountLower++;
|
|
}
|
|
else if (mail_list[i].m_nRefs > ai[i])
|
|
{
|
|
nCountHigher++;
|
|
}
|
|
}
|
|
if (nCountLower)
|
|
{
|
|
raw_notify(player, T("Some mailbag items are referred to more often than the mailbag item indicates."));
|
|
}
|
|
if (nCountHigher)
|
|
{
|
|
raw_notify(player, T("Some mailbag items are referred to less often than the mailbag item indicates."));
|
|
}
|
|
}
|
|
raw_notify(player, T("Mail sanity check completed."));
|
|
}
|
|
else if (string_prefix(T("fix"), action))
|
|
{
|
|
// First, we should fixup the reference counts.
|
|
//
|
|
if (mail_list)
|
|
{
|
|
raw_notify(player, tprintf(T("Re-counting mailbag reference counts.")));
|
|
std::vector<int> ai(mudstate.mail_db_top, 0);
|
|
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( 0 <= mp->number
|
|
&& mp->number < mudstate.mail_db_top)
|
|
{
|
|
ai[mp->number]++;
|
|
}
|
|
else
|
|
{
|
|
mp->number = NOTHING;
|
|
}
|
|
}
|
|
}
|
|
int i;
|
|
int nCountWrong = 0;
|
|
for (i = 0; i < mudstate.mail_db_top; i++)
|
|
{
|
|
if (mail_list[i].m_nRefs != ai[i])
|
|
{
|
|
mail_list[i].m_nRefs = ai[i];
|
|
nCountWrong++;
|
|
}
|
|
}
|
|
if (nCountWrong)
|
|
{
|
|
raw_notify(player, T("Some reference counts were wrong [FIXED]."));
|
|
}
|
|
|
|
}
|
|
|
|
raw_notify(player, tprintf(T("Removing @mail that is associated with non-players.")));
|
|
|
|
// Now, remove all mail to non-good or non-players, or mail that
|
|
// points to non-existent mailbag items.
|
|
//
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if ( !Good_obj(mp->to)
|
|
|| !isPlayer(mp->to)
|
|
|| NOTHING == mp->number)
|
|
{
|
|
// Delete this item.
|
|
//
|
|
raw_notify(player, tprintf(T("Fixing mail for #%d."), mp->to));
|
|
ml.RemoveItem();
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, T("Mail sanity fix completed."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("That is not a debugging option."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void do_mail_stats(dbref player, UTF8 *name, int full)
|
|
{
|
|
dbref target, thing;
|
|
int fc, fr, fu, tc, tr, tu, count;
|
|
size_t cchars = 0;
|
|
size_t fchars = 0;
|
|
size_t tchars = 0;
|
|
fc = fr = fu = tc = tr = tu = count = 0;
|
|
|
|
// Find player.
|
|
//
|
|
if ( !name
|
|
|| *name == '\0')
|
|
{
|
|
if (Wizard(player))
|
|
{
|
|
target = AMBIGUOUS;
|
|
}
|
|
else
|
|
{
|
|
target = player;
|
|
}
|
|
}
|
|
else if (*name == NUMBER_TOKEN)
|
|
{
|
|
target = mux_atol(&name[1]);
|
|
if (!Good_obj(target) || !isPlayer(target))
|
|
{
|
|
target = NOTHING;
|
|
}
|
|
}
|
|
else if (!mux_stricmp(name, T("me")))
|
|
{
|
|
target = player;
|
|
}
|
|
else
|
|
{
|
|
target = lookup_player(player, name, true);
|
|
}
|
|
|
|
if (target == NOTHING)
|
|
{
|
|
init_match(player, name, NOTYPE);
|
|
match_absolute();
|
|
target = match_result();
|
|
}
|
|
if (target == NOTHING)
|
|
{
|
|
raw_notify(player, tprintf(T("%s: No such player."), name));
|
|
return;
|
|
}
|
|
if (!ExpMail(player) && (target != player))
|
|
{
|
|
raw_notify(player, T("The post office protects privacy!"));
|
|
return;
|
|
}
|
|
|
|
// This comand is computationally expensive.
|
|
//
|
|
if (!payfor(player, mudconf.searchcost))
|
|
{
|
|
raw_notify(player, tprintf(T("Finding mail stats costs %d %s."),
|
|
mudconf.searchcost,
|
|
(mudconf.searchcost == 1) ? mudconf.one_coin : mudconf.many_coins));
|
|
return;
|
|
}
|
|
if (AMBIGUOUS == target)
|
|
{
|
|
// Stats for all.
|
|
//
|
|
if (full == 0)
|
|
{
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
for ((void)ml.FirstItem(); !ml.IsEnd(); (void)ml.NextItem())
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
raw_notify(player, tprintf(T("There are %d messages in the mail spool."), count));
|
|
return;
|
|
}
|
|
else if (full == 1)
|
|
{
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (Cleared(mp))
|
|
{
|
|
fc++;
|
|
}
|
|
else if (Read(mp))
|
|
{
|
|
fr++;
|
|
}
|
|
else
|
|
{
|
|
fu++;
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: There are %d msgs in the mail spool, %d unread, %d cleared."),
|
|
fc + fr + fu, fu, fc));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (Cleared(mp))
|
|
{
|
|
fc++;
|
|
cchars += MessageFetchSize(mp->number) + 1;
|
|
}
|
|
else if (Read(mp))
|
|
{
|
|
fr++;
|
|
fchars += MessageFetchSize(mp->number) + 1;
|
|
}
|
|
else
|
|
{
|
|
fu++;
|
|
tchars += MessageFetchSize(mp->number) + 1;
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, tprintf(T("MAIL: There are %d old msgs in the mail spool, totalling %d characters."), fr, fchars));
|
|
raw_notify(player, tprintf(T("MAIL: There are %d new msgs in the mail spool, totalling %d characters."), fu, tchars));
|
|
raw_notify(player, tprintf(T("MAIL: There are %d cleared msgs in the mail spool, totalling %d characters."), fc, cchars));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// individual stats
|
|
//
|
|
if (full == 0)
|
|
{
|
|
// Just count the number of messages.
|
|
//
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (mp->from == target)
|
|
{
|
|
fr++;
|
|
}
|
|
if (mp->to == target)
|
|
{
|
|
tr++;
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, tprintf(T("%s sent %d messages."), Moniker(target), fr));
|
|
raw_notify(player, tprintf(T("%s has %d messages."), Moniker(target), tr));
|
|
return;
|
|
}
|
|
|
|
// More detailed message count.
|
|
//
|
|
UTF8 last[50];
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (mp->from == target)
|
|
{
|
|
if (Cleared(mp))
|
|
{
|
|
fc++;
|
|
}
|
|
else if (Read(mp))
|
|
{
|
|
fr++;
|
|
}
|
|
else
|
|
{
|
|
fu++;
|
|
}
|
|
if (full == 2)
|
|
{
|
|
fchars += MessageFetchSize(mp->number) + 1;
|
|
}
|
|
}
|
|
if (mp->to == target)
|
|
{
|
|
if (!tr && !tu)
|
|
{
|
|
mux_strncpy(last, mp->time, sizeof(last)-1);
|
|
}
|
|
if (Cleared(mp))
|
|
{
|
|
tc++;
|
|
}
|
|
else if (Read(mp))
|
|
{
|
|
tr++;
|
|
}
|
|
else
|
|
{
|
|
tu++;
|
|
}
|
|
if (full == 2)
|
|
{
|
|
tchars += MessageFetchSize(mp->number) + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
raw_notify(player, tprintf(T("Mail statistics for %s:"), Moniker(target)));
|
|
|
|
if (full == 1)
|
|
{
|
|
raw_notify(player, tprintf(T("%d messages sent, %d unread, %d cleared."),
|
|
fc + fr + fu, fu, fc));
|
|
raw_notify(player, tprintf(T("%d messages received, %d unread, %d cleared."),
|
|
tc + tr + tu, tu, tc));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("%d messages sent, %d unread, %d cleared, totalling %d characters."),
|
|
fc + fr + fu, fu, fc, fchars));
|
|
raw_notify(player,
|
|
tprintf(T("%d messages received, %d unread, %d cleared, totalling %d characters."),
|
|
tc + tr + tu, tu, tc, tchars));
|
|
}
|
|
|
|
if (tc + tr + tu > 0)
|
|
{
|
|
raw_notify(player, tprintf(T("Last is dated %s"), last));
|
|
}
|
|
}
|
|
|
|
/*-------------------------------------------------------------------------*
|
|
* Main mail routine for @mail w/o a switch
|
|
*-------------------------------------------------------------------------*/
|
|
|
|
static void do_mail_stub(dbref player, UTF8 *arg1, UTF8 *arg2)
|
|
{
|
|
if (!arg1 || !*arg1)
|
|
{
|
|
if (arg2 && *arg2)
|
|
{
|
|
raw_notify(player, T("MAIL: Invalid mail command."));
|
|
return;
|
|
}
|
|
|
|
// Just the "@mail" command.
|
|
//
|
|
do_mail_list(player, arg1, nullptr, true);
|
|
return;
|
|
}
|
|
|
|
// purge a player's mailbox
|
|
//
|
|
if (!mux_stricmp(arg1, T("purge")))
|
|
{
|
|
do_mail_purge(player);
|
|
return;
|
|
}
|
|
|
|
// clear message
|
|
//
|
|
if (!mux_stricmp(arg1, T("clear")))
|
|
{
|
|
do_mail_clear(player, arg2);
|
|
return;
|
|
}
|
|
if (!mux_stricmp(arg1, T("unclear")))
|
|
{
|
|
do_mail_unclear(player, arg2);
|
|
return;
|
|
}
|
|
if (arg2 && *arg2)
|
|
{
|
|
// Sending mail
|
|
//
|
|
do_expmail_start(player, arg1, arg2);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Must be reading or listing mail - no arg2
|
|
//
|
|
if ( mux_isdigit(*arg1)
|
|
&& !strchr(reinterpret_cast<char *>(arg1), '-'))
|
|
{
|
|
do_mail_read(player, arg1, nullptr);
|
|
}
|
|
else
|
|
{
|
|
do_mail_list(player, arg1, nullptr, true);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void malias_write(FILE *fp)
|
|
{
|
|
int i, j;
|
|
malias_t *m;
|
|
|
|
putref(fp, ma_top);
|
|
for (i = 0; i < ma_top; i++)
|
|
{
|
|
m = malias[i];
|
|
mux_fprintf(fp, T("%d %d\n"), m->owner, static_cast<int>(m->list.size()));
|
|
mux_fprintf(fp, T("N:%s\n"), m->name);
|
|
mux_fprintf(fp, T("D:%s\n"), m->desc);
|
|
for (size_t j = 0; j < m->list.size(); j++)
|
|
{
|
|
putref(fp, m->list[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void save_malias(FILE *fp)
|
|
{
|
|
mux_fprintf(fp, T("*** Begin MALIAS ***\n"));
|
|
malias_write(fp);
|
|
}
|
|
|
|
int dump_mail(FILE *fp)
|
|
{
|
|
dbref thing;
|
|
int count = 0, i;
|
|
|
|
// Write out version number
|
|
//
|
|
mux_fprintf(fp, T("+V6\n"));
|
|
putref(fp, mudstate.mail_db_top);
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
if (isPlayer(thing))
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
putref(fp, mp->to);
|
|
putref(fp, mp->from);
|
|
putref(fp, mp->number);
|
|
putstring(fp, mp->tolist);
|
|
putstring(fp, mp->time);
|
|
putstring(fp, mp->subject);
|
|
putref(fp, mp->read);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
mux_fprintf(fp, T("*** END OF DUMP ***\n"));
|
|
|
|
// Add the db of mail messages
|
|
//
|
|
for (i = 0; i < mudstate.mail_db_top; i++)
|
|
{
|
|
if (0 < mail_list[i].m_nRefs)
|
|
{
|
|
putref(fp, i);
|
|
putstring(fp, MessageFetch(i));
|
|
}
|
|
}
|
|
mux_fprintf(fp, T("+++ END OF DUMP +++\n"));
|
|
save_malias(fp);
|
|
|
|
return count;
|
|
}
|
|
|
|
static void malias_read(FILE *fp, bool bConvert);
|
|
|
|
static void load_mail_V6(FILE *fp)
|
|
{
|
|
int mail_top = getref(fp);
|
|
mail_db_grow(mail_top + 1);
|
|
|
|
size_t nBuffer;
|
|
UTF8 *pBuffer;
|
|
UTF8 nbuf1[200];
|
|
UTF8 *p = reinterpret_cast<UTF8 *>(fgets(reinterpret_cast<char *>(nbuf1), sizeof(nbuf1), fp));
|
|
while ( nullptr != p
|
|
&& strncmp(reinterpret_cast<char *>(nbuf1), "***", 3) != 0)
|
|
{
|
|
struct mail *mp = nullptr;
|
|
try
|
|
{
|
|
mp = new struct mail;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == mp)
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "MAIL");
|
|
log_text(T("Out of memory."));
|
|
ENDLOG;
|
|
return;
|
|
}
|
|
|
|
mp->to = mux_atol(nbuf1);
|
|
mp->from = getref(fp);
|
|
|
|
mp->number = getref(fp);
|
|
MessageReferenceInc(mp->number);
|
|
|
|
pBuffer = reinterpret_cast<UTF8 *>(getstring_noalloc(fp, true, &nBuffer));
|
|
mp->tolist = StringCloneLen(pBuffer, nBuffer);
|
|
pBuffer = reinterpret_cast<UTF8 *>(getstring_noalloc(fp, true, &nBuffer));
|
|
mp->time = StringCloneLen(pBuffer, nBuffer);
|
|
pBuffer = reinterpret_cast<UTF8 *>(getstring_noalloc(fp, true, &nBuffer));
|
|
mp->subject = StringCloneLen(pBuffer, nBuffer);
|
|
mp->read = getref(fp);
|
|
mp->sqlite_id = -1;
|
|
|
|
MailList ml(mp->to);
|
|
ml.AppendItem(mp);
|
|
|
|
p = reinterpret_cast<UTF8 *>(fgets(reinterpret_cast<char *>(nbuf1), sizeof(nbuf1), fp));
|
|
}
|
|
|
|
p = reinterpret_cast<UTF8 *>(fgets(reinterpret_cast<char *>(nbuf1), sizeof(nbuf1), fp));
|
|
while ( nullptr != p
|
|
&& strncmp(reinterpret_cast<char *>(nbuf1), "+++", 3) != 0)
|
|
{
|
|
int number = mux_atol(nbuf1);
|
|
pBuffer = reinterpret_cast<UTF8 *>(getstring_noalloc(fp, true, &nBuffer));
|
|
new_mail_message(pBuffer, number);
|
|
p = reinterpret_cast<UTF8 *>(fgets(reinterpret_cast<char *>(nbuf1), sizeof(nbuf1), fp));
|
|
}
|
|
|
|
p = reinterpret_cast<UTF8 *>(fgets(reinterpret_cast<char *>(nbuf1), sizeof(nbuf1), fp));
|
|
if ( nullptr != p
|
|
&& strcmp(reinterpret_cast<char *>(nbuf1), "*** Begin MALIAS ***\n") == 0)
|
|
{
|
|
malias_read(fp, false);
|
|
}
|
|
else
|
|
{
|
|
Log.WriteString(T("ERROR: Couldn\xE2\x80\x99t find Begin MALIAS." ENDLINE));
|
|
}
|
|
}
|
|
|
|
static void load_mail_V5(FILE *fp)
|
|
{
|
|
int mail_top = getref(fp);
|
|
mail_db_grow(mail_top + 1);
|
|
|
|
size_t nBufferLatin1;
|
|
char *pBufferLatin1;
|
|
size_t nBufferUnicode;
|
|
UTF8 *pBufferUnicode;
|
|
|
|
char nbuf1[200];
|
|
char *p = fgets(nbuf1, sizeof(nbuf1), fp);
|
|
while ( nullptr != p
|
|
&& strncmp(nbuf1, "***", 3) != 0)
|
|
{
|
|
struct mail *mp = nullptr;
|
|
try
|
|
{
|
|
mp = new struct mail;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == mp)
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "MAIL");
|
|
log_text(T("Out of memory."));
|
|
ENDLOG;
|
|
return;
|
|
}
|
|
|
|
pBufferUnicode = reinterpret_cast<UTF8 *>(nbuf1);
|
|
|
|
mp->to = mux_atol(pBufferUnicode);
|
|
mp->from = getref(fp);
|
|
|
|
mp->number = getref(fp);
|
|
MessageReferenceInc(mp->number);
|
|
|
|
pBufferLatin1 = reinterpret_cast<char *>(getstring_noalloc(fp, true, &nBufferLatin1));
|
|
pBufferUnicode = ConvertToUTF8(pBufferLatin1, &nBufferUnicode);
|
|
mp->tolist = StringCloneLen(pBufferUnicode, nBufferUnicode);
|
|
|
|
pBufferLatin1 = reinterpret_cast<char *>(getstring_noalloc(fp, true, &nBufferLatin1));
|
|
pBufferUnicode = ConvertToUTF8(pBufferLatin1, &nBufferUnicode);
|
|
mp->time = StringCloneLen(pBufferUnicode, nBufferUnicode);
|
|
|
|
pBufferLatin1 = reinterpret_cast<char *>(getstring_noalloc(fp, true, &nBufferLatin1));
|
|
pBufferUnicode = ConvertToUTF8(pBufferLatin1, &nBufferUnicode);
|
|
mp->subject = StringCloneLen(pBufferUnicode, nBufferUnicode);
|
|
|
|
mp->read = getref(fp);
|
|
mp->sqlite_id = -1;
|
|
|
|
MailList ml(mp->to);
|
|
ml.AppendItem(mp);
|
|
|
|
p = fgets(nbuf1, sizeof(nbuf1), fp);
|
|
}
|
|
|
|
p = fgets(nbuf1, sizeof(nbuf1), fp);
|
|
while ( nullptr != p
|
|
&& strncmp(nbuf1, "+++", 3) != 0)
|
|
{
|
|
pBufferUnicode = reinterpret_cast<UTF8 *>(nbuf1);
|
|
int number = mux_atol(pBufferUnicode);
|
|
pBufferLatin1 = reinterpret_cast<char *>(getstring_noalloc(fp, true, &nBufferLatin1));
|
|
pBufferUnicode = ConvertToUTF8(pBufferLatin1, &nBufferUnicode);
|
|
new_mail_message(pBufferUnicode, number);
|
|
p = fgets(nbuf1, sizeof(nbuf1), fp);
|
|
}
|
|
|
|
p = fgets(nbuf1, sizeof(nbuf1), fp);
|
|
if ( nullptr != p
|
|
&& strcmp(nbuf1, "*** Begin MALIAS ***\n") == 0)
|
|
{
|
|
malias_read(fp, true);
|
|
}
|
|
else
|
|
{
|
|
Log.WriteString(T("ERROR: Couldn\xE2\x80\x99t find Begin MALIAS." ENDLINE));
|
|
}
|
|
}
|
|
|
|
// A mail alias description can be any combination of upper-case letters,
|
|
// lower-case letters, digits, blanks, and symbols. ANSI is permitted.
|
|
// Length is limited to SIZEOF_MALIASDESC-1. Visual width is limited to
|
|
// WIDTHOF_MALIASDESC. Case is preserved.
|
|
//
|
|
UTF8 *MakeCanonicalMailAliasDesc
|
|
(
|
|
UTF8 *pMailAliasDesc,
|
|
size_t *pnValidMailAliasDesc,
|
|
bool *pbValidMailAliasDesc,
|
|
size_t *pnVisualWidth
|
|
)
|
|
{
|
|
*pnValidMailAliasDesc = 0;
|
|
*pbValidMailAliasDesc = false;
|
|
*pnVisualWidth = 0;
|
|
if (!pMailAliasDesc)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// Remove all '\r\n\t' from the string.
|
|
// Terminate any ANSI in the string.
|
|
//
|
|
thread_local UTF8 szFittedMailAliasDesc[SIZEOF_MALIASDESC+1];
|
|
mux_field nValidMailAliasDesc = StripTabsAndTruncate
|
|
( pMailAliasDesc,
|
|
szFittedMailAliasDesc,
|
|
SIZEOF_MALIASDESC,
|
|
WIDTHOF_MALIASDESC
|
|
);
|
|
*pnValidMailAliasDesc = nValidMailAliasDesc.m_byte;
|
|
*pbValidMailAliasDesc = true;
|
|
*pnVisualWidth = nValidMailAliasDesc.m_column;
|
|
return szFittedMailAliasDesc;
|
|
}
|
|
|
|
static void malias_read(FILE *fp, bool bConvert)
|
|
{
|
|
int i, j;
|
|
|
|
i = getref(fp);
|
|
if (i <= 0)
|
|
{
|
|
return;
|
|
}
|
|
LBuf buffer = LBuf_Src("malias_read");
|
|
|
|
ma_size = ma_top = i;
|
|
|
|
malias = nullptr;
|
|
try
|
|
{
|
|
malias = new malias_t *[ma_size];
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == malias)
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "MAIL");
|
|
log_text(T("Out of memory."));
|
|
ENDLOG;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ma_top; i++)
|
|
{
|
|
// Format is: "%d %d\n", &(m->owner), &(m->numrecep)
|
|
//
|
|
if (!fgets(reinterpret_cast<char *>(buffer.get()), LBUF_SIZE, fp))
|
|
{
|
|
// We've hit the end of the file. Set the last recognized
|
|
// @malias, and give up.
|
|
//
|
|
STARTLOG(LOG_BUGS, "BUG", "MAIL");
|
|
log_text(T("Unexpected end of file. Mail bag truncated."));
|
|
ENDLOG;
|
|
|
|
ma_top = i;
|
|
return;
|
|
}
|
|
|
|
malias_t *m = nullptr;
|
|
try
|
|
{
|
|
m = new malias_t;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == m)
|
|
{
|
|
STARTLOG(LOG_BUGS, "BUG", "MAIL");
|
|
log_text(T("Out of memory. Mail bag truncated."));
|
|
ENDLOG;
|
|
|
|
ma_top = i;
|
|
return;
|
|
}
|
|
|
|
malias[i] = m;
|
|
|
|
UTF8 *p = reinterpret_cast<UTF8 *>(strchr(reinterpret_cast<char *>(buffer.get()), ' '));
|
|
m->owner = 0;
|
|
int numrecep = 0;
|
|
if (p)
|
|
{
|
|
m->owner = mux_atol(buffer);
|
|
numrecep = mux_atol(p+1);
|
|
}
|
|
|
|
// The format of @malias name is "N:<name>\n".
|
|
//
|
|
size_t nLen = GetLineTrunc(buffer, LBUF_SIZE, fp);
|
|
buffer[nLen-1] = '\0'; // Get rid of trailing '\n'.
|
|
|
|
UTF8 *pBufferUnicode;
|
|
if (bConvert)
|
|
{
|
|
size_t nBufferUnicode;
|
|
pBufferUnicode = ConvertToUTF8(reinterpret_cast<char *>(buffer.get()), &nBufferUnicode);
|
|
}
|
|
else
|
|
{
|
|
pBufferUnicode = buffer;
|
|
}
|
|
|
|
size_t nMailAlias;
|
|
bool bMailAlias;
|
|
UTF8 *pMailAlias = MakeCanonicalMailAlias( pBufferUnicode+2,
|
|
&nMailAlias,
|
|
&bMailAlias);
|
|
if (bMailAlias)
|
|
{
|
|
m->name = StringCloneLen(pMailAlias, nMailAlias);
|
|
}
|
|
else
|
|
{
|
|
m->name = StringCloneLen(T("Invalid"), 7);
|
|
}
|
|
|
|
// The format of the description is "D:<description>\n"
|
|
//
|
|
nLen = GetLineTrunc(buffer, LBUF_SIZE, fp);
|
|
if (bConvert)
|
|
{
|
|
size_t nBufferUnicode;
|
|
pBufferUnicode = ConvertToUTF8(reinterpret_cast<char *>(buffer.get()), &nBufferUnicode);
|
|
}
|
|
else
|
|
{
|
|
pBufferUnicode = buffer;
|
|
}
|
|
|
|
size_t nMailAliasDesc;
|
|
bool bMailAliasDesc;
|
|
size_t nVisualWidth;
|
|
UTF8 *pMailAliasDesc = MakeCanonicalMailAliasDesc( pBufferUnicode+2,
|
|
&nMailAliasDesc,
|
|
&bMailAliasDesc,
|
|
&nVisualWidth);
|
|
if (bMailAliasDesc)
|
|
{
|
|
m->desc = StringCloneLen(pMailAliasDesc, nMailAliasDesc);
|
|
m->desc_width = nVisualWidth;
|
|
}
|
|
else
|
|
{
|
|
m->desc = StringCloneLen(T("Invalid Desc"), 12);
|
|
m->desc_width = 12;
|
|
}
|
|
|
|
if (numrecep > 0)
|
|
{
|
|
m->list.reserve(numrecep);
|
|
for (j = 0; j < numrecep; j++)
|
|
{
|
|
int k = getref(fp);
|
|
m->list.push_back(k);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void load_malias(FILE *fp, bool bConvert)
|
|
{
|
|
UTF8 buffer[200];
|
|
|
|
getref(fp);
|
|
if ( fgets(reinterpret_cast<char *>(buffer), sizeof(buffer), fp)
|
|
&& strcmp(reinterpret_cast<char *>(buffer), "*** Begin MALIAS ***\n") == 0)
|
|
{
|
|
malias_read(fp, bConvert);
|
|
}
|
|
else
|
|
{
|
|
Log.WriteString(T("ERROR: Couldn\xE2\x80\x99t find Begin MALIAS." ENDLINE));
|
|
return;
|
|
}
|
|
}
|
|
|
|
void load_mail(FILE *fp)
|
|
{
|
|
UTF8 nbuf1[8];
|
|
|
|
// Read the version number.
|
|
//
|
|
if (!fgets(reinterpret_cast<char *>(nbuf1), sizeof(nbuf1), fp))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (strncmp(reinterpret_cast<char *>(nbuf1), "+V6", 3) == 0)
|
|
{
|
|
// Started v6 on 2007-MAR-13.
|
|
//
|
|
load_mail_V6(fp);
|
|
}
|
|
else if (strncmp(reinterpret_cast<char *>(nbuf1), "+V5", 3) == 0)
|
|
{
|
|
load_mail_V5(fp);
|
|
}
|
|
}
|
|
|
|
void check_mail_expiration(void)
|
|
{
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->ExpireMail();
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Negative values for expirations never expire.
|
|
//
|
|
if (0 > mudconf.mail_expiration)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dbref thing;
|
|
int expire_secs = mudconf.mail_expiration * 86400;
|
|
|
|
CLinearTimeAbsolute ltaNow;
|
|
ltaNow.GetLocal();
|
|
|
|
CLinearTimeAbsolute ltaMail;
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
if (No_Mail_Expire(thing))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (M_Safe(mp))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const UTF8 *pMailTimeStr = mp->time;
|
|
if (!ltaMail.SetString(pMailTimeStr))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CLinearTimeDelta ltd(ltaMail, ltaNow);
|
|
if (ltd.ReturnSeconds() <= expire_secs)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Delete this one.
|
|
//
|
|
ml.RemoveItem();
|
|
}
|
|
}
|
|
}
|
|
|
|
void check_mail(dbref player, int folder, bool silent)
|
|
{
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->CheckMail(player, folder, silent);
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check for new @mail
|
|
//
|
|
int rc; // Read messages.
|
|
int uc; // Unread messages.
|
|
int cc; // Cleared messages.
|
|
int gc; // urgent messages.
|
|
|
|
// Just count messages
|
|
//
|
|
count_mail(player, folder, &rc, &uc, &cc);
|
|
urgent_mail(player, folder, &gc);
|
|
#ifdef MAIL_ALL_FOLDERS
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared).\r\n"),
|
|
rc + uc, folder, get_folder_name(player, folder), uc, cc));
|
|
#else // MAIL_ALL_FOLDERS
|
|
if (rc + uc > 0)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: %d messages in folder %d [%s] (%d unread, %d cleared)."), rc + uc, folder, get_folder_name(player, folder), uc, cc));
|
|
}
|
|
else if (!silent)
|
|
{
|
|
raw_notify(player, tprintf(T("\r\nMAIL: You have no mail.\r\n")));
|
|
}
|
|
if (gc > 0)
|
|
{
|
|
raw_notify(player, tprintf(T("URGENT MAIL: You have %d urgent messages in folder %d [%s]."), gc, folder, get_folder_name(player, folder)));
|
|
}
|
|
#endif // MAIL_ALL_FOLDERS
|
|
}
|
|
|
|
static void do_malias_send
|
|
(
|
|
dbref player,
|
|
UTF8 *tolist,
|
|
UTF8 *listto,
|
|
UTF8 *senderlistto,
|
|
UTF8 *subject,
|
|
int number,
|
|
mail_flag flags,
|
|
bool silent
|
|
)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, tolist, &nResult);
|
|
if (nResult == GMA_INVALIDFORM)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: I can\xE2\x80\x99t figure out from \xE2\x80\x98%s\xE2\x80\x99 who you want to mail to."), tolist));
|
|
return;
|
|
}
|
|
else if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), tolist));
|
|
return;
|
|
}
|
|
|
|
// Parse the player list.
|
|
//
|
|
dbref vic;
|
|
int k;
|
|
for (k = 0; k < static_cast<int>(m->list.size()); k++)
|
|
{
|
|
vic = m->list[k];
|
|
|
|
if (isPlayer(vic))
|
|
{
|
|
send_mail(player, m->list[k], (m->list[k] == player) ? senderlistto : listto, subject, number, flags, silent);
|
|
}
|
|
else
|
|
{
|
|
// Complain about it.
|
|
//
|
|
UTF8 *pMail = tprintf(T("Alias Error: Bad Player %d for %s"), vic, tolist);
|
|
int iMail = add_mail_message(player, pMail);
|
|
if (iMail != NOTHING)
|
|
{
|
|
send_mail(GOD, GOD, listto, subject, iMail, 0, silent);
|
|
MessageReferenceDec(iMail);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void do_malias_create(dbref player, UTF8 *alias, UTF8 *tolist)
|
|
{
|
|
malias_t **nm;
|
|
int nResult;
|
|
get_malias(player, alias, &nResult);
|
|
|
|
if (nResult == GMA_INVALIDFORM)
|
|
{
|
|
raw_notify(player, T("MAIL: What alias do you want to create?."));
|
|
return;
|
|
}
|
|
else if (nResult == GMA_FOUND)
|
|
{
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: Mail Alias \xE2\x80\x98%s\xE2\x80\x99 already exists."), alias));
|
|
return;
|
|
}
|
|
|
|
malias_t *pt = nullptr;
|
|
try
|
|
{
|
|
pt = new malias_t;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == pt)
|
|
{
|
|
raw_notify(player, T("MAIL: Out of memory."));
|
|
return;
|
|
}
|
|
|
|
int i = 0;
|
|
if (!ma_size)
|
|
{
|
|
ma_size = MA_INC;
|
|
malias = nullptr;
|
|
try
|
|
{
|
|
malias = new malias_t *[ma_size];
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == malias)
|
|
{
|
|
raw_notify(player, T("MAIL: Out of memory."));
|
|
delete pt;
|
|
return;
|
|
}
|
|
}
|
|
else if (ma_top >= ma_size)
|
|
{
|
|
ma_size += MA_INC;
|
|
nm = nullptr;
|
|
try
|
|
{
|
|
nm = new malias_t *[ma_size];
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == nm)
|
|
{
|
|
raw_notify(player, T("MAIL: Out of memory."));
|
|
delete pt;
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < ma_top; i++)
|
|
{
|
|
nm[i] = malias[i];
|
|
}
|
|
|
|
delete [] malias;
|
|
malias = nm;
|
|
}
|
|
|
|
malias[ma_top] = pt;
|
|
|
|
// Parse the player list.
|
|
//
|
|
UTF8 *head = tolist;
|
|
UTF8 *tail, spot;
|
|
UTF8 *buff;
|
|
dbref target;
|
|
i = 0;
|
|
while ( head
|
|
&& *head
|
|
)
|
|
{
|
|
while (*head == ' ')
|
|
{
|
|
head++;
|
|
}
|
|
tail = head;
|
|
while ( *tail
|
|
&& *tail != ' ')
|
|
{
|
|
if (*tail == '"')
|
|
{
|
|
head++;
|
|
tail++;
|
|
while ( *tail
|
|
&& *tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
if (*tail)
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
tail--;
|
|
if (*tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
spot = *tail;
|
|
*tail = '\0';
|
|
|
|
// Now locate a target.
|
|
//
|
|
if (!mux_stricmp(head, T("me")))
|
|
{
|
|
target = player;
|
|
}
|
|
else if (*head == '#')
|
|
{
|
|
target = mux_atol(head + 1);
|
|
}
|
|
else
|
|
{
|
|
target = lookup_player(player, head, true);
|
|
}
|
|
|
|
if ( !Good_obj(target)
|
|
|| !isPlayer(target))
|
|
{
|
|
raw_notify(player, T("MAIL: No such player."));
|
|
}
|
|
else
|
|
{
|
|
buff = unparse_object(player, target, false);
|
|
raw_notify(player,
|
|
tprintf(T("MAIL: %s added to alias %s"), buff, alias));
|
|
malias[ma_top]->list.push_back(target);
|
|
i++;
|
|
free_lbuf(buff);
|
|
}
|
|
|
|
// Get the next recip.
|
|
//
|
|
*tail = spot;
|
|
head = tail;
|
|
if (*head == '"')
|
|
{
|
|
head++;
|
|
}
|
|
}
|
|
size_t nValidMailAlias;
|
|
bool bValidMailAlias;
|
|
UTF8 *pValidMailAlias = MakeCanonicalMailAlias
|
|
( alias+1,
|
|
&nValidMailAlias,
|
|
&bValidMailAlias
|
|
);
|
|
|
|
if (!bValidMailAlias)
|
|
{
|
|
raw_notify(player, T("MAIL: Invalid mail alias."));
|
|
return;
|
|
}
|
|
|
|
// The Mail Alias Description is a superset of the Mail Alias,
|
|
// so, the following code is not necessary unless the specification
|
|
// of the Mail Alias Description becomes more restrictive at some
|
|
// future time.
|
|
//
|
|
UTF8 *pValidMailAliasDesc = pValidMailAlias;
|
|
size_t nValidMailAliasDesc = nValidMailAlias;
|
|
|
|
malias[ma_top]->name = StringCloneLen(pValidMailAlias, nValidMailAlias);
|
|
malias[ma_top]->owner = player;
|
|
malias[ma_top]->desc = StringCloneLen(pValidMailAliasDesc, nValidMailAliasDesc);
|
|
malias[ma_top]->desc_width = nValidMailAliasDesc;
|
|
ma_top++;
|
|
sqlite_wt_sync_all_aliases();
|
|
|
|
raw_notify(player, tprintf(T("MAIL: Alias set \xE2\x80\x98%s\xE2\x80\x99 defined."), alias));
|
|
}
|
|
|
|
static void do_malias_list(dbref player, UTF8 *alias)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
return;
|
|
}
|
|
if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
if (!ExpMail(player) && (player != m->owner) && !(God(m->owner)))
|
|
{
|
|
raw_notify(player, T("MAIL: Permission denied."));
|
|
return;
|
|
}
|
|
UTF8 *buff = alloc_lbuf("do_malias_list");
|
|
UTF8 *bp = buff;
|
|
|
|
safe_tprintf_str(buff, &bp, T("MAIL: Alias *%s: "), m->name);
|
|
for (int i = static_cast<int>(m->list.size()) - 1; i > -1; i--)
|
|
{
|
|
const UTF8 *p = Moniker(m->list[i]);
|
|
if (strchr(reinterpret_cast<const char *>(p), ' '))
|
|
{
|
|
safe_chr('"', buff, &bp);
|
|
safe_str(p, buff, &bp);
|
|
safe_chr('"', buff, &bp);
|
|
}
|
|
else
|
|
{
|
|
safe_str(p, buff, &bp);
|
|
}
|
|
safe_chr(' ', buff, &bp);
|
|
}
|
|
*bp = '\0';
|
|
|
|
raw_notify(player, buff);
|
|
free_lbuf(buff);
|
|
}
|
|
|
|
static const UTF8 *Spaces(size_t n)
|
|
{
|
|
static const UTF8 buffer[42] = " ";
|
|
|
|
if (n < sizeof(buffer)-1)
|
|
{
|
|
return buffer + (sizeof(buffer)-1) - n;
|
|
}
|
|
else
|
|
{
|
|
return T("");
|
|
}
|
|
}
|
|
|
|
static bool malias_compare(const malias_t &a, const malias_t &b)
|
|
{
|
|
return mux_stricmp(a.name, b.name) < 0;
|
|
}
|
|
|
|
static void do_malias_list_all(dbref player)
|
|
{
|
|
int actual_entries = 0;
|
|
malias_t* alias_array = nullptr;
|
|
try
|
|
{
|
|
alias_array = static_cast<malias_t *>(MEMALLOC(sizeof(malias_t)*ma_top));
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == alias_array)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int i;
|
|
for (i = 0; i < ma_top; i++)
|
|
{
|
|
malias_t *m = malias[i];
|
|
if ( GOD == m->owner
|
|
|| m->owner == player
|
|
|| God(player))
|
|
{
|
|
alias_array[actual_entries].name = m->name;
|
|
alias_array[actual_entries].desc = m->desc;
|
|
alias_array[actual_entries].desc_width = m->desc_width;
|
|
alias_array[actual_entries].owner = m->owner;
|
|
++actual_entries;
|
|
}
|
|
}
|
|
std::sort(alias_array, alias_array + actual_entries, malias_compare);
|
|
|
|
bool notified = false;
|
|
for (i = 0; i < actual_entries; i++)
|
|
{
|
|
malias_t *m = &alias_array[i];
|
|
if (!notified)
|
|
{
|
|
raw_notify(player, T("Name Description Owner"));
|
|
notified = true;
|
|
}
|
|
|
|
const UTF8 *pSpaces = Spaces(40 - m->desc_width);
|
|
|
|
UTF8 *p = tprintf(T("%-12s %s%s %-15.15s"),
|
|
m->name, m->desc, pSpaces, Moniker(m->owner));
|
|
raw_notify(player, p);
|
|
}
|
|
raw_notify(player, T("***** End of Mail Aliases *****"));
|
|
MEMFREE(alias_array);
|
|
}
|
|
|
|
static void do_malias_switch(dbref player, UTF8 *a1, UTF8 *a2)
|
|
{
|
|
if (a1 && *a1)
|
|
{
|
|
if (a2 && *a2)
|
|
{
|
|
do_malias_create(player, a1, a2);
|
|
}
|
|
else
|
|
{
|
|
do_malias_list(player, a1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
do_malias_list_all(player);
|
|
}
|
|
}
|
|
|
|
static void do_mail_cc(dbref player, UTF8 *arg, bool bBlind)
|
|
{
|
|
if (!(Flags2(player) & PLAYER_MAILS))
|
|
{
|
|
raw_notify(player, T("MAIL: No mail message in progress."));
|
|
return;
|
|
}
|
|
if (!arg || !*arg)
|
|
{
|
|
raw_notify(player, T("MAIL: I do not know whom you want to mail."));
|
|
return;
|
|
}
|
|
|
|
UTF8 *tolist = make_numlist(player, arg, bBlind);
|
|
if (!tolist)
|
|
{
|
|
return;
|
|
}
|
|
UTF8 *fulllist = alloc_lbuf("do_mail_cc");
|
|
UTF8 *bp = fulllist;
|
|
|
|
safe_str(tolist, fulllist, &bp);
|
|
const UTF8 *pPlayerMailTo = atr_get_raw(player, A_MAILTO);
|
|
if (pPlayerMailTo)
|
|
{
|
|
safe_chr(' ', fulllist, &bp);
|
|
safe_str(pPlayerMailTo, fulllist, &bp);
|
|
}
|
|
*bp = '\0';
|
|
|
|
atr_add_raw(player, A_MAILTO, fulllist);
|
|
UTF8 *names = make_namelist(player, fulllist);
|
|
raw_notify(player, tprintf(T("MAIL: You are sending mail to \xE2\x80\x98%s\xE2\x80\x99."), names));
|
|
free_lbuf(names);
|
|
free_lbuf(tolist);
|
|
free_lbuf(fulllist);
|
|
}
|
|
|
|
static void mail_to_list(dbref player, UTF8 *list, UTF8 *subject, UTF8 *message, int flags, bool silent)
|
|
{
|
|
if (!list)
|
|
{
|
|
return;
|
|
}
|
|
if (!*list)
|
|
{
|
|
free_lbuf(list);
|
|
return;
|
|
}
|
|
|
|
// Construct a tolist which excludes all the Blind Carbon Copy (BCC)
|
|
// recipients, and a senderlist which includes all recipients (so the
|
|
// sender's copy retains the full BCC information).
|
|
//
|
|
UTF8 *tolist = alloc_lbuf("mail_to_list");
|
|
UTF8 *p = tolist;
|
|
UTF8 *senderlist = alloc_lbuf("mail_to_list.senderlist");
|
|
UTF8 *sp = senderlist;
|
|
UTF8 *tail;
|
|
UTF8 *head = list;
|
|
while (*head)
|
|
{
|
|
while (*head == ' ')
|
|
{
|
|
head++;
|
|
}
|
|
|
|
tail = head;
|
|
while ( *tail
|
|
&& *tail != ' ')
|
|
{
|
|
if (*tail == '"')
|
|
{
|
|
head++;
|
|
tail++;
|
|
while ( *tail
|
|
&& *tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
if (*tail)
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
tail--;
|
|
if (*tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
|
|
// Append every token to senderlist (including BCC entries).
|
|
//
|
|
if (sp != senderlist)
|
|
{
|
|
safe_chr(' ', senderlist, &sp);
|
|
}
|
|
memcpy(sp, head, tail - head);
|
|
sp += tail - head;
|
|
|
|
if (*head != '!')
|
|
{
|
|
if (p != tolist)
|
|
{
|
|
safe_chr(' ', tolist, &p);
|
|
}
|
|
memcpy(p, head, tail-head);
|
|
p += tail-head;
|
|
}
|
|
|
|
// Get the next recipient.
|
|
//
|
|
head = tail;
|
|
if (*head == '"')
|
|
{
|
|
head++;
|
|
}
|
|
}
|
|
*p = '\0';
|
|
*sp = '\0';
|
|
|
|
int number = add_mail_message(player, message);
|
|
if (number != NOTHING)
|
|
{
|
|
UTF8 spot;
|
|
head = list;
|
|
while (*head)
|
|
{
|
|
while (' ' == *head)
|
|
{
|
|
head++;
|
|
}
|
|
|
|
tail = head;
|
|
while ( *tail
|
|
&& *tail != ' ')
|
|
{
|
|
if (*tail == '"')
|
|
{
|
|
head++;
|
|
tail++;
|
|
while ( *tail
|
|
&& *tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
if (*tail)
|
|
{
|
|
tail++;
|
|
}
|
|
}
|
|
tail--;
|
|
if (*tail != '"')
|
|
{
|
|
tail++;
|
|
}
|
|
spot = *tail;
|
|
*tail = '\0';
|
|
|
|
if (*head == '!')
|
|
{
|
|
head++;
|
|
}
|
|
|
|
if (*head == '*')
|
|
{
|
|
do_malias_send(player, head, tolist, senderlist, subject, number, flags, silent);
|
|
}
|
|
else
|
|
{
|
|
dbref target = mux_atol(head);
|
|
if ( Good_obj(target)
|
|
&& isPlayer(target))
|
|
{
|
|
send_mail(player, target, (target == player) ? senderlist : tolist, subject, number, flags, silent);
|
|
}
|
|
}
|
|
|
|
// Get the next recipient.
|
|
//
|
|
*tail = spot;
|
|
head = tail;
|
|
if (*head == '"')
|
|
{
|
|
head++;
|
|
}
|
|
}
|
|
MessageReferenceDec(number);
|
|
}
|
|
free_lbuf(senderlist);
|
|
free_lbuf(tolist);
|
|
free_lbuf(list);
|
|
}
|
|
|
|
static void do_mail_quick(dbref player, UTF8 *arg1, UTF8 *arg2)
|
|
{
|
|
if (!arg1 || !*arg1)
|
|
{
|
|
raw_notify(player, T("MAIL: I don\xE2\x80\x99t know who you want to mail."));
|
|
return;
|
|
}
|
|
if (!arg2 || !*arg2)
|
|
{
|
|
raw_notify(player, T("MAIL: No message."));
|
|
return;
|
|
}
|
|
if (Flags2(player) & PLAYER_MAILS)
|
|
{
|
|
raw_notify(player, T("MAIL: Mail message already in progress."));
|
|
return;
|
|
}
|
|
if ( !Wizard(player)
|
|
&& ThrottleMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Too much @mail sent recently."));
|
|
return;
|
|
}
|
|
UTF8 *bufDest = alloc_lbuf("do_mail_quick");
|
|
UTF8 *bpSubject = bufDest;
|
|
|
|
mux_strncpy(bpSubject, arg1, LBUF_SIZE-1);
|
|
parse_to(&bpSubject, '/', 1);
|
|
|
|
if (!bpSubject)
|
|
{
|
|
raw_notify(player, T("MAIL: No subject."));
|
|
free_lbuf(bufDest);
|
|
return;
|
|
}
|
|
|
|
mail_to_list(player, make_numlist(player, bufDest, false), bpSubject, arg2, 0, false);
|
|
free_lbuf(bufDest);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// do_mail_send_softcode: Entry point for mailsend() softcode function.
|
|
//
|
|
// Returns: 1 on success, or a static error string on failure.
|
|
//
|
|
const UTF8 *do_mail_send_softcode(dbref player, UTF8 *recipients, UTF8 *subject, UTF8 *message)
|
|
{
|
|
// Resolve to the owning player — softcode may run on a non-player object.
|
|
//
|
|
player = Owner(player);
|
|
if (!Good_obj(player) || !isPlayer(player))
|
|
{
|
|
return T("#-1 NOT A PLAYER");
|
|
}
|
|
if (!recipients || !*recipients)
|
|
{
|
|
return T("#-1 NO RECIPIENTS");
|
|
}
|
|
if (!subject || !*subject)
|
|
{
|
|
return T("#-1 NO SUBJECT");
|
|
}
|
|
if (!message || !*message)
|
|
{
|
|
return T("#-1 NO MESSAGE");
|
|
}
|
|
if (Flags2(player) & PLAYER_MAILS)
|
|
{
|
|
return T("#-1 MAIL ALREADY IN PROGRESS");
|
|
}
|
|
if ( !Wizard(player)
|
|
&& ThrottleMail(player))
|
|
{
|
|
return T("#-1 TOO MUCH MAIL SENT");
|
|
}
|
|
|
|
UTF8 *numlist = make_numlist(player, recipients, false);
|
|
if (!numlist || !*numlist)
|
|
{
|
|
if (numlist)
|
|
{
|
|
free_lbuf(numlist);
|
|
}
|
|
return T("#-1 NO VALID RECIPIENTS");
|
|
}
|
|
|
|
mail_to_list(player, numlist, subject, message, 0, true);
|
|
return nullptr;
|
|
}
|
|
|
|
static void do_expmail_stop(dbref player, int flags)
|
|
{
|
|
if ((Flags2(player) & PLAYER_MAILS) != PLAYER_MAILS)
|
|
{
|
|
raw_notify(player, T("MAIL: No message started."));
|
|
return;
|
|
}
|
|
|
|
dbref aowner;
|
|
dbref aflags;
|
|
UTF8 *tolist = atr_get("do_expmail_stop.3854", player, A_MAILTO, & aowner, &aflags);
|
|
if (*tolist == '\0')
|
|
{
|
|
raw_notify(player, T("MAIL: No recipients."));
|
|
free_lbuf(tolist);
|
|
}
|
|
else
|
|
{
|
|
UTF8 *pMailMsg = atr_get("do_expmail_stop.3862", player, A_MAILMSG, &aowner, &aflags);
|
|
if (*pMailMsg == '\0')
|
|
{
|
|
raw_notify(player, T("MAIL: The body of this message is empty. Use - to add to the message."));
|
|
free_lbuf(tolist);
|
|
}
|
|
else
|
|
{
|
|
UTF8 *mailsub = atr_get("do_expmail_stop.3870", player, A_MAILSUB, &aowner, &aflags);
|
|
UTF8 *mailflags = atr_get("do_expmail_stop.3871", player, A_MAILFLAGS, &aowner, &aflags);
|
|
mail_to_list(player, tolist, mailsub, pMailMsg, flags | mux_atol(mailflags), false);
|
|
free_lbuf(mailflags);
|
|
free_lbuf(mailsub);
|
|
|
|
Flags2(player) &= ~PLAYER_MAILS;
|
|
}
|
|
free_lbuf(pMailMsg);
|
|
}
|
|
}
|
|
|
|
static void do_expmail_abort(dbref player)
|
|
{
|
|
Flags2(player) &= ~PLAYER_MAILS;
|
|
raw_notify(player, T("MAIL: Message aborted."));
|
|
}
|
|
|
|
void do_prepend(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *text, const UTF8 *cargs[], int ncargs)
|
|
{
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (Flags2(executor) & PLAYER_MAILS)
|
|
{
|
|
if ( !text
|
|
|| !*text)
|
|
{
|
|
raw_notify(executor, T("No text prepended."));
|
|
return;
|
|
}
|
|
|
|
UTF8 *bufText = alloc_lbuf("do_prepend");
|
|
UTF8 *bpText = bufText;
|
|
mux_exec(text+1, LBUF_SIZE-1, bufText, &bpText, executor, caller, enactor,
|
|
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, nullptr, 0);
|
|
*bpText = '\0';
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
|
|
UTF8 *oldmsg = atr_get("do_prepend.3915", executor, A_MAILMSG, &aowner, &aflags);
|
|
if (*oldmsg)
|
|
{
|
|
UTF8 *newmsg = alloc_lbuf("do_prepend");
|
|
UTF8 *bp = newmsg;
|
|
safe_str(bufText, newmsg, &bp);
|
|
safe_chr(' ', newmsg, &bp);
|
|
safe_str(oldmsg, newmsg, &bp);
|
|
*bp = '\0';
|
|
atr_add_raw(executor, A_MAILMSG, newmsg);
|
|
free_lbuf(newmsg);
|
|
}
|
|
else
|
|
{
|
|
atr_add_raw(executor, A_MAILMSG, bufText);
|
|
}
|
|
|
|
free_lbuf(bufText);
|
|
free_lbuf(oldmsg);
|
|
size_t nLen;
|
|
|
|
atr_get_raw_LEN(executor, A_MAILMSG, &nLen);
|
|
raw_notify(executor, tprintf(T("%d/%d characters prepended."), nLen, LBUF_SIZE-1));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("MAIL: No message in progress."));
|
|
}
|
|
}
|
|
|
|
void do_postpend(dbref executor, dbref caller, dbref enactor, int eval, int key, UTF8 *text, const UTF8 *cargs[], int ncargs)
|
|
{
|
|
UNUSED_PARAMETER(key);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if ( text[1] == '-'
|
|
&& text[2] == '\0')
|
|
{
|
|
do_expmail_stop(executor, 0);
|
|
return;
|
|
}
|
|
|
|
if (Flags2(executor) & PLAYER_MAILS)
|
|
{
|
|
if ( !text
|
|
|| !*text)
|
|
{
|
|
raw_notify(executor, T("No text added."));
|
|
return;
|
|
}
|
|
|
|
UTF8 *bufText = alloc_lbuf("do_prepend");
|
|
UTF8 *bpText = bufText;
|
|
mux_exec(text+1, LBUF_SIZE-1, bufText, &bpText, executor, caller, enactor,
|
|
eval|EV_STRIP_CURLY|EV_FCHECK|EV_EVAL, nullptr, 0);
|
|
*bpText = '\0';
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
|
|
UTF8 *oldmsg = atr_get("do_postpend.3978", executor, A_MAILMSG, &aowner, &aflags);
|
|
if (*oldmsg)
|
|
{
|
|
UTF8 *newmsg = alloc_lbuf("do_postpend");
|
|
UTF8 *bp = newmsg;
|
|
safe_str(oldmsg, newmsg, &bp);
|
|
safe_chr(' ', newmsg, &bp);
|
|
safe_str(bufText, newmsg, &bp);
|
|
*bp = '\0';
|
|
atr_add_raw(executor, A_MAILMSG, newmsg);
|
|
free_lbuf(newmsg);
|
|
}
|
|
else
|
|
{
|
|
atr_add_raw(executor, A_MAILMSG, bufText);
|
|
}
|
|
|
|
free_lbuf(bufText);
|
|
free_lbuf(oldmsg);
|
|
size_t nLen;
|
|
|
|
atr_get_raw_LEN(executor, A_MAILMSG, &nLen);
|
|
raw_notify(executor, tprintf(T("%d/%d characters added."), nLen, LBUF_SIZE-1));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(executor, T("MAIL: No message in progress."));
|
|
}
|
|
}
|
|
|
|
static void do_edit_msg(dbref player, UTF8 *from, UTF8 *to)
|
|
{
|
|
if (Flags2(player) & PLAYER_MAILS)
|
|
{
|
|
dbref aowner;
|
|
int aflags;
|
|
UTF8 *msg = atr_get("do_edit_msg.4014", player, A_MAILMSG, &aowner, &aflags);
|
|
UTF8 *result = replace_string(from, to, msg);
|
|
atr_add(player, A_MAILMSG, result, aowner, aflags);
|
|
raw_notify(player, T("Text edited."));
|
|
free_lbuf(result);
|
|
free_lbuf(msg);
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("MAIL: No message in progress."));
|
|
}
|
|
}
|
|
|
|
static void do_mail_proof(dbref player)
|
|
{
|
|
if (!(Flags2(player) & PLAYER_MAILS))
|
|
{
|
|
raw_notify(player, T("MAIL: No message in progress."));
|
|
return;
|
|
}
|
|
|
|
dbref aowner;
|
|
int aflags;
|
|
|
|
UTF8 *mailto = atr_get("do_mail_proof.4038", player, A_MAILTO, &aowner, &aflags);
|
|
UTF8 *pMailMsg = atr_get("do_mail_proof.4039", player, A_MAILMSG, &aowner, &aflags);
|
|
UTF8 *names = make_namelist(player, mailto);
|
|
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
StripTabsAndTruncate( atr_get_raw(player, A_MAILSUB), szSubjectBuffer,
|
|
MBUF_SIZE-1, 35);
|
|
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
trimmed_name(player, szFromName, 16, 16, 0);
|
|
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, tprintf(T("From: %s Subject: %s\nTo: %s"),
|
|
szFromName, szSubjectBuffer, names));
|
|
raw_notify(player, DASH_LINE);
|
|
raw_notify(player, pMailMsg);
|
|
raw_notify(player, DASH_LINE);
|
|
free_lbuf(pMailMsg);
|
|
free_lbuf(names);
|
|
free_lbuf(mailto);
|
|
}
|
|
|
|
static void do_malias_desc(dbref player, UTF8 *alias, UTF8 *desc)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
return;
|
|
}
|
|
if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
if ( m->owner != GOD
|
|
|| ExpMail(player))
|
|
{
|
|
size_t nValidMailAliasDesc;
|
|
bool bValidMailAliasDesc;
|
|
size_t nVisualWidth;
|
|
UTF8 *pValidMailAliasDesc = MakeCanonicalMailAliasDesc
|
|
( desc,
|
|
&nValidMailAliasDesc,
|
|
&bValidMailAliasDesc,
|
|
&nVisualWidth
|
|
);
|
|
|
|
if (bValidMailAliasDesc)
|
|
{
|
|
MEMFREE(m->desc);
|
|
m->desc = StringCloneLen(pValidMailAliasDesc, nValidMailAliasDesc);
|
|
m->desc_width = nVisualWidth;
|
|
sqlite_wt_sync_all_aliases();
|
|
raw_notify(player, T("MAIL: Description changed."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("MAIL: Description is not valid."));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("MAIL: Permission denied."));
|
|
}
|
|
}
|
|
|
|
static void do_malias_chown(dbref player, UTF8 *alias, UTF8 *owner)
|
|
{
|
|
if (!ExpMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: You cannot do that!"));
|
|
return;
|
|
}
|
|
|
|
int nResult;
|
|
malias_t *m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
return;
|
|
}
|
|
if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
dbref no = lookup_player(player, owner, true);
|
|
if (no == NOTHING)
|
|
{
|
|
raw_notify(player, T("MAIL: I do not see that here."));
|
|
return;
|
|
}
|
|
m->owner = no;
|
|
sqlite_wt_sync_all_aliases();
|
|
raw_notify(player, T("MAIL: Owner changed for alias."));
|
|
}
|
|
|
|
static void do_malias_add(dbref player, UTF8 *alias, UTF8 *person)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
return;
|
|
}
|
|
else if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
dbref thing = NOTHING;
|
|
if (*person == '#')
|
|
{
|
|
thing = parse_dbref(person + 1);
|
|
if (!isPlayer(thing))
|
|
{
|
|
raw_notify(player, T("MAIL: Only players may be added."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (thing == NOTHING)
|
|
{
|
|
thing = lookup_player(player, person, true);
|
|
}
|
|
|
|
if (thing == NOTHING)
|
|
{
|
|
raw_notify(player, T("MAIL: I do not see that person here."));
|
|
return;
|
|
}
|
|
|
|
if ((m->owner == GOD) && !ExpMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Permission denied."));
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < m->list.size(); i++)
|
|
{
|
|
if (m->list[i] == thing)
|
|
{
|
|
raw_notify(player, T("MAIL: That person is already on the list."));
|
|
return;
|
|
}
|
|
}
|
|
|
|
m->list.push_back(thing);
|
|
sqlite_wt_sync_all_aliases();
|
|
raw_notify(player, tprintf(T("MAIL: %s added to %s"), Moniker(thing), m->name));
|
|
}
|
|
|
|
static void do_malias_remove(dbref player, UTF8 *alias, UTF8 *person)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
return;
|
|
}
|
|
if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
if ((m->owner == GOD) && !ExpMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Permission denied."));
|
|
return;
|
|
}
|
|
|
|
dbref thing = NOTHING;
|
|
if (*person == '#')
|
|
{
|
|
thing = parse_dbref(person + 1);
|
|
}
|
|
if (thing == NOTHING)
|
|
{
|
|
thing = lookup_player(player, person, true);
|
|
}
|
|
if (thing == NOTHING)
|
|
{
|
|
raw_notify(player, T("MAIL: I do not see that person here."));
|
|
return;
|
|
}
|
|
|
|
auto it = std::find(m->list.begin(), m->list.end(), thing);
|
|
bool ok = (it != m->list.end());
|
|
|
|
if (ok)
|
|
{
|
|
m->list.erase(it);
|
|
sqlite_wt_sync_all_aliases();
|
|
raw_notify(player, tprintf(T("MAIL: %s removed from alias %s."),
|
|
Moniker(thing), alias));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: %s is not a member of alias %s."),
|
|
Moniker(thing), alias));
|
|
}
|
|
}
|
|
|
|
static void do_malias_rename(dbref player, UTF8 *alias, UTF8 *newname)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, newname, &nResult);
|
|
if (nResult == GMA_FOUND)
|
|
{
|
|
raw_notify(player, T("MAIL: That name already exists!"));
|
|
return;
|
|
}
|
|
if (nResult != GMA_NOTFOUND)
|
|
{
|
|
return;
|
|
}
|
|
m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, T("MAIL: I cannot find that alias!"));
|
|
return;
|
|
}
|
|
if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
if (!ExpMail(player) && !(m->owner == player))
|
|
{
|
|
raw_notify(player, T("MAIL: Permission denied."));
|
|
return;
|
|
}
|
|
|
|
size_t nValidMailAlias;
|
|
bool bValidMailAlias;
|
|
UTF8 *pValidMailAlias = MakeCanonicalMailAlias
|
|
( newname+1,
|
|
&nValidMailAlias,
|
|
&bValidMailAlias
|
|
);
|
|
if (bValidMailAlias)
|
|
{
|
|
MEMFREE(m->name);
|
|
m->name = StringCloneLen(pValidMailAlias, nValidMailAlias);
|
|
sqlite_wt_sync_all_aliases();
|
|
raw_notify(player, T("MAIL: Mailing Alias renamed."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("MAIL: Alias is not valid."));
|
|
}
|
|
}
|
|
|
|
static void do_malias_delete(dbref player, UTF8 *alias)
|
|
{
|
|
int nResult;
|
|
malias_t *m = get_malias(player, alias, &nResult);
|
|
if (nResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
return;
|
|
}
|
|
if (nResult != GMA_FOUND)
|
|
{
|
|
return;
|
|
}
|
|
bool done = false;
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
if (done)
|
|
{
|
|
malias[i] = malias[i + 1];
|
|
}
|
|
else
|
|
{
|
|
if ((m->owner == player) || ExpMail(player))
|
|
{
|
|
if (m == malias[i])
|
|
{
|
|
done = true;
|
|
raw_notify(player, T("MAIL: Alias Deleted."));
|
|
malias[i] = malias[i + 1];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!done)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Alias \xE2\x80\x98%s\xE2\x80\x99 not found."), alias));
|
|
}
|
|
else
|
|
{
|
|
ma_top--;
|
|
sqlite_wt_sync_all_aliases();
|
|
}
|
|
}
|
|
|
|
static void do_malias_adminlist(dbref player)
|
|
{
|
|
if (!ExpMail(player))
|
|
{
|
|
do_malias_list_all(player);
|
|
return;
|
|
}
|
|
raw_notify(player,
|
|
T("Num Name Description Owner"));
|
|
|
|
malias_t *m;
|
|
int i;
|
|
|
|
for (i = 0; i < ma_top; i++)
|
|
{
|
|
m = malias[i];
|
|
const UTF8 *pSpaces = Spaces(40 - m->desc_width);
|
|
raw_notify(player, tprintf(T("%-4d %-12s %s%s %-15.15s"),
|
|
i, m->name, m->desc, pSpaces,
|
|
Moniker(m->owner)));
|
|
}
|
|
|
|
raw_notify(player, T("***** End of Mail Aliases *****"));
|
|
}
|
|
|
|
static void do_malias_status(dbref player)
|
|
{
|
|
if (!ExpMail(player))
|
|
{
|
|
raw_notify(player, T("MAIL: Permission denied."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Number of mail aliases defined: %d"), ma_top));
|
|
raw_notify(player, tprintf(T("MAIL: Allocated slots %d"), ma_size));
|
|
}
|
|
}
|
|
|
|
XFUNCTION(fun_malias)
|
|
{
|
|
UNUSED_PARAMETER(fp);
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
dbref target;
|
|
if (nfargs == 0)
|
|
{
|
|
target = executor;
|
|
}
|
|
else
|
|
{
|
|
target = match_thing_quiet(executor, fargs[0]);
|
|
if (!Good_obj(target))
|
|
{
|
|
safe_match_result(target, buff, bufc);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( target != executor
|
|
&& !ExpMail(executor))
|
|
{
|
|
safe_noperm(buff, bufc);
|
|
return;
|
|
}
|
|
|
|
ITL pContext;
|
|
ItemToList_Init(&pContext, buff, bufc);
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
if (malias[i]->owner == target)
|
|
{
|
|
if (!ItemToList_AddString(&pContext, malias[i]->name))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
ItemToList_Final(&pContext);
|
|
}
|
|
|
|
static void malias_cleanup1(malias_t *m, dbref target)
|
|
{
|
|
m->list.erase(
|
|
std::remove_if(m->list.begin(), m->list.end(),
|
|
[target](dbref j) { return !Good_obj(j) || j == target; }),
|
|
m->list.end());
|
|
}
|
|
|
|
void malias_cleanup(dbref player)
|
|
{
|
|
// Remove destroyed player from alias membership lists.
|
|
//
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
malias_cleanup1(malias[i], player);
|
|
}
|
|
|
|
// Delete aliases owned by the destroyed player. Iterate backwards
|
|
// to avoid index-shift issues when removing multiple entries.
|
|
//
|
|
for (int i = ma_top - 1; i >= 0; i--)
|
|
{
|
|
if (malias[i]->owner == player)
|
|
{
|
|
MEMFREE(malias[i]->name);
|
|
MEMFREE(malias[i]->desc);
|
|
delete malias[i];
|
|
|
|
// Shift remaining entries down.
|
|
//
|
|
for (int j = i; j < ma_top - 1; j++)
|
|
{
|
|
malias[j] = malias[j + 1];
|
|
}
|
|
ma_top--;
|
|
}
|
|
}
|
|
sqlite_wt_sync_all_aliases();
|
|
}
|
|
|
|
static void do_mail_retract1(dbref player, UTF8 *name, UTF8 *msglist)
|
|
{
|
|
dbref target = lookup_player(player, name, true);
|
|
if (target == NOTHING)
|
|
{
|
|
raw_notify(player, T("MAIL: No such player."));
|
|
return;
|
|
}
|
|
struct mail_selector ms;
|
|
if (!parse_msglist(msglist, &ms, target))
|
|
{
|
|
return;
|
|
}
|
|
|
|
int i = 0, j = 0;
|
|
MailList ml(target);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (mail_from_player(player, mp))
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
j++;
|
|
if (Unread(mp))
|
|
{
|
|
ml.RemoveItem();
|
|
raw_notify(player, T("MAIL: Mail retracted."));
|
|
}
|
|
else
|
|
{
|
|
raw_notify(player, T("MAIL: That message has been read."));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!j)
|
|
{
|
|
// Ran off the end of the list without finding anything.
|
|
//
|
|
raw_notify(player, T("MAIL: No matching messages."));
|
|
}
|
|
}
|
|
|
|
static void do_mail_retract(dbref player, UTF8 *name, UTF8 *msglist)
|
|
{
|
|
if (*name == '*')
|
|
{
|
|
int pnResult;
|
|
malias_t *m = get_malias(player, name, &pnResult);
|
|
if (pnResult == GMA_NOTFOUND)
|
|
{
|
|
raw_notify(player, tprintf(T("MAIL: Mail alias %s not found."), name));
|
|
return;
|
|
}
|
|
if (pnResult == GMA_FOUND)
|
|
{
|
|
for (size_t i = 0; i < m->list.size(); i++)
|
|
{
|
|
do_mail_retract1(player, tprintf(T("#%d"), m->list[i]), msglist);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
do_mail_retract1(player, name, msglist);
|
|
}
|
|
}
|
|
|
|
void do_malias
|
|
(
|
|
dbref executor,
|
|
dbref caller,
|
|
dbref enactor,
|
|
int eval,
|
|
int key,
|
|
int nargs,
|
|
UTF8 *arg1,
|
|
UTF8 *arg2,
|
|
const UTF8 *cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->MaliasCommand(executor, key, arg1, arg2);
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case 0:
|
|
do_malias_switch(executor, arg1, arg2);
|
|
break;
|
|
case MALIAS_DESC:
|
|
do_malias_desc(executor, arg1, arg2);
|
|
break;
|
|
case MALIAS_CHOWN:
|
|
do_malias_chown(executor, arg1, arg2);
|
|
break;
|
|
case MALIAS_ADD:
|
|
do_malias_add(executor, arg1, arg2);
|
|
break;
|
|
case MALIAS_REMOVE:
|
|
do_malias_remove(executor, arg1, arg2);
|
|
break;
|
|
case MALIAS_DELETE:
|
|
do_malias_delete(executor, arg1);
|
|
break;
|
|
case MALIAS_RENAME:
|
|
do_malias_rename(executor, arg1, arg2);
|
|
break;
|
|
case 7:
|
|
// empty
|
|
break;
|
|
case MALIAS_LIST:
|
|
do_malias_adminlist(executor);
|
|
break;
|
|
case MALIAS_STATUS:
|
|
do_malias_status(executor);
|
|
}
|
|
}
|
|
|
|
void do_mail
|
|
(
|
|
dbref executor,
|
|
dbref caller,
|
|
dbref enactor,
|
|
int eval,
|
|
int key,
|
|
int nargs,
|
|
UTF8 *arg1,
|
|
UTF8 *arg2,
|
|
const UTF8 *cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(nargs);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->MailCommand(executor, key, arg1, arg2);
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Only @mail/quick is allowed from non-player executors. All other
|
|
// @mail subcommands use interactive state (composing buffer, folder
|
|
// selection, etc.) that only makes sense in a player session.
|
|
//
|
|
if ( (key & ~MAIL_QUOTE) != MAIL_QUICK
|
|
&& !isPlayer(executor))
|
|
{
|
|
return;
|
|
}
|
|
|
|
switch (key & ~MAIL_QUOTE)
|
|
{
|
|
case 0:
|
|
do_mail_stub(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_STATS:
|
|
do_mail_stats(executor, arg1, 0);
|
|
break;
|
|
case MAIL_DSTATS:
|
|
do_mail_stats(executor, arg1, 1);
|
|
break;
|
|
case MAIL_FSTATS:
|
|
do_mail_stats(executor, arg1, 2);
|
|
break;
|
|
case MAIL_DEBUG:
|
|
do_mail_debug(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_NUKE:
|
|
do_mail_nuke(executor);
|
|
break;
|
|
case MAIL_FOLDER:
|
|
do_mail_change_folder(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_LIST:
|
|
do_mail_list(executor, arg1, arg2, false);
|
|
break;
|
|
case MAIL_READ:
|
|
do_mail_read(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_CLEAR:
|
|
do_mail_clear(executor, arg1);
|
|
break;
|
|
case MAIL_UNCLEAR:
|
|
do_mail_unclear(executor, arg1);
|
|
break;
|
|
case MAIL_PURGE:
|
|
do_mail_purge(executor);
|
|
break;
|
|
case MAIL_FILE:
|
|
do_mail_file(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_TAG:
|
|
do_mail_tag(executor, arg1);
|
|
break;
|
|
case MAIL_UNTAG:
|
|
do_mail_untag(executor, arg1);
|
|
break;
|
|
case MAIL_FORWARD:
|
|
do_mail_fwd(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_REPLY:
|
|
do_mail_reply(executor, arg1, false, key);
|
|
break;
|
|
case MAIL_REPLYALL:
|
|
do_mail_reply(executor, arg1, true, key);
|
|
break;
|
|
case MAIL_SEND:
|
|
do_expmail_stop(executor, 0);
|
|
break;
|
|
case MAIL_EDIT:
|
|
do_edit_msg(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_URGENT:
|
|
do_expmail_stop(executor, M_URGENT);
|
|
break;
|
|
case MAIL_ALIAS:
|
|
do_malias_create(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_ALIST:
|
|
do_malias_list_all(executor);
|
|
break;
|
|
case MAIL_PROOF:
|
|
do_mail_proof(executor);
|
|
break;
|
|
case MAIL_ABORT:
|
|
do_expmail_abort(executor);
|
|
break;
|
|
case MAIL_QUICK:
|
|
do_mail_quick(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_REVIEW:
|
|
do_mail_review(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_RETRACT:
|
|
do_mail_retract(executor, arg1, arg2);
|
|
break;
|
|
case MAIL_CC:
|
|
do_mail_cc(executor, arg1, false);
|
|
break;
|
|
case MAIL_SAFE:
|
|
do_mail_safe(executor, arg1);
|
|
break;
|
|
case MAIL_UNSAFE:
|
|
do_mail_unsafe(executor, arg1);
|
|
break;
|
|
case MAIL_BCC:
|
|
do_mail_cc(executor, arg1, true);
|
|
break;
|
|
case MAIL_NEXT:
|
|
do_mail_next(executor);
|
|
break;
|
|
}
|
|
}
|
|
|
|
struct mail *MailList::FirstItem(void)
|
|
{
|
|
auto it_first = mudstate.mail_htab.find(m_player);
|
|
m_miHead = (it_first != mudstate.mail_htab.end()) ? static_cast<struct mail*>(it_first->second) : nullptr;
|
|
m_mi = m_miHead;
|
|
m_bRemoved = false;
|
|
return m_mi;
|
|
}
|
|
|
|
struct mail *MailList::NextItem(void)
|
|
{
|
|
if (!m_bRemoved)
|
|
{
|
|
if (nullptr != m_mi)
|
|
{
|
|
m_mi = m_mi->next;
|
|
if (m_mi == m_miHead)
|
|
{
|
|
m_mi = nullptr;
|
|
}
|
|
}
|
|
}
|
|
m_bRemoved = false;
|
|
return m_mi;
|
|
}
|
|
|
|
bool MailList::IsEnd(void)
|
|
{
|
|
return (nullptr == m_mi);
|
|
}
|
|
|
|
MailList::MailList(dbref player)
|
|
{
|
|
m_mi = nullptr;
|
|
m_miHead = nullptr;
|
|
m_player = player;
|
|
m_bRemoved = false;
|
|
}
|
|
|
|
void MailList::RemoveItem(void)
|
|
{
|
|
if ( nullptr == m_mi
|
|
|| NOTHING == m_player)
|
|
{
|
|
return;
|
|
}
|
|
|
|
struct mail *miNext = m_mi->next;
|
|
|
|
if (m_mi == m_miHead)
|
|
{
|
|
if (miNext == m_miHead)
|
|
{
|
|
mudstate.mail_htab.erase(m_player);
|
|
miNext = nullptr;
|
|
}
|
|
else
|
|
{
|
|
auto it_repl = mudstate.mail_htab.find(m_player);
|
|
if (it_repl != mudstate.mail_htab.end()) it_repl->second = miNext;
|
|
}
|
|
m_miHead = miNext;
|
|
}
|
|
|
|
// Relink the list
|
|
//
|
|
m_mi->prev->next = m_mi->next;
|
|
m_mi->next->prev = m_mi->prev;
|
|
|
|
m_mi->next = nullptr;
|
|
m_mi->prev = nullptr;
|
|
sqlite_wt_delete_mail(m_mi);
|
|
MessageReferenceDec(m_mi->number);
|
|
MEMFREE(m_mi->subject);
|
|
m_mi->subject = nullptr;
|
|
MEMFREE(m_mi->time);
|
|
m_mi->time = nullptr;
|
|
MEMFREE(m_mi->tolist);
|
|
m_mi->tolist = nullptr;
|
|
delete m_mi;
|
|
|
|
m_mi = miNext;
|
|
m_bRemoved = true;
|
|
}
|
|
|
|
void MailList::AppendItem(struct mail *miNew)
|
|
{
|
|
auto it_append = mudstate.mail_htab.find(m_player);
|
|
struct mail *miHead = (it_append != mudstate.mail_htab.end()) ? static_cast<struct mail*>(it_append->second) : nullptr;
|
|
|
|
if (miHead)
|
|
{
|
|
// Add new item to the end of the list.
|
|
//
|
|
struct mail *miEnd = miHead->prev;
|
|
|
|
miNew->next = miHead;
|
|
miNew->prev = miEnd;
|
|
|
|
miHead->prev = miNew;
|
|
miEnd->next = miNew;
|
|
}
|
|
else
|
|
{
|
|
mudstate.mail_htab.emplace(m_player, miNew);
|
|
miNew->next = miNew;
|
|
miNew->prev = miNew;
|
|
}
|
|
}
|
|
|
|
void MailList::RemoveAll(void)
|
|
{
|
|
auto it_removeall = mudstate.mail_htab.find(m_player);
|
|
struct mail *miHead = (it_removeall != mudstate.mail_htab.end()) ? static_cast<struct mail*>(it_removeall->second) : nullptr;
|
|
|
|
if (nullptr != miHead)
|
|
{
|
|
sqlite_wt_delete_all_mail(m_player);
|
|
mudstate.mail_htab.erase(it_removeall);
|
|
}
|
|
|
|
struct mail *mi;
|
|
struct mail *miNext;
|
|
for (mi = miHead; nullptr != mi; mi = miNext)
|
|
{
|
|
miNext = mi->next;
|
|
if (miNext == miHead)
|
|
{
|
|
miNext = nullptr;
|
|
}
|
|
MessageReferenceDec(mi->number);
|
|
MEMFREE(mi->subject);
|
|
mi->subject = nullptr;
|
|
MEMFREE(mi->tolist);
|
|
mi->tolist = nullptr;
|
|
MEMFREE(mi->time);
|
|
mi->time = nullptr;
|
|
delete mi;
|
|
}
|
|
m_mi = nullptr;
|
|
}
|
|
|
|
static void ListMailInFolderNumber(dbref player, int folder_num, UTF8 *msglist)
|
|
{
|
|
int original_folder = player_folder(player);
|
|
set_player_folder(player, folder_num);
|
|
|
|
struct mail_selector ms;
|
|
|
|
if (!parse_msglist(msglist, &ms, player))
|
|
{
|
|
return;
|
|
}
|
|
int i = 0;
|
|
UTF8 *time;
|
|
UTF8 szSubjectBuffer[MBUF_SIZE];
|
|
|
|
raw_notify(player, tprintf(T(FOLDER_LINE), folder_num));
|
|
|
|
MailList ml(player);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
if (Folder(mp) == folder_num)
|
|
{
|
|
i++;
|
|
if (mail_match(mp, ms, i))
|
|
{
|
|
time = mail_list_time(mp->time);
|
|
size_t nSize = MessageFetchSize(mp->number);
|
|
|
|
UTF8 szFromName[MBUF_SIZE];
|
|
trimmed_name(mp->from, szFromName, 16, 16, 0);
|
|
|
|
StripTabsAndTruncate(mp->subject, szSubjectBuffer,
|
|
MBUF_SIZE-1, 25);
|
|
|
|
raw_notify(player, tprintf(T("[%s] %-3d (%4d) From: %s Sub: %s"),
|
|
status_chars(mp), i, nSize, szFromName,
|
|
szSubjectBuffer));
|
|
free_lbuf(time);
|
|
}
|
|
}
|
|
}
|
|
raw_notify(player, DASH_LINE);
|
|
|
|
set_player_folder(player, original_folder);
|
|
|
|
}
|
|
|
|
static void ListMailInFolder(dbref player, UTF8 *folder_name, UTF8 *msglist)
|
|
{
|
|
int folder = 0;
|
|
|
|
if ( nullptr == folder_name
|
|
|| '\0' == folder_name[0])
|
|
{
|
|
folder = player_folder(player);
|
|
}
|
|
else
|
|
{
|
|
folder = parse_folder(player, folder_name);
|
|
}
|
|
|
|
if (-1 == folder)
|
|
{
|
|
raw_notify(player, T("MAIL: No such folder."));
|
|
return;
|
|
}
|
|
ListMailInFolderNumber(player, folder, msglist);
|
|
}
|
|
|
|
void do_folder
|
|
(
|
|
dbref executor,
|
|
dbref caller,
|
|
dbref enactor,
|
|
int eval,
|
|
int key,
|
|
int nargs,
|
|
UTF8 *arg1,
|
|
UTF8 *arg2,
|
|
const UTF8 *cargs[],
|
|
int ncargs
|
|
)
|
|
{
|
|
UNUSED_PARAMETER(caller);
|
|
UNUSED_PARAMETER(enactor);
|
|
UNUSED_PARAMETER(eval);
|
|
UNUSED_PARAMETER(cargs);
|
|
UNUSED_PARAMETER(ncargs);
|
|
|
|
if (nullptr != mudstate.pIMailControl)
|
|
{
|
|
MUX_RESULT mr = mudstate.pIMailControl->FolderCommand(executor, key, nargs, arg1, arg2);
|
|
if (MUX_SUCCEEDED(mr))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
switch (key)
|
|
{
|
|
case FOLDER_FILE:
|
|
do_mail_file(executor, arg1, arg2);
|
|
break;
|
|
|
|
case FOLDER_LIST:
|
|
ListMailInFolder(executor, arg1, arg2);
|
|
break;
|
|
|
|
case FOLDER_READ:
|
|
do_mail_read(executor, arg1, arg2);
|
|
break;
|
|
|
|
case FOLDER_SET:
|
|
do_mail_change_folder(executor, arg1, arg2);
|
|
break;
|
|
|
|
default:
|
|
if ( nullptr == arg1
|
|
|| '\0' == arg1[0])
|
|
{
|
|
DoListMailBrief(executor);
|
|
}
|
|
else if (2 == nargs)
|
|
{
|
|
do_mail_read(executor, arg1, arg2);
|
|
}
|
|
else
|
|
{
|
|
do_mail_change_folder(executor, arg1, arg2);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// SQLite mail bulk sync and load (Phase 1).
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static void clear_runtime_mail_data(void)
|
|
{
|
|
while (!mudstate.mail_htab.empty())
|
|
{
|
|
MailList ml(mudstate.mail_htab.begin()->first);
|
|
ml.RemoveAll();
|
|
}
|
|
|
|
if (nullptr != mail_list)
|
|
{
|
|
mail_list -= MAIL_FUDGE;
|
|
MEMFREE(mail_list);
|
|
mail_list = nullptr;
|
|
}
|
|
mudstate.mail_db_top = 0;
|
|
mudstate.mail_db_size = 0;
|
|
|
|
if (nullptr != malias)
|
|
{
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
if (malias[i])
|
|
{
|
|
if (malias[i]->name)
|
|
{
|
|
MEMFREE(malias[i]->name);
|
|
malias[i]->name = nullptr;
|
|
}
|
|
if (malias[i]->desc)
|
|
{
|
|
MEMFREE(malias[i]->desc);
|
|
malias[i]->desc = nullptr;
|
|
}
|
|
delete malias[i];
|
|
malias[i] = nullptr;
|
|
}
|
|
}
|
|
delete [] malias;
|
|
malias = nullptr;
|
|
}
|
|
ma_top = 0;
|
|
ma_size = 0;
|
|
}
|
|
|
|
bool sqlite_sync_mail(void)
|
|
{
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
if (!sqldb.Begin())
|
|
{
|
|
return false;
|
|
}
|
|
if (!sqldb.ClearMailTables())
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
|
|
// Sync mail bodies first (mail_headers.body_number references
|
|
// mail_bodies.number via foreign key).
|
|
//
|
|
for (int i = 0; i < mudstate.mail_db_top; i++)
|
|
{
|
|
if (0 < mail_list[i].m_nRefs)
|
|
{
|
|
if (!sqldb.SyncMailBody(i, MessageFetch(i)))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sync mail headers.
|
|
//
|
|
dbref thing;
|
|
DO_WHOLE_DB(thing)
|
|
{
|
|
if (isPlayer(thing))
|
|
{
|
|
MailList ml(thing);
|
|
struct mail *mp;
|
|
for (mp = ml.FirstItem(); !ml.IsEnd(); mp = ml.NextItem())
|
|
{
|
|
mp->sqlite_id = sqldb.InsertMailHeaderReturningId(
|
|
mp->to, mp->from, mp->number,
|
|
mp->tolist, mp->time, mp->subject, mp->read);
|
|
if (mp->sqlite_id < 0)
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sync mail aliases.
|
|
//
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
malias_t *m = malias[i];
|
|
|
|
// Serialize member list to space-separated string.
|
|
//
|
|
LBuf members_buf = LBuf_Src("sync_mail_alias");
|
|
UTF8 *bp = members_buf;
|
|
for (size_t j = 0; j < m->list.size(); j++)
|
|
{
|
|
if (j > 0)
|
|
{
|
|
safe_chr(' ', members_buf, &bp);
|
|
}
|
|
safe_str(tprintf(T("%d"), m->list[j]), members_buf, &bp);
|
|
}
|
|
*bp = '\0';
|
|
|
|
if (!sqldb.SyncMailAlias(m->owner, m->name, m->desc,
|
|
static_cast<int>(m->desc_width), members_buf))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!sqldb.PutMeta("mail_db_top", mudstate.mail_db_top))
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
if (!sqldb.Commit())
|
|
{
|
|
sqldb.Rollback();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int sqlite_load_mail(void)
|
|
{
|
|
mudstate.bSQLiteLoading = true;
|
|
CSQLiteDB &sqldb = g_pSQLiteBackend->GetDB();
|
|
|
|
int mail_top = 0;
|
|
CSQLiteDB::MetaGetResult mail_top_meta = sqldb.GetMetaEx("mail_db_top", &mail_top);
|
|
if (CSQLiteDB::MetaGetResult::Error == mail_top_meta)
|
|
{
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
if (CSQLiteDB::MetaGetResult::Found != mail_top_meta)
|
|
{
|
|
mudstate.bSQLiteLoading = false;
|
|
return 0;
|
|
}
|
|
|
|
clear_runtime_mail_data();
|
|
mail_db_grow(mail_top + 1);
|
|
|
|
// Load mail bodies first (they must exist before headers reference them).
|
|
//
|
|
if (!sqldb.LoadAllMailBodies([](int number, const UTF8 *message)
|
|
{
|
|
new_mail_message(const_cast<UTF8 *>(message), number);
|
|
}))
|
|
{
|
|
clear_runtime_mail_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
// Load mail headers.
|
|
//
|
|
if (!sqldb.LoadAllMailHeaders([](int64_t rowid, int to_player, int from_player,
|
|
int body_number, const UTF8 *tolist, const UTF8 *time_str,
|
|
const UTF8 *subject, int read_flags)
|
|
{
|
|
struct mail *mp = nullptr;
|
|
try
|
|
{
|
|
mp = new struct mail;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == mp)
|
|
{
|
|
return;
|
|
}
|
|
|
|
mp->to = to_player;
|
|
mp->from = from_player;
|
|
mp->number = body_number;
|
|
MessageReferenceInc(mp->number);
|
|
mp->tolist = StringClone(tolist);
|
|
mp->time = StringClone(time_str);
|
|
mp->subject = StringClone(subject);
|
|
mp->read = read_flags;
|
|
mp->sqlite_id = rowid;
|
|
|
|
MailList ml(mp->to);
|
|
ml.AppendItem(mp);
|
|
}))
|
|
{
|
|
clear_runtime_mail_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
// Load mail aliases.
|
|
//
|
|
std::vector<malias_t *> alias_vec;
|
|
auto free_alias_vec = [&alias_vec]()
|
|
{
|
|
for (auto *m : alias_vec)
|
|
{
|
|
if (m)
|
|
{
|
|
if (m->name)
|
|
{
|
|
MEMFREE(m->name);
|
|
m->name = nullptr;
|
|
}
|
|
if (m->desc)
|
|
{
|
|
MEMFREE(m->desc);
|
|
m->desc = nullptr;
|
|
}
|
|
delete m;
|
|
}
|
|
}
|
|
alias_vec.clear();
|
|
};
|
|
|
|
if (!sqldb.LoadAllMailAliases([&alias_vec](int owner, const UTF8 *name,
|
|
const UTF8 *desc, int desc_width, const UTF8 *members)
|
|
{
|
|
malias_t *m = nullptr;
|
|
try
|
|
{
|
|
m = new malias_t;
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (nullptr == m)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m->owner = owner;
|
|
m->name = StringClone(name);
|
|
m->desc = StringClone(desc);
|
|
m->desc_width = desc_width;
|
|
|
|
// Parse space-separated member list.
|
|
//
|
|
m->list.clear();
|
|
if (members && members[0] != '\0')
|
|
{
|
|
LBuf buf = LBuf_Src("load_mail_alias");
|
|
mux_strncpy(buf, members, LBUF_SIZE - 1);
|
|
UTF8 *p = buf;
|
|
while (*p)
|
|
{
|
|
while (*p == ' ') p++;
|
|
if (*p == '\0') break;
|
|
m->list.push_back(mux_atol(p));
|
|
while (*p && *p != ' ') p++;
|
|
}
|
|
}
|
|
|
|
alias_vec.push_back(m);
|
|
}))
|
|
{
|
|
free_alias_vec();
|
|
clear_runtime_mail_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
if (!alias_vec.empty())
|
|
{
|
|
int alias_count = static_cast<int>(alias_vec.size());
|
|
malias = nullptr;
|
|
try
|
|
{
|
|
malias = new malias_t *[alias_count];
|
|
}
|
|
catch (...)
|
|
{
|
|
; // Nothing.
|
|
}
|
|
|
|
if (!malias)
|
|
{
|
|
free_alias_vec();
|
|
clear_runtime_mail_data();
|
|
mudstate.bSQLiteLoading = false;
|
|
return -1;
|
|
}
|
|
|
|
ma_top = ma_size = alias_count;
|
|
for (int i = 0; i < ma_top; i++)
|
|
{
|
|
malias[i] = alias_vec[i];
|
|
}
|
|
}
|
|
|
|
mudstate.bSQLiteLoading = false;
|
|
return 1;
|
|
}
|