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