mirror of
https://github.com/MeshEnvy/lobbs.git
synced 2026-10-31 02:11:59 +01:00
285 lines
10 KiB
C++
285 lines
10 KiB
C++
#include "MailCommands.h"
|
|
#include "../../core/LoBBSCommandRegistry.h"
|
|
#include "../../core/LoBBSConfig.h"
|
|
#include "../../core/LoBBSHooks.h"
|
|
#include "../../core/LoBBSKernel.h"
|
|
#include "../../core/LoBBSReply.h"
|
|
#include "../../core/LoBBSResponse.h"
|
|
#include "../AppUtil.h"
|
|
#include "../Msg/MsgCommon.h"
|
|
#include "MailDal.h"
|
|
#include "MailRecords.h"
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <lodb/LoDB.h>
|
|
#include <vector>
|
|
|
|
#include "core/LoBBSStackGuard.h"
|
|
|
|
static void mailAppendListRecord(LoBBSKernel *kernel, LoBBSResponse &resp, uint32_t oneBasedIndex, const LoScalar &mail)
|
|
{
|
|
char name[LOBBS_USERNAME_BUFFER_SIZE];
|
|
char when[LOBBS_TIME_AGO_BUFFER_SIZE];
|
|
char trunc[LOBBS_LIST_LINE_TRUNC_BUFFER_SIZE];
|
|
// One char past the preview limit is enough for lobbsAppTruncMsg to add "...".
|
|
char msg[LOBBS_LIST_LINE_TRUNC_MAX_CHARS + 2];
|
|
char line[LOBBS_REPLY_BYTES + 1];
|
|
lobbsAppUsernameForUuid(kernel, MailDal::mailFromUuid(mail), name, sizeof(name));
|
|
lobbsAppTimeAgo(MailDal::mailTimestamp(mail), when, sizeof(when));
|
|
MailDal::mailMessage(mail, msg, sizeof(msg));
|
|
lobbsAppTruncMsg(msg, trunc, sizeof(trunc), LOBBS_LIST_LINE_TRUNC_MAX_CHARS);
|
|
snprintf(line, sizeof(line), "[%u]%s @%s: %s (%s)", (unsigned)oneBasedIndex, MailDal::mailRead(mail) ? "" : "*", name, trunc,
|
|
when);
|
|
|
|
LoScalar rec;
|
|
rec.setUint64(LODB_F_ID, MailDal::mailUuid(mail));
|
|
rec.setString(LODB_F_TITLE, line);
|
|
rec.setString(LODB_F_DESCRIPTION, trunc);
|
|
rec.setBool(MailField::FIELD_READ, MailDal::mailRead(mail));
|
|
rec.setUint32(LODB_F_CREATED, MailDal::mailTimestamp(mail));
|
|
lobbsResponseAppendRecord(resp, rec);
|
|
}
|
|
|
|
static void mailSubList(LoBBSCommandCtx &ctx)
|
|
{
|
|
const char *sub = lobbsArgPeek(ctx);
|
|
if (sub && strcasecmp(sub, "list") == 0)
|
|
lobbsArgShift(ctx);
|
|
|
|
uint64_t inboxUuid = lobbsCtxUserUuid(ctx);
|
|
const char *maybeUser = lobbsArgPeek(ctx);
|
|
if (maybeUser) {
|
|
if (!lobbsCommandRequireSysop(ctx))
|
|
return;
|
|
const char *user = lobbsArgShift(ctx);
|
|
if (!lobbsAppResolveUsername(ctx, user, inboxUuid))
|
|
return;
|
|
}
|
|
|
|
auto all = ctx.kernel->mail().dal().getAllMailForUser(inboxUuid);
|
|
LoBBSResponse resp;
|
|
if (all.empty()) {
|
|
lobbsResponseSetError(resp, "No mail");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
for (size_t i = 0; i < all.size(); i++)
|
|
mailAppendListRecord(ctx.kernel, resp, (uint32_t)(i + 1), all[i]);
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
}
|
|
|
|
static void mailSubRead(LoBBSCommandCtx &ctx, bool numericShorthand)
|
|
{
|
|
MailDal &mail = ctx.kernel->mail().dal();
|
|
if (!numericShorthand) {
|
|
const char *sub = lobbsArgShift(ctx);
|
|
(void)sub;
|
|
}
|
|
|
|
uint32_t idx = 0;
|
|
uint64_t inboxUuid = lobbsCtxUserUuid(ctx);
|
|
bool markRead = true;
|
|
bool sysopRead = ctx.session.isSysop && lobbsArgPeek(ctx) && !lobbsArgPeekIsUint(ctx);
|
|
|
|
if (sysopRead) {
|
|
const char *user = lobbsArgShift(ctx);
|
|
if (!user || !lobbsAppResolveUsername(ctx, user, inboxUuid))
|
|
return;
|
|
const char *beforeNum = lobbsArgPeek(ctx);
|
|
if (!lobbsArgShiftUint(ctx, idx)) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, beforeNum ? "Invalid message number." : "Usage: /mail read N");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
markRead = false;
|
|
} else {
|
|
const char *beforeNum = lobbsArgPeek(ctx);
|
|
if (!lobbsArgShiftUint(ctx, idx)) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, beforeNum ? "Invalid message number." : "Usage: /mail read N");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
}
|
|
|
|
auto mailMessages = mail.getAllMailForUser(inboxUuid);
|
|
if (idx == 0 || idx > mailMessages.size()) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, "Invalid message number");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
const LoScalar &m = mailMessages[idx - 1];
|
|
char name[LOBBS_USERNAME_BUFFER_SIZE];
|
|
char when[LOBBS_TIME_AGO_BUFFER_SIZE];
|
|
char body[LOBBS_MESSAGE_READ_BODY_BUFFER_SIZE];
|
|
char header[LOBBS_USERNAME_BUFFER_SIZE + LOBBS_TIME_AGO_BUFFER_SIZE + 16];
|
|
lobbsAppUsernameForUuid(ctx.kernel, MailDal::mailFromUuid(m), name, sizeof(name));
|
|
MailDal::mailMessage(m, body, sizeof(body));
|
|
lobbsAppTimeAgo(MailDal::mailTimestamp(m), when, sizeof(when));
|
|
snprintf(header, sizeof(header), "From: @%s (%s)", name, when);
|
|
|
|
LoScalar rec;
|
|
rec.setUint64(LODB_F_ID, MailDal::mailUuid(m));
|
|
rec.setString(LODB_F_TITLE, header);
|
|
rec.setString(LODB_F_DESCRIPTION, body);
|
|
rec.setUint32(LODB_F_CREATED, MailDal::mailTimestamp(m));
|
|
if (markRead && inboxUuid == lobbsCtxUserUuid(ctx))
|
|
mail.markMailAsRead(MailDal::mailUuid(m));
|
|
|
|
LoBBSResponse resp;
|
|
lobbsResponseAppendRecord(resp, rec);
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
}
|
|
|
|
static void mailSubReadCmd(LoBBSCommandCtx &ctx)
|
|
{
|
|
mailSubRead(ctx, false);
|
|
}
|
|
|
|
static void mailSubUnread(LoBBSCommandCtx &ctx)
|
|
{
|
|
uint32_t idx = 0;
|
|
if (!lobbsMsgShiftIndex(ctx, 1, "Usage: /mail unread N", "Invalid message number.", idx))
|
|
return;
|
|
MailDal &mail = ctx.kernel->mail().dal();
|
|
auto mailMessages = mail.getAllMailForUser(lobbsCtxUserUuid(ctx));
|
|
if (idx == 0 || idx > mailMessages.size()) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, "Invalid message number");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
bool ok = mail.markMailAsUnread(MailDal::mailUuid(mailMessages[idx - 1]));
|
|
LoBBSResponse resp;
|
|
lobbsRecordPush(resp.records, ok ? "Marked unread." : "Failed.");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
}
|
|
|
|
static void mailSubDelete(LoBBSCommandCtx &ctx)
|
|
{
|
|
uint32_t idx = 0;
|
|
if (!lobbsMsgShiftIndex(ctx, 1, "Usage: /mail delete N", "Invalid message number.", idx))
|
|
return;
|
|
LoBBSResponse resp;
|
|
lobbsRecordPush(resp.records,
|
|
ctx.kernel->mail().dal().deleteMailInboxIndex(lobbsCtxUserUuid(ctx), idx) ? "Deleted." : "Failed.");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
}
|
|
|
|
static void mailSubSend(LoBBSCommandCtx &ctx)
|
|
{
|
|
const char *sub = lobbsArgShift(ctx);
|
|
if (!sub || strcasecmp(sub, "send") != 0) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, "Usage: /mail send user message...");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
const char *user = lobbsArgShift(ctx);
|
|
const char *body = lobbsArgRest(ctx);
|
|
if (!user || !body[0]) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, "Usage: /mail send user message...");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
return;
|
|
}
|
|
uint64_t toUuid = 0;
|
|
if (!lobbsAppResolveUsername(ctx, user, toUuid))
|
|
return;
|
|
LoBBSResponse resp;
|
|
LoDbError err = ctx.kernel->mail().dal().sendMail(lobbsCtxUserUuid(ctx), toUuid, body);
|
|
lobbsRecordPush(resp.records, err == LODB_OK ? "Mail sent." : lobbsDbErrorText(err, "Failed to send mail."));
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
}
|
|
|
|
static const LoBBSVerb mailVerbs[] = {
|
|
{"list", mailSubList, LOBBS_V_LOGIN, "list — inbox"},
|
|
{"list", nullptr, LOBBS_V_LOGIN | LOBBS_V_SYSOP, "list user — sysop: another inbox"},
|
|
{"read", mailSubReadCmd, LOBBS_V_LOGIN, "read N — read message (/mail N)"},
|
|
{"read", nullptr, LOBBS_V_LOGIN | LOBBS_V_SYSOP, "read user N — sysop: read without marking read"},
|
|
{"unread", mailSubUnread, LOBBS_V_LOGIN, "unread N — mark message unread"},
|
|
{"delete", mailSubDelete, LOBBS_V_LOGIN, "delete N — delete message from inbox"},
|
|
{"send", mailSubSend, LOBBS_V_LOGIN, "send user message... — send mail"},
|
|
};
|
|
|
|
static void handleMail(LoBBSCommandCtx &ctx)
|
|
{
|
|
if (!lobbsCommandRequireLogin(ctx))
|
|
return;
|
|
|
|
if (lobbsArgPeekIsUint(ctx)) {
|
|
mailSubRead(ctx, true);
|
|
return;
|
|
}
|
|
|
|
if (!lobbsDispatchSub(ctx, mailVerbs, sizeof(mailVerbs) / sizeof(mailVerbs[0]))) {
|
|
LoBBSResponse resp;
|
|
lobbsResponseSetError(resp, "Unknown command. Try /help mail");
|
|
lobbsCommandReplyResponse(ctx, resp);
|
|
}
|
|
}
|
|
|
|
static void slashMail(LoBBSCommandCtx *ctx, const LoScalar &args)
|
|
{
|
|
if (!ctx || !lobbsSlashVerbIs(args, "mail"))
|
|
return;
|
|
handleMail(*ctx);
|
|
}
|
|
|
|
static void filterMailHelpTopics(LoBBSCommandCtx *ctx, std::vector<LoScalar> &topics, const LoScalar &args)
|
|
{
|
|
(void)args;
|
|
if (!ctx || !lobbsCtxLoggedIn(*ctx))
|
|
return;
|
|
lobbsRecordPush(topics, "mail", "read and send mail");
|
|
}
|
|
|
|
static void filterMailHelpForTopic(LoBBSCommandCtx *ctx, LoScalar &value, const LoScalar &args)
|
|
{
|
|
lobbsHelpForTable(ctx, value, args, "mail", mailVerbs, sizeof(mailVerbs) / sizeof(mailVerbs[0]));
|
|
}
|
|
|
|
static void filterMailStatusLines(LoBBSCommandCtx *ctx, std::vector<LoScalar> &lines, const LoScalar &args)
|
|
{
|
|
(void)args;
|
|
if (!ctx || !ctx->kernel)
|
|
return;
|
|
char title[64];
|
|
char value[32];
|
|
if (lobbsCtxLoggedIn(*ctx)) {
|
|
uint32_t n = ctx->kernel->mail().dal().countUnreadMail(lobbsCtxUserUuid(*ctx));
|
|
snprintf(title, sizeof(title), "Mail");
|
|
snprintf(value, sizeof(value), "%u unread", (unsigned)n);
|
|
} else {
|
|
uint32_t n = ctx->kernel->mail().dal().countAllMail();
|
|
snprintf(title, sizeof(title), "Mail");
|
|
snprintf(value, sizeof(value), "%u total", (unsigned)n);
|
|
}
|
|
lobbsRecordPush(lines, title, value);
|
|
}
|
|
|
|
#if LOBBS_SEED
|
|
#include "MailSeed.h"
|
|
static void actionMailSeed(LoBBSCommandCtx *ctx, const LoScalar &args)
|
|
{
|
|
(void)args;
|
|
if (ctx && ctx->kernel)
|
|
lobbsSeedMail(*ctx->kernel);
|
|
}
|
|
#endif
|
|
|
|
void lobbsMailRegisterCommands()
|
|
{
|
|
lobbsAddAction("slash_cmd", slashMail, LOBBS_HOOK_PRIORITY_FEATURE);
|
|
lobbsAddFilter("help_topics", filterMailHelpTopics, LOBBS_HOOK_PRIORITY_FEATURE);
|
|
lobbsAddFilter("help_for_topic", filterMailHelpForTopic, LOBBS_HOOK_PRIORITY_FEATURE);
|
|
lobbsAddFilter("status_lines", filterMailStatusLines, LOBBS_HOOK_PRIORITY_FEATURE);
|
|
#if LOBBS_SEED
|
|
lobbsAddAction("seed", actionMailSeed, LOBBS_HOOK_PRIORITY_FEATURE);
|
|
#endif
|
|
}
|
|
|