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