]> jfr.im git - irc/evilnet/x3.git/blame - src/mod-memoserv.c
Fix for "whats the color" 8ball question formats
[irc/evilnet/x3.git] / src / mod-memoserv.c
CommitLineData
d76ed9a9
AS
1/* mod-memoserv.c - MemoServ module for srvx
2 * Copyright 2003-2004 Martijn Smit and srvx Development Team
34a9e19a 3 * Copyright 2005-2006 X3 Development Team
d76ed9a9 4 *
83ff05c3 5 * This file is part of x3.
d76ed9a9 6 *
d0f04f71 7 * x3 is free software; you can redistribute it and/or modify
d76ed9a9 8 * it under the terms of the GNU General Public License as published by
348683aa 9 * the Free Software Foundation; either version 3 of the License, or
d76ed9a9
AS
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with srvx; if not, write to the Free Software Foundation,
19 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
20 */
21
acf3c6d5 22/*
d76ed9a9
AS
23 * /msg opserv bind nickserv * *memoserv.*
24 *
25 * If you want a dedicated MemoServ bot, make sure the service control
26 * commands are bound to OpServ:
27 * /msg opserv bind opserv service *modcmd.joiner
28 * /msg opserv bind opserv service\ add *modcmd.service\ add
29 * /msg opserv bind opserv service\ rename *modcmd.service\ rename
30 * /msg opserv bind opserv service\ trigger *modcmd.service\ trigger
31 * /msg opserv bind opserv service\ remove *modcmd.service\ remove
32 * Add the bot:
33 * /msg opserv service add MemoServ User-to-user Memorandum Service
34 * /msg opserv bind memoserv help *modcmd.help
35 * Restart srvx with the updated conf file (as above, butwith "bot"
36 * "MemoServ"), and bind the commands to it:
37 * /msg opserv bind memoserv * *memoserv.*
fc4a50fc 38 * /msg opserv bind memoserv set *memoserv.set
d76ed9a9
AS
39 */
40
41#include "chanserv.h"
42#include "conf.h"
43#include "modcmd.h"
e3e5ba49 44#include "nickserv.h"
08895577 45#include "opserv.h"
d76ed9a9 46#include "saxdb.h"
1136f709 47#include "mail.h"
d76ed9a9
AS
48#include "timeq.h"
49
34a9e19a 50#define KEY_MAIN_ACCOUNTS "accounts"
51#define KEY_FLAGS "flags"
52#define KEY_LIMIT "limit"
53
1b4a47ca 54#define KEY_MAIN_HISTORY "history"
34a9e19a 55#define KEY_MAIN_MEMOS "memos"
d76ed9a9
AS
56#define KEY_SENT "sent"
57#define KEY_RECIPIENT "to"
58#define KEY_FROM "from"
59#define KEY_MESSAGE "msg"
60#define KEY_READ "read"
82794e1b 61#define KEY_RECIEPT "reciept"
f2e592d3 62#define KEY_ID "id"
d76ed9a9 63
1b4a47ca 64
d76ed9a9
AS
65static const struct message_entry msgtab[] = {
66 { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
a76bcc6a 67 { "MSMSG_UNKNOWN_SEND_FLAG", "Unreccognised send flag '%c', message not sent." },
f2e592d3 68 { "MSMSG_MEMO_SENT", "Message sent to $b%s$b (ID# %d)." },
d76ed9a9 69 { "MSMSG_NO_MESSAGES", "You have no messages." },
fc4a50fc 70 { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches." },
cf33840c
AS
71 { "MSMSG_HOWTO_READ", "Use READ <ID> to read a message." },
72 { "MSMSG_CLEAN_INBOX", "You have $b%d$b or more messages, please clean out your inbox.\nUse READ <ID> to read a message." },
cbc5a1a4 73 { "MSMSG_LIST_HEAD", "$bID$b $bFrom$b $bTime Sent$b" },
74 { "MSMSG_LIST_FORMAT", "%-2u %s $b%s$b %s" },
75 { "MSMSG_HISTORY_HEADER", "$bID$b $bTo$b $bTime Sent$b" },
76 { "MSMSG_HISTORY_FORMAT", "%-2u %s %s" },
d76ed9a9 77 { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
82794e1b 78 { "MSMSG_MEMO_RECIEPT", "$bRead Reciept$b requested, %s." },
d76ed9a9
AS
79 { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
80 { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
81 { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
f2e592d3 82 { "MSMSG_MEMO_CANCEL_NUMBER", "You must specify a number id" },
83 { "MSMSG_MEMO_DONT_OWN", "You did not send memo# %d" },
84 { "MSMSG_MEMO_READ", "Memo# %d has already been read, you cannot cancel it." },
85 { "MSMSG_MEMO_CANT_LOCATE", "Could not locate memo# %d" },
d76ed9a9
AS
86 { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
87 { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
88 { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
cf33840c
AS
89 { "MSMSG_MEMOS_INBOX", "You have $b%d$b new message(s) in your inbox and %d old messages. Use LIST to list them." },
90 { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. Use LIST to see your messages." },
a8692672 91 { "MSMSG_FULL_INBOX", "$b%s$b cannot recieve anymore memos as their inbox is full" },
d76ed9a9 92 { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
cf33840c 93 { "MSMSG_USE_CONFIRM", "Please use DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
e3e5ba49 94
1b4a47ca 95 { "MSMSG_STATUS_HIST_TOTAL", "I have $b%u$b history entries in my database." },
acf3c6d5 96 { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
d76ed9a9 97 { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
acf3c6d5 98 { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
e3e5ba49 99
a8692672 100 { "MSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
e3e5ba49 101 { "MSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
fc4a50fc
AS
102 { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify$b: %s" },
103 { "MSMSG_SET_NEWNOTIFY", "$bNewNotify$b: %s" },
104 { "MSMSG_SET_PRIVMSG", "$bPrivmsg$b: %s" },
105 { "MSMSG_SET_PRIVATE", "$bPrivate$b: %s" },
106 { "MSMSG_SET_IGNORERECIEPTS", "$bIgnoreReciepts$b: %s" },
107 { "MSMSG_SET_SENDRECIEPTS", "$bSendReciepts$b: %s" },
108 { "MSMSG_SET_LIMIT", "$bLimit$b: %d" },
2a951803 109 { "MSMSG_SET_OPTIONS", "$bMessaging Options$b" },
e3e5ba49 110 { "MSMSG_SET_OPTIONS_END", "-------------End of Options-------------" },
111
112 { "MSMSG_LIST_END", "--------------End of Memos--------------" },
113 { "MSMSG_BAR", "----------------------------------------"},
114
87708af4 115 { "MSEMAIL_NEWMEMO_SUBJECT", "New %s %s message from %s" },
116 { "MSEMAIL_NEWMEMO_BODY", "This email has been sent to let you know that %s has sent you a message via %s.\n\n The message is: %s.\n\nTo delete this message just type in /msg %s delete %d when on %s next." },
117
08895577 118 { "MSMSG_DEFCON_NO_NEW_MEMOS", "You cannot send new memos at this time, please try again soon." },
119
d76ed9a9
AS
120 { NULL, NULL }
121};
122
123struct memo {
124 struct memo_account *recipient;
125 struct memo_account *sender;
126 char *message;
127 time_t sent;
f2e592d3 128 unsigned long id;
d76ed9a9 129 unsigned int is_read : 1;
82794e1b 130 unsigned int reciept : 1;
d76ed9a9
AS
131};
132
1b4a47ca 133struct history {
134 struct memo_account *recipient;
135 struct memo_account *sender;
136 time_t sent;
137 unsigned long id;
138};
139
68cb9c2c 140struct userNode *memoserv;
141
142#define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
143#define MEMOSERV_SYNTAX() svccmd_send_help_brief(user, memoserv, cmd)
144#define MEMOSERV_MIN_PARAMS(N) if(argc < (N)) { \
145 reply("MSG_MISSING_PARAMS", argv[0]); \
146 MEMOSERV_SYNTAX(); \
147 return 0; }
148
d76ed9a9 149DECLARE_LIST(memoList, struct memo*);
1136f709 150DEFINE_LIST(memoList, struct memo*)
1b4a47ca 151DECLARE_LIST(historyList, struct history*);
1136f709 152DEFINE_LIST(historyList, struct history*)
d76ed9a9
AS
153
154/* memo_account.flags fields */
2a951803 155#define MEMO_NOTIFY_NEW 0x00000001
156#define MEMO_NOTIFY_LOGIN 0x00000002
157#define MEMO_DENY_NONCHANNEL 0x00000004
158#define MEMO_IGNORE_RECIEPTS 0x00000008
159#define MEMO_ALWAYS_RECIEPTS 0x00000010
fc4a50fc 160#define MEMO_USE_PRIVMSG 0x00000020
d76ed9a9
AS
161
162struct memo_account {
163 struct handle_info *handle;
fc4a50fc 164 unsigned int flags : 6;
a8692672 165 unsigned int limit;
d76ed9a9
AS
166 struct memoList sent;
167 struct memoList recvd;
1b4a47ca 168 struct historyList hsent;
169 struct historyList hrecvd;
d76ed9a9
AS
170};
171
172static struct {
173 struct userNode *bot;
174 int message_expiry;
a8692672 175 unsigned int limit;
d76ed9a9
AS
176} memoserv_conf;
177
d9abe201 178#define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
c092fcad 179#define OPTION_FUNC(NAME) int NAME(struct svccmd *cmd, struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
d9abe201 180typedef OPTION_FUNC(option_func_t);
181
f2e592d3 182unsigned long memo_id;
183
e3e5ba49 184extern struct string_list *autojoin_channels;
d76ed9a9
AS
185const char *memoserv_module_deps[] = { NULL };
186static struct module *memoserv_module;
187static struct log_type *MS_LOG;
1136f709 188static unsigned long memoCount;
189static unsigned long memosSent;
190static unsigned long memosExpired;
d76ed9a9 191static struct dict *memos; /* memo_account->handle->handle -> memo_account */
1b4a47ca 192static struct dict *historys;
e3e5ba49 193static dict_t memoserv_opt_dict; /* contains option_func_t* */
194
d76ed9a9
AS
195static struct memo_account *
196memoserv_get_account(struct handle_info *hi)
197{
198 struct memo_account *ma;
199 if (!hi)
200 return NULL;
201 ma = dict_find(memos, hi->handle, NULL);
202 if (ma)
203 return ma;
204 ma = calloc(1, sizeof(*ma));
205 if (!ma)
206 return ma;
207 ma->handle = hi;
fc4a50fc 208 ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN | MEMO_USE_PRIVMSG;
512d7958 209 ma->limit = memoserv_conf.limit;
d76ed9a9 210 dict_insert(memos, ma->handle->handle, ma);
1b4a47ca 211 dict_insert(historys, ma->handle->handle, ma);
d76ed9a9
AS
212 return ma;
213}
214
215static void
216delete_memo(struct memo *memo)
217{
218 memoList_remove(&memo->recipient->recvd, memo);
219 memoList_remove(&memo->sender->sent, memo);
220 free(memo->message);
221 free(memo);
1136f709 222 memoCount--;
d76ed9a9
AS
223}
224
1b4a47ca 225static void
226delete_history(struct history *history)
227{
228 historyList_remove(&history->recipient->hrecvd, history);
229 historyList_remove(&history->sender->hsent, history);
230 free(history);
231}
232
d76ed9a9
AS
233static void
234delete_memo_account(void *data)
235{
236 struct memo_account *ma = data;
237
238 while (ma->recvd.used)
239 delete_memo(ma->recvd.list[0]);
240 while (ma->sent.used)
241 delete_memo(ma->sent.list[0]);
242 memoList_clean(&ma->recvd);
243 memoList_clean(&ma->sent);
1b4a47ca 244
245 while (ma->hrecvd.used)
246 delete_history(ma->hrecvd.list[0]);
247 while (ma->hsent.used)
248 delete_history(ma->hsent.list[0]);
249 historyList_clean(&ma->hrecvd);
250 historyList_clean(&ma->hsent);
d76ed9a9
AS
251 free(ma);
252}
253
254void
255do_expire(void)
256{
257 dict_iterator_t it;
258 for (it = dict_first(memos); it; it = iter_next(it)) {
1136f709 259 struct memo_account *account = iter_data(it);
d76ed9a9 260 unsigned int ii;
1136f709 261 for (ii = 0; ii < account->sent.used; ++ii) {
262 struct memo *memo = account->sent.list[ii];
d76ed9a9
AS
263 if ((now - memo->sent) > memoserv_conf.message_expiry) {
264 delete_memo(memo);
265 memosExpired++;
266 ii--;
267 }
268 }
269 }
1b4a47ca 270
271 for (it = dict_first(historys); it; it = iter_next(it)) {
1136f709 272 struct memo_account *account = iter_data(it);
1b4a47ca 273 unsigned int ii;
1136f709 274 for (ii = 0; ii < account->hsent.used; ++ii) {
275 struct history *history = account->hsent.list[ii];
1b4a47ca 276 if ((now - history->sent) > memoserv_conf.message_expiry) {
277 delete_history(history);
278 memosExpired++;
279 ii--;
280 }
281 }
282 }
d76ed9a9
AS
283}
284
285static void
286expire_memos(UNUSED_ARG(void *data))
287{
288 if (memoserv_conf.message_expiry) {
289 do_expire();
290 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
291 }
292}
293
1b4a47ca 294static struct history*
295add_history(time_t sent, struct memo_account *recipient, struct memo_account *sender, unsigned long id)
296{
297 struct history *history;
298
299 history = calloc(1, sizeof(*history));
300 if (!history)
301 return NULL;
302
303 history->id = id;
304 history->recipient = recipient;
305 historyList_append(&recipient->hrecvd, history);
306 history->sender = sender;
307 historyList_append(&sender->hsent, history);
308 history->sent = sent;
309
310 return history;
311}
312
313
d76ed9a9 314static struct memo*
f2e592d3 315add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sender, char *message, int nfrom_read)
d76ed9a9
AS
316{
317 struct memo *memo;
318
319 memo = calloc(1, sizeof(*memo));
320 if (!memo)
321 return NULL;
322
f2e592d3 323 if (nfrom_read) {
324 memo_id++;
325 memo->id = memo_id;
326 }
327
d76ed9a9
AS
328 memo->recipient = recipient;
329 memoList_append(&recipient->recvd, memo);
330 memo->sender = sender;
331 memoList_append(&sender->sent, memo);
332 memo->sent = sent;
333 memo->message = strdup(message);
334 memosSent++;
1136f709 335 memoCount++;
1b4a47ca 336
337 if (nfrom_read)
bde4a337 338 add_history(sent, recipient, sender, memo->id);
1b4a47ca 339
d76ed9a9
AS
340 return memo;
341}
342
343static int
344memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *acct)
345{
346 extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
347 struct userData *dest;
35ca8140 348 unsigned int i = 0, match = 0;
d76ed9a9
AS
349
350 if (!user->handle_info)
351 return 0;
a8692672 352
353 /* Sanity checks here because if the user doesnt have a limit set
354 the limit comes out at like 21233242 if you try and use it. */
355 if (acct->limit > memoserv_conf.limit)
356 acct->limit = memoserv_conf.limit;
357
358 if (acct->recvd.used > acct->limit) {
359 send_message(user, bot, "MSMSG_FULL_INBOX", acct->handle->handle);
360 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
361 return 0;
362 }
363
35ca8140 364 if (acct->handle->ignores->used) {
365 for (i=0; i < acct->handle->ignores->used; i++) {
277ad996 366 if (user_matches_glob(user, acct->handle->ignores->list[i], MATCH_USENICK, 0)) {
35ca8140 367 match = 1;
368 break;
369 }
370 }
371
372 if (match) {
373 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
374 return 0;
375 }
376 }
377
d76ed9a9
AS
378 if (!(acct->flags & MEMO_DENY_NONCHANNEL))
379 return 1;
a8692672 380
d76ed9a9
AS
381 for (dest = acct->handle->channels; dest; dest = dest->u_next)
382 if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
383 return 1;
a8692672 384
d76ed9a9
AS
385 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
386 return 0;
387}
388
389static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
390{
391 unsigned int memoid;
392 if (!isdigit(msgid[0])) {
393 if (ma->recvd.used)
394 reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
395 else
396 reply("MSMSG_NO_MESSAGES");
397 return NULL;
398 }
399 memoid = atoi(msgid);
400 if (memoid >= ma->recvd.used) {
401 reply("MSMSG_NO_SUCH_MEMO");
402 return NULL;
403 }
404 return ma->recvd.list[*id = memoid];
405}
406
407static MODCMD_FUNC(cmd_send)
408{
409 char *message;
87708af4 410 int reciept = 0, inc = 2, email = 0;
d76ed9a9
AS
411 struct handle_info *hi;
412 struct memo_account *ma, *sender;
82794e1b 413 struct memo *memo;
87708af4 414 char subject[128], body[4096];
415 char *estr;
416 const char *netname, *fmt;
d76ed9a9 417
68cb9c2c 418 MEMOSERV_MIN_PARAMS(3);
419
08895577 420 if (checkDefCon(DEFCON_NO_NEW_MEMOS) && !IsOper(user)) {
421 reply("MSMSG_DEFCON_NO_NEW_MEMOS");
422 return 0;
423 }
424
d76ed9a9
AS
425 if (!(hi = modcmd_get_handle_info(user, argv[1])))
426 return 0;
a8692672 427
d76ed9a9
AS
428 if (!(sender = memoserv_get_account(user->handle_info))
429 || !(ma = memoserv_get_account(hi))) {
430 reply("MSG_INTERNAL_FAILURE");
431 return 0;
432 }
a8692672 433
d76ed9a9
AS
434 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
435 return 0;
a8692672 436
a76bcc6a
AS
437 inc = 2; /* Start of message on 3rd ([2]) word */
438 if(argv[2][0] == '-' && argv[2][1] != '-') { /* first word is flags ('-r')*/
439 char *flags = argv[2];
440 inc++; /* Start of message is now 1 word later */
441 for(flags++;*flags;flags++) {
442 switch (*flags) {
443 case 'r':
82794e1b 444 reciept = 1;
445 break;
446
447 default:
a76bcc6a
AS
448 /* Unknown mode. Give an error */
449 reply("MSMSG_UNKNOWN_SEND_FLAG", *flags);
450 return 0;
451 }
82794e1b 452 }
453 }
a76bcc6a
AS
454 else
455 inc = 2; /* Start of message is word 2 */
82794e1b 456
457 message = unsplit_string(argv + inc, argc - inc, NULL);
f2e592d3 458 memo = add_memo(now, ma, sender, message, 1);
2a951803 459 if ((reciept == 1) || (ma->flags & MEMO_ALWAYS_RECIEPTS))
82794e1b 460 memo->reciept = 1;
a8692672 461
d76ed9a9
AS
462 if (ma->flags & MEMO_NOTIFY_NEW) {
463 struct userNode *other;
a8692672 464
d76ed9a9 465 for (other = ma->handle->users; other; other = other->next_authed)
cf33840c 466 send_message_type((ma->flags & MEMO_USE_PRIVMSG)? MSG_TYPE_PRIVMSG : MSG_TYPE_NOTICE, other, memoserv ? memoserv : cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
d76ed9a9 467 }
a8692672 468
87708af4 469 estr = conf_get_data("services/nickserv/email_enabled", RECDB_QSTRING);
470 netname = conf_get_data("server/network", RECDB_QSTRING);
471 email = atoi(estr);
472 if (email && (ma->flags & MEMO_NOTIFY_NEW)) {
473 fmt = handle_find_message(hi, "MSEMAIL_NEWMEMO_SUBJECT");
474 snprintf(subject, sizeof(subject), fmt, netname, memoserv->nick, user->nick);
475
476 fmt = handle_find_message(hi, "MSEMAIL_NEWMEMO_BODY");
477 snprintf(body, sizeof(body), fmt, user->nick, memoserv->nick, message, memoserv->nick, memo_id, netname);
478
1136f709 479 mail_send(memoserv, hi, subject, body, 0);
87708af4 480 }
481
f2e592d3 482 reply("MSMSG_MEMO_SENT", ma->handle->handle, memo_id);
d76ed9a9
AS
483 return 1;
484}
485
486static MODCMD_FUNC(cmd_list)
487{
488 struct memo_account *ma;
489 struct memo *memo;
490 unsigned int ii;
491 char posted[24];
492 struct tm tm;
493
494 if (!(ma = memoserv_get_account(user->handle_info)))
495 return 0;
e3e5ba49 496
d76ed9a9 497 reply("MSMSG_LIST_HEAD");
e3e5ba49 498
499 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
500 reply("MSMSG_BAR");
501
d76ed9a9
AS
502 for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
503 memo = ma->recvd.list[ii];
504 localtime_r(&memo->sent, &tm);
505 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
82794e1b 506 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, memo->reciept ? "(r)" : "", posted);
d76ed9a9
AS
507 }
508 if (ii == 0)
509 reply("MSG_NONE");
510 else if (ii == 15)
511 reply("MSMSG_CLEAN_INBOX", ii);
fc4a50fc 512 else {
d76ed9a9 513 reply("MSMSG_MEMOS_FOUND", ii);
fc4a50fc
AS
514 reply("MSMSG_HOWTO_READ");
515 }
e3e5ba49 516
517 reply("MSMSG_LIST_END");
518
d76ed9a9
AS
519 return 1;
520}
521
cbc5a1a4 522static MODCMD_FUNC(cmd_history)
523{
524 struct memo_account *ma;
1b4a47ca 525 struct history *history;
cbc5a1a4 526 dict_iterator_t it;
527 unsigned int ii = 0;
528 unsigned int cc = 0;
529 char posted[24];
530 struct tm tm;
531
532 if (!(ma = memoserv_get_account(user->handle_info)))
533 return 0;
534
535 reply("MSMSG_HISTORY_HEADER");
536
537 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
538 reply("MSMSG_BAR");
539
1b4a47ca 540 for (it = dict_first(historys); it; it = iter_next(it)) {
cbc5a1a4 541 ma = iter_data(it);
1b4a47ca 542 for (ii = 0; ii < ma->hrecvd.used; ++ii) {
543 history = ma->hrecvd.list[ii];
544 if (!strcasecmp(history->sender->handle->handle, user->handle_info->handle)) {
cbc5a1a4 545 cc++;
1b4a47ca 546 localtime_r(&history->sent, &tm);
cbc5a1a4 547 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
1b4a47ca 548 reply("MSMSG_HISTORY_FORMAT", history->id, history->recipient->handle->handle, posted);
cbc5a1a4 549 }
550 }
551 }
552
553 if (cc == 0)
554 reply("MSG_NONE");
555 else
556 reply("MSMSG_MEMOS_FOUND", cc);
557
558 reply("MSMSG_LIST_END");
559
560 return 1;
561}
562
d76ed9a9
AS
563static MODCMD_FUNC(cmd_read)
564{
565 struct memo_account *ma;
566 unsigned int memoid;
82794e1b 567 int rignore = 0, brk = 0, s = 0;
d76ed9a9 568 struct memo *memo;
8cfd8013 569 struct memo *memob;
d76ed9a9
AS
570 char posted[24];
571 struct tm tm;
572
8108185c 573 if (argc > 2) {
82794e1b 574 char *argtwo = argv[2];
575 while (*argtwo) {
576 switch (*argtwo) {
577 case '-':
578 if (s != 0)
579 brk = 1;
580 break;
581
582 case 'i':
583 if (s > 0)
584 rignore = 1;
585 break;
586
587 default: break;
588 }
589
590 if (brk == 1)
591 break;
592 else {
593 s++;
594 argtwo++;
595 }
596 }
597 }
598
928e5a35
AS
599 if (!(ma = memoserv_get_account(user->handle_info)))
600 return 0;
601
602 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
603 return 0;
604
d76ed9a9
AS
605 localtime_r(&memo->sent, &tm);
606 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
82794e1b 607
d76ed9a9
AS
608 reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
609 send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
610 memo->is_read = 1;
8cfd8013 611 memob = memo;
82794e1b 612
2a951803 613 if (ma->flags & MEMO_IGNORE_RECIEPTS)
614 rignore = 1;
615
82794e1b 616 if (memo->reciept == 1) {
617 memo->reciept = 0;
618 reply("MSMSG_MEMO_RECIEPT", rignore ? "ignoring" : "sending");
619 if (rignore == 0) {
620 struct memo_account *ma;
621 struct memo_account *sender;
622 char content[MAXLEN];
623
624 ma = memoserv_get_account(user->handle_info);
625 sender = memoserv_get_account(memo->sender->handle);
626
627 sprintf(content, "%s has read your memo dated %s.", ma->handle->handle, posted);
628
f2e592d3 629 memo = add_memo(now, sender, ma, content, 1);
8cfd8013 630 reply("MSMSG_MEMO_SENT", memob->sender->handle->handle, memo_id);
82794e1b 631
632 if (sender->flags & MEMO_NOTIFY_NEW) {
633 struct userNode *other;
634
635 for (other = sender->handle->users; other; other = other->next_authed)
fc4a50fc 636 send_message_type((ma->flags & MEMO_USE_PRIVMSG)? MSG_TYPE_PRIVMSG : MSG_TYPE_NOTICE, other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", ma->handle->handle);
82794e1b 637 }
638
639
640 }
641 }
d76ed9a9
AS
642 return 1;
643}
644
645static MODCMD_FUNC(cmd_delete)
646{
647 struct memo_account *ma;
648 struct memo *memo;
649 unsigned int memoid;
650
68cb9c2c 651 MEMOSERV_MIN_PARAMS(2);
652
d76ed9a9
AS
653 if (!(ma = memoserv_get_account(user->handle_info)))
654 return 0;
655 if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
656 if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
657 reply("MSMSG_USE_CONFIRM");
658 return 0;
659 }
660 while (ma->recvd.used)
661 delete_memo(ma->recvd.list[0]);
662 reply("MSMSG_DELETED_ALL");
663 return 1;
664 }
665
666 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
667 return 0;
668 delete_memo(memo);
669 reply("MSMSG_MEMO_DELETED", memoid);
670 return 1;
671}
672
f2e592d3 673static MODCMD_FUNC(cmd_cancel)
674{
675 unsigned long id;
676 unsigned int ii;
677 dict_iterator_t it;
678 struct memo *memo;
679 struct memo_account *ma;
680
68cb9c2c 681 MEMOSERV_MIN_PARAMS(2);
682
f2e592d3 683 if (isdigit(argv[1][0])) {
684 id = strtoul(argv[1], NULL, 0);
685 } else {
686 reply("MSMSG_MEMO_CANCEL_NUMBER");
687 return 0;
688 }
689
690 for (it = dict_first(memos); it; it = iter_next(it)) {
691 ma = iter_data(it);
692 for (ii = 0; ii < ma->recvd.used; ++ii) {
693 memo = ma->recvd.list[ii];
694
695 if (id == memo->id) {
696 if (!strcasecmp(memo->sender->handle->handle, user->handle_info->handle)) {
697 if (memo->is_read) {
698 reply("MSMSG_MEMO_READ", id);
699 return 0;
700 } else {
701 delete_memo(memo);
702 reply("MSMSG_MEMO_DELETED", id);
703 return 1;
704 }
705 } else {
706 reply("MSMSG_MEMO_DONT_OWN", id);
707 return 0;
708 }
709 }
710 }
711 }
712
713 reply("MSMSG_MEMO_CANT_LOCATE", id);
714 return 0;
715}
716
d76ed9a9
AS
717static MODCMD_FUNC(cmd_expire)
718{
719 unsigned long old_expired = memosExpired;
720 do_expire();
721 reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
722 return 1;
723}
724
725static MODCMD_FUNC(cmd_expiry)
726{
727 char interval[INTERVALLEN];
728
729 if (!memoserv_conf.message_expiry) {
730 reply("MSMSG_EXPIRY_OFF");
731 return 1;
732 }
733
734 intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
735 reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
736 return 1;
737}
738
e3e5ba49 739
740static void
c092fcad 741set_list(struct svccmd *cmd, struct userNode *user, struct handle_info *hi, int override)
e3e5ba49 742{
743 option_func_t *opt;
d9abe201 744 unsigned int i;
fc4a50fc 745 char *set_display[] = {"AUTHNOTIFY", "NEWNOTIFY", "PRIVMSG", "PRIVATE", "LIMIT",
2a951803 746 "IGNORERECIEPTS", "SENDRECIEPTS"};
e3e5ba49 747
c092fcad
AS
748 reply("MSMSG_SET_OPTIONS");
749 reply("MSMSG_BAR");
e3e5ba49 750
751 /* Do this so options are presented in a consistent order. */
d9abe201 752 for (i = 0; i < ArrayLength(set_display); ++i)
753 if ((opt = dict_find(memoserv_opt_dict, set_display[i], NULL)))
c092fcad
AS
754 opt(cmd, user, hi, override, 0, NULL);
755 reply("MSMSG_SET_OPTIONS_END");
e3e5ba49 756}
757
758static MODCMD_FUNC(cmd_set)
759{
760 struct handle_info *hi;
761 option_func_t *opt;
762
763 hi = user->handle_info;
d9abe201 764 if (argc < 2) {
c092fcad 765 set_list(cmd, user, hi, 0);
e3e5ba49 766 return 1;
767 }
768
d9abe201 769 if (!(opt = dict_find(memoserv_opt_dict, argv[1], NULL))) {
e3e5ba49 770 reply("MSMSG_INVALID_OPTION", argv[1]);
771 return 0;
772 }
d9abe201 773
c092fcad 774 return opt(cmd, user, hi, 0, argc-1, argv+1);
e3e5ba49 775}
776
777static MODCMD_FUNC(cmd_oset)
778{
779 struct handle_info *hi;
780 option_func_t *opt;
781
68cb9c2c 782 MEMOSERV_MIN_PARAMS(2);
eb43ca8c 783
1136f709 784 if (!(hi = get_victim_oper(user, argv[1])))
d9abe201 785 return 0;
e3e5ba49 786
d9abe201 787 if (argc < 3) {
c092fcad 788 set_list(cmd, user, hi, 0);
e3e5ba49 789 return 1;
790 }
791
d9abe201 792 if (!(opt = dict_find(memoserv_opt_dict, argv[2], NULL))) {
e3e5ba49 793 reply("MSMSG_INVALID_OPTION", argv[2]);
794 return 0;
795 }
796
c092fcad 797 return opt(cmd, user, hi, 1, argc-2, argv+2);
e3e5ba49 798}
799
fc4a50fc 800static OPTION_FUNC(opt_newnotify)
d76ed9a9
AS
801{
802 struct memo_account *ma;
803 char *choice;
804
d9abe201 805 if (!(ma = memoserv_get_account(hi)))
d76ed9a9
AS
806 return 0;
807 if (argc > 1) {
808 choice = argv[1];
809 if (enabled_string(choice)) {
810 ma->flags |= MEMO_NOTIFY_NEW;
811 } else if (disabled_string(choice)) {
812 ma->flags &= ~MEMO_NOTIFY_NEW;
813 } else {
c092fcad 814 reply("MSMSG_INVALID_BINARY", choice);
d76ed9a9
AS
815 return 0;
816 }
817 }
818
819 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
fc4a50fc
AS
820 reply("MSMSG_SET_NEWNOTIFY", choice);
821 return 1;
822}
823
824static OPTION_FUNC(opt_privmsg)
825{
826 struct memo_account *ma;
827 char *choice;
828
829 if (!(ma = memoserv_get_account(hi)))
830 return 0;
831 if (argc > 1) {
832 choice = argv[1];
833 if (enabled_string(choice)) {
834 ma->flags |= MEMO_USE_PRIVMSG;
835 } else if (disabled_string(choice)) {
836 ma->flags &= ~MEMO_USE_PRIVMSG;
837 } else {
838 reply("MSMSG_INVALID_BINARY", choice);
839 return 0;
840 }
841 }
842 choice = (ma->flags & MEMO_USE_PRIVMSG) ? "on" : "off";
843 reply("MSMSG_SET_PRIVMSG", choice);
d76ed9a9
AS
844 return 1;
845}
846
d9abe201 847static OPTION_FUNC(opt_authnotify)
d76ed9a9
AS
848{
849 struct memo_account *ma;
850 char *choice;
851
d9abe201 852 if (!(ma = memoserv_get_account(hi)))
d76ed9a9
AS
853 return 0;
854 if (argc > 1) {
855 choice = argv[1];
856 if (enabled_string(choice)) {
857 ma->flags |= MEMO_NOTIFY_LOGIN;
858 } else if (disabled_string(choice)) {
859 ma->flags &= ~MEMO_NOTIFY_LOGIN;
860 } else {
c092fcad 861 reply("MSMSG_INVALID_BINARY", choice);
d76ed9a9
AS
862 return 0;
863 }
864 }
865
866 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
c092fcad 867 reply("MSMSG_SET_AUTHNOTIFY", choice);
d76ed9a9
AS
868 return 1;
869}
870
2a951803 871static OPTION_FUNC(opt_ignorereciepts)
872{
873 struct memo_account *ma;
874 char *choice;
875
876 if (!(ma = memoserv_get_account(hi)))
877 return 0;
878 if (argc > 1) {
879 choice = argv[1];
880 if (enabled_string(choice)) {
881 ma->flags |= MEMO_IGNORE_RECIEPTS;
882 } else if (disabled_string(choice)) {
883 ma->flags &= ~MEMO_IGNORE_RECIEPTS;
884 } else {
c092fcad 885 reply("MSMSG_INVALID_BINARY", choice);
2a951803 886 return 0;
887 }
888 }
889
890 choice = (ma->flags & MEMO_IGNORE_RECIEPTS) ? "on" : "off";
c092fcad 891 reply("MSMSG_SET_IGNORERECIEPTS", choice);
2a951803 892 return 1;
893}
894
895static OPTION_FUNC(opt_sendreciepts)
896{
897 struct memo_account *ma;
898 char *choice;
899
900 if (!(ma = memoserv_get_account(hi)))
901 return 0;
902 if (argc > 1) {
903 choice = argv[1];
904 if (enabled_string(choice)) {
905 ma->flags |= MEMO_ALWAYS_RECIEPTS;
906 } else if (disabled_string(choice)) {
907 ma->flags &= ~MEMO_ALWAYS_RECIEPTS;
908 } else {
c092fcad 909 reply("MSMSG_INVALID_BINARY", choice);
2a951803 910 return 0;
911 }
912 }
913
914 choice = (ma->flags & MEMO_ALWAYS_RECIEPTS) ? "on" : "off";
c092fcad 915 reply("MSMSG_SET_SENDRECIEPTS", choice);
2a951803 916 return 1;
917}
918
d9abe201 919static OPTION_FUNC(opt_private)
d76ed9a9
AS
920{
921 struct memo_account *ma;
922 char *choice;
923
d9abe201 924 if (!(ma = memoserv_get_account(hi)))
d76ed9a9
AS
925 return 0;
926 if (argc > 1) {
927 choice = argv[1];
928 if (enabled_string(choice)) {
929 ma->flags |= MEMO_DENY_NONCHANNEL;
930 } else if (disabled_string(choice)) {
931 ma->flags &= ~MEMO_DENY_NONCHANNEL;
932 } else {
c092fcad 933 reply("MSMSG_INVALID_BINARY", choice);
d76ed9a9
AS
934 return 0;
935 }
936 }
937
938 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
c092fcad 939 reply("MSMSG_SET_PRIVATE", choice);
d76ed9a9
AS
940 return 1;
941}
942
d9abe201 943static OPTION_FUNC(opt_limit)
acf3c6d5 944{
945 struct memo_account *ma;
a8692672 946 unsigned int choice;
acf3c6d5 947
d9abe201 948 if (!(ma = memoserv_get_account(hi)))
acf3c6d5 949 return 0;
950 if (argc > 1) {
951 choice = atoi(argv[1]);
952 if (choice > memoserv_conf.limit)
953 choice = memoserv_conf.limit;
954
955 ma->limit = choice;
956 }
957
c092fcad 958 reply("MSMSG_SET_LIMIT", ma->limit);
acf3c6d5 959 return 1;
960}
961
d76ed9a9
AS
962static MODCMD_FUNC(cmd_status)
963{
1b4a47ca 964 struct memo_account *ma;
965 dict_iterator_t it;
966 int mc = 0, hc = 0;
967 unsigned int ii;
968
969 for (it = dict_first(memos); it; it = iter_next(it)) {
970 ma = iter_data(it);
971 for (ii = 0; ii < ma->recvd.used; ++ii)
972 mc++;
973 }
974
975 for (it = dict_first(historys); it; it = iter_next(it)) {
976 ma = iter_data(it);
977 for (ii = 0; ii < ma->hrecvd.used; ++ii)
978 hc++;
979 }
980
981 reply("MSMSG_STATUS_HIST_TOTAL", hc);
1136f709 982 reply("MSMSG_STATUS_TOTAL", memoCount);
d76ed9a9
AS
983 reply("MSMSG_STATUS_EXPIRED", memosExpired);
984 reply("MSMSG_STATUS_SENT", memosSent);
985 return 1;
986}
987
988static void
989memoserv_conf_read(void)
990{
991 dict_t conf_node;
992 const char *str;
993
994 str = "modules/memoserv";
995 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
996 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
997 return;
998 }
999
d9abe201 1000 str = database_get_data(conf_node, "limit", RECDB_QSTRING);
a8370a20 1001 memoserv_conf.limit = str ? atoi(str) : 50;
d9abe201 1002
d76ed9a9
AS
1003 str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
1004 memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
1005}
1006
1007static int
34a9e19a 1008memoserv_user_read(const char *key, struct record_data *hir)
1009{
1010 char *str;
1011 struct memo_account *ma;
1012 struct handle_info *hi;
1013
1014 if (!(hi = get_handle_info(key)))
1015 return 0;
1016
1017 ma = dict_find(memos, hi->handle, NULL);
1018 if (ma)
1019 return 0;
1020
1021
1022 ma = calloc(1, sizeof(*ma));
1023 if (!ma)
1024 return 0;
1025
1026 ma->handle = hi;
1027
1028 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
1029 if (!str) {
1030 log_module(MS_LOG, LOG_ERROR, "Flags not present in memo %s; skipping", key);
1031 return 0;
1032 }
1033 ma->flags = strtoul(str, NULL, 0);
1034
1035 str = database_get_data(hir->d.object, KEY_LIMIT, RECDB_QSTRING);
1036 if (!str) {
1037 log_module(MS_LOG, LOG_ERROR, "Limit not present in memo %s; skipping", key);
1038 return 0;
1039 }
1040 ma->limit = strtoul(str, NULL, 0);
1041
1042 dict_insert(memos, ma->handle->handle, ma);
1b4a47ca 1043 dict_insert(historys, ma->handle->handle, ma);
34a9e19a 1044
1045 return 0;
1046}
1047
1048static int
1049memoserv_memo_read(const char *key, struct record_data *hir)
d76ed9a9
AS
1050{
1051 char *str;
1052 struct handle_info *sender, *recipient;
d76ed9a9 1053 struct memo *memo;
f2e592d3 1054 unsigned long id;
d76ed9a9
AS
1055 time_t sent;
1056
34a9e19a 1057 if (hir->type != RECDB_OBJECT) {
1058 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, key);
1059 return 0;
1060 }
d76ed9a9 1061
34a9e19a 1062 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
1063 log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", key);
1064 return 0;
1065 }
d76ed9a9 1066
34a9e19a 1067 sent = atoi(str);
d76ed9a9 1068
f2e592d3 1069 if (!(str = database_get_data(hir->d.object, KEY_ID, RECDB_QSTRING))) {
1070 log_module(MS_LOG, LOG_ERROR, "ID sent not present in memo %s; skipping", key);
1071 return 0;
1072 }
1073 id = strtoul(str, NULL, 0);
1074 if (id > memo_id)
1075 memo_id = id;
1076
34a9e19a 1077 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
1078 log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", key);
1079 return 0;
1080 } else if (!(recipient = get_handle_info(str))) {
1081 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, key);
1082 return 0;
1083 }
d76ed9a9 1084
34a9e19a 1085 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
1086 log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", key);
1087 return 0;
1088 } else if (!(sender = get_handle_info(str))) {
1089 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, key);
1090 return 0;
1091 }
82794e1b 1092
34a9e19a 1093 if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
1094 log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", key);
1095 return 0;
d76ed9a9 1096 }
34a9e19a 1097
f2e592d3 1098 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str, 0);
34a9e19a 1099 if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
1100 memo->is_read = 1;
1101
1102 if ((str = database_get_data(hir->d.object, KEY_RECIEPT, RECDB_QSTRING)))
1103 memo->reciept = 1;
1104
f2e592d3 1105 memo->id = id;
1106
34a9e19a 1107 return 0;
1108}
1109
1b4a47ca 1110static int
1111memoserv_history_read(const char *key, struct record_data *hir)
1112{
1113 char *str;
1114 struct handle_info *sender, *recipient;
1b4a47ca 1115 unsigned long id;
1116 time_t sent;
1117
1118 if (hir->type != RECDB_OBJECT) {
1119 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, key);
1120 return 0;
1121 }
1122
1123 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
1124 log_module(MS_LOG, LOG_ERROR, "Date sent not present in history %s; skipping", key);
1125 return 0;
1126 }
1127
1128 sent = atoi(str);
1129
1130 if (!(str = database_get_data(hir->d.object, KEY_ID, RECDB_QSTRING))) {
1131 log_module(MS_LOG, LOG_ERROR, "ID sent not present in history %s; skipping", key);
1132 return 0;
1133 }
1134 id = strtoul(str, NULL, 0);
1135
1136 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
1137 log_module(MS_LOG, LOG_ERROR, "Recipient not present in history %s; skipping", key);
1138 return 0;
1139 } else if (!(recipient = get_handle_info(str))) {
1140 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in history %s; skipping", str, key);
1141 return 0;
1142 }
1143
1144 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
1145 log_module(MS_LOG, LOG_ERROR, "Sender not present in history %s; skipping", key);
1146 return 0;
1147 } else if (!(sender = get_handle_info(str))) {
1148 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in history %s; skipping", str, key);
1149 return 0;
1150 }
1151
bde4a337 1152 add_history(sent, memoserv_get_account(recipient), memoserv_get_account(sender), id);
1b4a47ca 1153
1154 return 0;
1155}
1156
34a9e19a 1157static int
1158memoserv_saxdb_read(struct dict *database)
1159{
1160 struct dict *section;
1161 dict_iterator_t it;
1162
1163 if((section = database_get_data(database, KEY_MAIN_ACCOUNTS, RECDB_OBJECT)))
1164 for(it = dict_first(section); it; it = iter_next(it))
1165 memoserv_user_read(iter_key(it), iter_data(it));
1166
1167 if((section = database_get_data(database, KEY_MAIN_MEMOS, RECDB_OBJECT)))
1168 for(it = dict_first(section); it; it = iter_next(it))
1169 memoserv_memo_read(iter_key(it), iter_data(it));
1170
1b4a47ca 1171 if((section = database_get_data(database, KEY_MAIN_HISTORY, RECDB_OBJECT)))
1172 for(it = dict_first(section); it; it = iter_next(it))
1173 memoserv_history_read(iter_key(it), iter_data(it));
1174
34a9e19a 1175 return 0;
1176}
1177
1178static int
1179memoserv_write_users(struct saxdb_context *ctx, struct memo_account *ma)
1180{
1181 saxdb_start_record(ctx, ma->handle->handle, 0);
1182
1183 saxdb_write_int(ctx, KEY_FLAGS, ma->flags);
1184 saxdb_write_int(ctx, KEY_LIMIT, ma->limit);
1185
1186 saxdb_end_record(ctx);
1187 return 0;
1188}
1189
1190static int
1191memoserv_write_memos(struct saxdb_context *ctx, struct memo *memo)
1192{
fc4a50fc 1193 char str[20];
34a9e19a 1194
fc4a50fc
AS
1195 memset(str, '\0', sizeof(str));
1196 saxdb_start_record(ctx, inttobase64(str, memo->id, sizeof(str)-1), 0);
34a9e19a 1197
1198 saxdb_write_int(ctx, KEY_SENT, memo->sent);
f2e592d3 1199 saxdb_write_int(ctx, KEY_ID, memo->id);
34a9e19a 1200 saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
1201 saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
1202 saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
1203
1204 if (memo->is_read)
1205 saxdb_write_int(ctx, KEY_READ, 1);
1206
1207 if (memo->reciept)
1208 saxdb_write_int(ctx, KEY_RECIEPT, 1);
1209
1210 saxdb_end_record(ctx);
d76ed9a9
AS
1211 return 0;
1212}
1213
1b4a47ca 1214static int
1215memoserv_write_history(struct saxdb_context *ctx, struct history *history)
1216{
fc4a50fc 1217 char str[20];
1b4a47ca 1218
fc4a50fc
AS
1219 memset(str, '\0', sizeof(str));
1220 saxdb_start_record(ctx, inttobase64(str, history->id, sizeof(str)-1), 0);
1b4a47ca 1221
1222 saxdb_write_int(ctx, KEY_SENT, history->sent);
1223 saxdb_write_int(ctx, KEY_ID, history->id);
1224 saxdb_write_string(ctx, KEY_RECIPIENT, history->recipient->handle->handle);
1225 saxdb_write_string(ctx, KEY_FROM, history->sender->handle->handle);
1226
1227 saxdb_end_record(ctx);
1228 return 0;
1229}
1230
d76ed9a9
AS
1231static int
1232memoserv_saxdb_write(struct saxdb_context *ctx)
1233{
1234 dict_iterator_t it;
1235 struct memo_account *ma;
1236 struct memo *memo;
1b4a47ca 1237 struct history *history;
34a9e19a 1238 unsigned int ii;
d76ed9a9 1239
34a9e19a 1240 /* Accounts */
1241 saxdb_start_record(ctx, KEY_MAIN_ACCOUNTS, 1);
1242 for (it = dict_first(memos); it; it = iter_next(it)) {
1243 ma = iter_data(it);
1244 memoserv_write_users(ctx, ma);
1245 }
1246 saxdb_end_record(ctx);
1247
1b4a47ca 1248 /* Memos */
34a9e19a 1249 saxdb_start_record(ctx, KEY_MAIN_MEMOS, 1);
d76ed9a9
AS
1250 for (it = dict_first(memos); it; it = iter_next(it)) {
1251 ma = iter_data(it);
1252 for (ii = 0; ii < ma->recvd.used; ++ii) {
1253 memo = ma->recvd.list[ii];
34a9e19a 1254 memoserv_write_memos(ctx, memo);
d76ed9a9
AS
1255 }
1256 }
34a9e19a 1257 saxdb_end_record(ctx);
1258
1b4a47ca 1259 /* History */
1260 saxdb_start_record(ctx, KEY_MAIN_HISTORY, 1);
1261 for (it = dict_first(historys); it; it = iter_next(it)) {
1262 ma = iter_data(it);
1263 for (ii = 0; ii < ma->hrecvd.used; ++ii) {
1264 history = ma->hrecvd.list[ii];
1265 memoserv_write_history(ctx, history);
1266 }
1267 }
1268 saxdb_end_record(ctx);
1269
d76ed9a9
AS
1270 return 0;
1271}
1272
1273static void
30874d66 1274memoserv_cleanup(UNUSED_ARG(void *extra))
d76ed9a9
AS
1275{
1276 dict_delete(memos);
1b4a47ca 1277 dict_delete(historys);
d76ed9a9
AS
1278}
1279
1280static void
81ac4787 1281memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle), UNUSED_ARG(void *extra))
d76ed9a9
AS
1282{
1283 unsigned int ii, unseen;
1284 struct memo_account *ma;
1285 struct memo *memo;
1286
7637f48f 1287 if (!user->uplink->burst) {
1288 if (!(ma = memoserv_get_account(user->handle_info))
1289 || !(ma->flags & MEMO_NOTIFY_LOGIN))
1290 return;
1291 for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
1292 memo = ma->recvd.list[ii];
1293 if (!memo->is_read)
1294 unseen++;
1295 }
68cb9c2c 1296 if (ma->recvd.used && memoserv)
fc4a50fc 1297 if(unseen) send_message_type((ma->flags & MEMO_USE_PRIVMSG)? 1 : 0, user, memoserv, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
d76ed9a9 1298 }
d76ed9a9
AS
1299}
1300
1301static void
3070719a 1302memoserv_rename_account(struct handle_info *hi, const char *old_handle, UNUSED_ARG(void *extra))
d76ed9a9
AS
1303{
1304 struct memo_account *ma;
1305 if (!(ma = dict_find(memos, old_handle, NULL)))
1306 return;
1307 dict_remove2(memos, old_handle, 1);
1308 dict_insert(memos, hi->handle, ma);
1b4a47ca 1309
1310 dict_remove2(historys, old_handle, 1);
1311 dict_insert(historys, hi->handle, ma);
d76ed9a9
AS
1312}
1313
1314static void
974d3831 1315memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle, UNUSED_ARG(void *extra))
d76ed9a9
AS
1316{
1317 dict_remove(memos, handle->handle);
1b4a47ca 1318 dict_remove(historys, handle->handle);
d76ed9a9
AS
1319}
1320
1321int
1322memoserv_init(void)
1323{
1324 MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
1325 memos = dict_new();
1b4a47ca 1326 historys = dict_new();
d76ed9a9 1327 dict_set_free_data(memos, delete_memo_account);
81ac4787 1328 reg_auth_func(memoserv_check_messages, NULL);
3070719a 1329 reg_handle_rename_func(memoserv_rename_account, NULL);
974d3831 1330 reg_unreg_func(memoserv_unreg_account, NULL);
d76ed9a9 1331 conf_register_reload(memoserv_conf_read);
30874d66 1332 reg_exit_func(memoserv_cleanup, NULL);
d76ed9a9
AS
1333 saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
1334
1335 memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
cbc5a1a4 1336 modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
1337 modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
1338 modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
1339 modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
1340 modcmd_register(memoserv_module, "cancel", cmd_cancel, 2, MODCMD_REQUIRE_AUTHED, NULL);
1341 modcmd_register(memoserv_module, "history", cmd_history, 1, MODCMD_REQUIRE_AUTHED, NULL);
1342 modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
1343 modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
1344 modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
1345 modcmd_register(memoserv_module, "set", cmd_set, 1, MODCMD_REQUIRE_AUTHED, NULL);
1346 modcmd_register(memoserv_module, "oset", cmd_oset, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
e3e5ba49 1347
1348 memoserv_opt_dict = dict_new();
1349 dict_insert(memoserv_opt_dict, "AUTHNOTIFY", opt_authnotify);
fc4a50fc
AS
1350 dict_insert(memoserv_opt_dict, "NEWNOTIFY", opt_newnotify);
1351 dict_insert(memoserv_opt_dict, "PRIVMSG", opt_privmsg);
e3e5ba49 1352 dict_insert(memoserv_opt_dict, "PRIVATE", opt_private);
2a951803 1353 dict_insert(memoserv_opt_dict, "IGNORERECIEPTS", opt_ignorereciepts);
1354 dict_insert(memoserv_opt_dict, "SENDRECIEPTS", opt_sendreciepts);
d9abe201 1355 dict_insert(memoserv_opt_dict, "LIMIT", opt_limit);
e3e5ba49 1356
d76ed9a9
AS
1357 message_register_table(msgtab);
1358
1359 if (memoserv_conf.message_expiry)
1360 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
e3e5ba49 1361
d76ed9a9
AS
1362 return 1;
1363}
1364
1365int
1366memoserv_finalize(void) {
e3e5ba49 1367 struct chanNode *chan;
1368 unsigned int i;
d76ed9a9
AS
1369 dict_t conf_node;
1370 const char *str;
1371
1372 str = "modules/memoserv";
1373 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
1374 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
1375 return 0;
1376 }
1377
1378 str = database_get_data(conf_node, "bot", RECDB_QSTRING);
68cb9c2c 1379 if (str) {
1380 memoserv = memoserv_conf.bot;
14f0e274 1381 const char *modes = conf_get_data("modules/memoserv/modes", RECDB_QSTRING);
1136f709 1382 memoserv = AddLocalUser(str, str, NULL, "User-User Memorandum Services", modes);
7acc75ee 1383 service_register(memoserv);
6a64b9ce 1384 } else {
c092fcad 1385 log_module(MS_LOG, LOG_ERROR, "database_get_data for memoserv_conf.bot failed!");
6a64b9ce 1386 exit(1);
1387 }
e3e5ba49 1388
68cb9c2c 1389 if (autojoin_channels && memoserv) {
e3e5ba49 1390 for (i = 0; i < autojoin_channels->used; i++) {
1391 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
68cb9c2c 1392 AddChannelUser(memoserv, chan)->modes |= MODE_CHANOP;
e3e5ba49 1393 }
1394 }
1395
d76ed9a9
AS
1396 return 1;
1397}