]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-memoserv.c
some read recipient support
[irc/evilnet/x3.git] / src / mod-memoserv.c
1 /* mod-memoserv.c - MemoServ module for srvx
2 * Copyright 2003-2004 Martijn Smit and srvx Development Team
3 *
4 * This file is part of x3.
5 *
6 * x3 is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with srvx; if not, write to the Free Software Foundation,
18 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
19 */
20
21 /*
22 * /msg opserv bind nickserv * *memoserv.*
23 *
24 * If you want a dedicated MemoServ bot, make sure the service control
25 * commands are bound to OpServ:
26 * /msg opserv bind opserv service *modcmd.joiner
27 * /msg opserv bind opserv service\ add *modcmd.service\ add
28 * /msg opserv bind opserv service\ rename *modcmd.service\ rename
29 * /msg opserv bind opserv service\ trigger *modcmd.service\ trigger
30 * /msg opserv bind opserv service\ remove *modcmd.service\ remove
31 * Add the bot:
32 * /msg opserv service add MemoServ User-to-user Memorandum Service
33 * /msg opserv bind memoserv help *modcmd.help
34 * Restart srvx with the updated conf file (as above, butwith "bot"
35 * "MemoServ"), and bind the commands to it:
36 * /msg opserv bind memoserv * *memoserv.*
37 * /msg opserv bind memoserv set *modcmd.joiner
38 */
39
40 #include "chanserv.h"
41 #include "conf.h"
42 #include "modcmd.h"
43 #include "nickserv.h"
44 #include "saxdb.h"
45 #include "timeq.h"
46
47 #define KEY_SENT "sent"
48 #define KEY_RECIPIENT "to"
49 #define KEY_FROM "from"
50 #define KEY_MESSAGE "msg"
51 #define KEY_READ "read"
52 #define KEY_RECIEPT "reciept"
53
54 static const struct message_entry msgtab[] = {
55 { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
56 { "MSMSG_MEMO_SENT", "Message sent to $b%s$b." },
57 { "MSMSG_NO_MESSAGES", "You have no messages." },
58 { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
59 { "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." },
60 { "MSMSG_LIST_HEAD", "$bID$b $bFrom$b $bTime Sent$b" },
61 { "MSMSG_LIST_FORMAT", "%-2u %s $b%s$b %s" },
62 { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
63 { "MSMSG_MEMO_RECIEPT", "$bRead Reciept$b requested, %s." },
64 { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
65 { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
66 { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
67 { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
68 { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
69 { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
70 { "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." },
71 { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. /msg $S LIST" },
72 { "MSMSG_FULL_INBOX", "$b%s$b cannot recieve anymore memos as their inbox is full" },
73 { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
74 { "MSMSG_USE_CONFIRM", "Please use /msg $S DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
75
76 { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
77 { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
78 { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
79
80 { "MSMSG_INVALID_OPTION", "$b%s$b is not a valid option." },
81 { "MSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
82 { "MSMSG_SET_NOTIFY", "$bNotify: $b %s" },
83 { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify: $b %s" },
84 { "MSMSG_SET_PRIVATE", "$bPrivate: $b %s" },
85 { "MSMSG_SET_LIMIT", "$bLimit: $b %d" },
86 { "MSMSG_SET_OPTIONS", "$bMessaging Options$b" },
87 { "MSMSG_SET_OPTIONS_END", "-------------End of Options-------------" },
88
89 { "MSMSG_LIST_END", "--------------End of Memos--------------" },
90 { "MSMSG_BAR", "----------------------------------------"},
91
92 { NULL, NULL }
93 };
94
95 struct memo {
96 struct memo_account *recipient;
97 struct memo_account *sender;
98 char *message;
99 time_t sent;
100 unsigned int is_read : 1;
101 unsigned int reciept : 1;
102 };
103
104 DECLARE_LIST(memoList, struct memo*);
105 DEFINE_LIST(memoList, struct memo*);
106
107 /* memo_account.flags fields */
108 #define MEMO_NOTIFY_NEW 1
109 #define MEMO_NOTIFY_LOGIN 2
110 #define MEMO_DENY_NONCHANNEL 4
111
112 struct memo_account {
113 struct handle_info *handle;
114 unsigned int flags;
115 unsigned int limit;
116 struct memoList sent;
117 struct memoList recvd;
118 };
119
120 static struct {
121 struct userNode *bot;
122 int message_expiry;
123 unsigned int limit;
124 } memoserv_conf;
125
126 #define MEMOSERV_FUNC(NAME) MODCMD_FUNC(NAME)
127 #define OPTION_FUNC(NAME) int NAME(struct userNode *user, struct handle_info *hi, UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
128 typedef OPTION_FUNC(option_func_t);
129
130 extern struct string_list *autojoin_channels;
131 const char *memoserv_module_deps[] = { NULL };
132 static struct module *memoserv_module;
133 static struct log_type *MS_LOG;
134 static unsigned long memosSent, memosExpired;
135 static struct dict *memos; /* memo_account->handle->handle -> memo_account */
136 static dict_t memoserv_opt_dict; /* contains option_func_t* */
137
138 static struct memo_account *
139 memoserv_get_account(struct handle_info *hi)
140 {
141 struct memo_account *ma;
142 if (!hi)
143 return NULL;
144 ma = dict_find(memos, hi->handle, NULL);
145 if (ma)
146 return ma;
147 ma = calloc(1, sizeof(*ma));
148 if (!ma)
149 return ma;
150 ma->handle = hi;
151 ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
152 dict_insert(memos, ma->handle->handle, ma);
153 return ma;
154 }
155
156 static void
157 delete_memo(struct memo *memo)
158 {
159 memoList_remove(&memo->recipient->recvd, memo);
160 memoList_remove(&memo->sender->sent, memo);
161 free(memo->message);
162 free(memo);
163 }
164
165 static void
166 delete_memo_account(void *data)
167 {
168 struct memo_account *ma = data;
169
170 while (ma->recvd.used)
171 delete_memo(ma->recvd.list[0]);
172 while (ma->sent.used)
173 delete_memo(ma->sent.list[0]);
174 memoList_clean(&ma->recvd);
175 memoList_clean(&ma->sent);
176 free(ma);
177 }
178
179 void
180 do_expire(void)
181 {
182 dict_iterator_t it;
183 for (it = dict_first(memos); it; it = iter_next(it)) {
184 struct memo_account *acct = iter_data(it);
185 unsigned int ii;
186 for (ii = 0; ii < acct->sent.used; ++ii) {
187 struct memo *memo = acct->sent.list[ii];
188 if ((now - memo->sent) > memoserv_conf.message_expiry) {
189 delete_memo(memo);
190 memosExpired++;
191 ii--;
192 }
193 }
194 }
195 }
196
197 static void
198 expire_memos(UNUSED_ARG(void *data))
199 {
200 if (memoserv_conf.message_expiry) {
201 do_expire();
202 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
203 }
204 }
205
206 static struct memo*
207 add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sender, char *message)
208 {
209 struct memo *memo;
210
211 memo = calloc(1, sizeof(*memo));
212 if (!memo)
213 return NULL;
214
215 memo->recipient = recipient;
216 memoList_append(&recipient->recvd, memo);
217 memo->sender = sender;
218 memoList_append(&sender->sent, memo);
219 memo->sent = sent;
220 memo->message = strdup(message);
221 memosSent++;
222 return memo;
223 }
224
225 static int
226 memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *acct)
227 {
228 extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
229 struct userData *dest;
230
231 if (!user->handle_info)
232 return 0;
233
234 /* Sanity checks here because if the user doesnt have a limit set
235 the limit comes out at like 21233242 if you try and use it. */
236 if (acct->limit > memoserv_conf.limit)
237 acct->limit = memoserv_conf.limit;
238
239 if (acct->recvd.used > acct->limit) {
240 send_message(user, bot, "MSMSG_FULL_INBOX", acct->handle->handle);
241 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
242 return 0;
243 }
244
245 if (!(acct->flags & MEMO_DENY_NONCHANNEL))
246 return 1;
247
248 for (dest = acct->handle->channels; dest; dest = dest->u_next)
249 if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
250 return 1;
251
252 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
253 return 0;
254 }
255
256 static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
257 {
258 unsigned int memoid;
259 if (!isdigit(msgid[0])) {
260 if (ma->recvd.used)
261 reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
262 else
263 reply("MSMSG_NO_MESSAGES");
264 return NULL;
265 }
266 memoid = atoi(msgid);
267 if (memoid >= ma->recvd.used) {
268 reply("MSMSG_NO_SUCH_MEMO");
269 return NULL;
270 }
271 return ma->recvd.list[*id = memoid];
272 }
273
274 static MODCMD_FUNC(cmd_send)
275 {
276 char *message;
277 int s = 0, brk = 0;
278 int reciept = 0, inc = 2;
279 struct handle_info *hi;
280 struct memo_account *ma, *sender;
281 struct memo *memo;
282
283 if (!(hi = modcmd_get_handle_info(user, argv[1])))
284 return 0;
285
286 if (!(sender = memoserv_get_account(user->handle_info))
287 || !(ma = memoserv_get_account(hi))) {
288 reply("MSG_INTERNAL_FAILURE");
289 return 0;
290 }
291
292 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
293 return 0;
294
295 char *flags = argv[2];
296 while (*flags) {
297 switch (*flags) {
298 case '-':
299 if (s != 0)
300 brk = 1;
301 break;
302
303 case 'r':
304 if (s > 0)
305 reciept = 1;
306 break;
307
308 default:
309 break;
310 }
311
312 if (brk == 1)
313 break;
314 else {
315 s++;
316 flags++;
317 }
318 }
319
320 if (s > 0)
321 inc = 3;
322
323 message = unsplit_string(argv + inc, argc - inc, NULL);
324 memo = add_memo(now, ma, sender, message);
325 if (reciept == 1)
326 memo->reciept = 1;
327
328 if (ma->flags & MEMO_NOTIFY_NEW) {
329 struct userNode *other;
330
331 for (other = ma->handle->users; other; other = other->next_authed)
332 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
333 }
334
335 reply("MSMSG_MEMO_SENT", ma->handle->handle);
336 return 1;
337 }
338
339 static MODCMD_FUNC(cmd_list)
340 {
341 struct memo_account *ma;
342 struct memo *memo;
343 unsigned int ii;
344 char posted[24];
345 struct tm tm;
346
347 if (!(ma = memoserv_get_account(user->handle_info)))
348 return 0;
349
350 reply("MSMSG_LIST_HEAD");
351
352 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
353 reply("MSMSG_BAR");
354
355 for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
356 memo = ma->recvd.list[ii];
357 localtime_r(&memo->sent, &tm);
358 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
359 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, memo->reciept ? "(r)" : "", posted);
360 }
361 if (ii == 0)
362 reply("MSG_NONE");
363 else if (ii == 15)
364 reply("MSMSG_CLEAN_INBOX", ii);
365 else
366 reply("MSMSG_MEMOS_FOUND", ii);
367
368 reply("MSMSG_LIST_END");
369
370 return 1;
371 }
372
373 static MODCMD_FUNC(cmd_read)
374 {
375 struct memo_account *ma;
376 unsigned int memoid;
377 int rignore = 0, brk = 0, s = 0;
378 struct memo *memo;
379 char posted[24];
380 struct tm tm;
381
382 if (!(ma = memoserv_get_account(user->handle_info)))
383 return 0;
384 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
385 return 0;
386
387 if (argv[2]) {
388 char *argtwo = argv[2];
389 while (*argtwo) {
390 switch (*argtwo) {
391 case '-':
392 if (s != 0)
393 brk = 1;
394 break;
395
396 case 'i':
397 if (s > 0)
398 rignore = 1;
399 break;
400
401 default: break;
402 }
403
404 if (brk == 1)
405 break;
406 else {
407 s++;
408 argtwo++;
409 }
410 }
411 }
412
413 localtime_r(&memo->sent, &tm);
414 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
415
416 reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
417 send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
418 memo->is_read = 1;
419
420 if (memo->reciept == 1) {
421 memo->reciept = 0;
422 reply("MSMSG_MEMO_RECIEPT", rignore ? "ignoring" : "sending");
423 if (rignore == 0) {
424 struct memo_account *ma;
425 struct memo_account *sender;
426 char content[MAXLEN];
427
428 ma = memoserv_get_account(user->handle_info);
429 sender = memoserv_get_account(memo->sender->handle);
430
431 sprintf(content, "%s has read your memo dated %s.", ma->handle->handle, posted);
432
433 memo = add_memo(now, sender, ma, content);
434
435 if (sender->flags & MEMO_NOTIFY_NEW) {
436 struct userNode *other;
437
438 for (other = sender->handle->users; other; other = other->next_authed)
439 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", ma->handle->handle);
440 }
441
442
443 }
444 }
445 return 1;
446 }
447
448 static MODCMD_FUNC(cmd_delete)
449 {
450 struct memo_account *ma;
451 struct memo *memo;
452 unsigned int memoid;
453
454 if (!(ma = memoserv_get_account(user->handle_info)))
455 return 0;
456 if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
457 if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
458 reply("MSMSG_USE_CONFIRM");
459 return 0;
460 }
461 while (ma->recvd.used)
462 delete_memo(ma->recvd.list[0]);
463 reply("MSMSG_DELETED_ALL");
464 return 1;
465 }
466
467 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
468 return 0;
469 delete_memo(memo);
470 reply("MSMSG_MEMO_DELETED", memoid);
471 return 1;
472 }
473
474 static MODCMD_FUNC(cmd_expire)
475 {
476 unsigned long old_expired = memosExpired;
477 do_expire();
478 reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
479 return 1;
480 }
481
482 static MODCMD_FUNC(cmd_expiry)
483 {
484 char interval[INTERVALLEN];
485
486 if (!memoserv_conf.message_expiry) {
487 reply("MSMSG_EXPIRY_OFF");
488 return 1;
489 }
490
491 intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
492 reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
493 return 1;
494 }
495
496
497 static void
498 set_list(struct userNode *user, struct handle_info *hi, int override)
499 {
500 option_func_t *opt;
501 unsigned int i;
502 char *set_display[] = {"AUTHNOTIFY", "NOTIFY", "PRIVATE", "LIMIT"};
503
504 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS");
505 send_message(user, memoserv_conf.bot, "MSMSG_BAR");
506
507 /* Do this so options are presented in a consistent order. */
508 for (i = 0; i < ArrayLength(set_display); ++i)
509 if ((opt = dict_find(memoserv_opt_dict, set_display[i], NULL)))
510 opt(user, hi, override, 0, NULL);
511 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS_END");
512 }
513
514 static MODCMD_FUNC(cmd_set)
515 {
516 struct handle_info *hi;
517 option_func_t *opt;
518
519 hi = user->handle_info;
520 if (argc < 2) {
521 set_list(user, hi, 0);
522 return 1;
523 }
524
525 if (!(opt = dict_find(memoserv_opt_dict, argv[1], NULL))) {
526 reply("MSMSG_INVALID_OPTION", argv[1]);
527 return 0;
528 }
529
530 return opt(user, hi, 0, argc-1, argv+1);
531 }
532
533 static MODCMD_FUNC(cmd_oset)
534 {
535 struct handle_info *hi;
536 option_func_t *opt;
537
538 if (!(hi = get_victim_oper(user, argv[1])))
539 return 0;
540
541 if (argc < 3) {
542 set_list(user, hi, 0);
543 return 1;
544 }
545
546 if (!(opt = dict_find(memoserv_opt_dict, argv[2], NULL))) {
547 reply("MSMSG_INVALID_OPTION", argv[2]);
548 return 0;
549 }
550
551 return opt(user, hi, 1, argc-2, argv+2);
552 }
553
554 static OPTION_FUNC(opt_notify)
555 {
556 struct memo_account *ma;
557 char *choice;
558
559 if (!(ma = memoserv_get_account(hi)))
560 return 0;
561 if (argc > 1) {
562 choice = argv[1];
563 if (enabled_string(choice)) {
564 ma->flags |= MEMO_NOTIFY_NEW;
565 } else if (disabled_string(choice)) {
566 ma->flags &= ~MEMO_NOTIFY_NEW;
567 } else {
568 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
569 return 0;
570 }
571 }
572
573 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
574 send_message(user, memoserv_conf.bot, "MSMSG_SET_NOTIFY", choice);
575 return 1;
576 }
577
578 static OPTION_FUNC(opt_authnotify)
579 {
580 struct memo_account *ma;
581 char *choice;
582
583 if (!(ma = memoserv_get_account(hi)))
584 return 0;
585 if (argc > 1) {
586 choice = argv[1];
587 if (enabled_string(choice)) {
588 ma->flags |= MEMO_NOTIFY_LOGIN;
589 } else if (disabled_string(choice)) {
590 ma->flags &= ~MEMO_NOTIFY_LOGIN;
591 } else {
592 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
593 return 0;
594 }
595 }
596
597 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
598 send_message(user, memoserv_conf.bot, "MSMSG_SET_AUTHNOTIFY", choice);
599 return 1;
600 }
601
602 static OPTION_FUNC(opt_private)
603 {
604 struct memo_account *ma;
605 char *choice;
606
607 if (!(ma = memoserv_get_account(hi)))
608 return 0;
609 if (argc > 1) {
610 choice = argv[1];
611 if (enabled_string(choice)) {
612 ma->flags |= MEMO_DENY_NONCHANNEL;
613 } else if (disabled_string(choice)) {
614 ma->flags &= ~MEMO_DENY_NONCHANNEL;
615 } else {
616 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
617 return 0;
618 }
619 }
620
621 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
622 send_message(user, memoserv_conf.bot, "MSMSG_SET_PRIVATE", choice);
623 return 1;
624 }
625
626 static OPTION_FUNC(opt_limit)
627 {
628 struct memo_account *ma;
629 unsigned int choice;
630
631 if (!(ma = memoserv_get_account(hi)))
632 return 0;
633 if (argc > 1) {
634 choice = atoi(argv[1]);
635 if (choice > memoserv_conf.limit)
636 choice = memoserv_conf.limit;
637
638 ma->limit = choice;
639 }
640
641 send_message(user, memoserv_conf.bot, "MSMSG_SET_LIMIT", ma->limit);
642 return 1;
643 }
644
645 static MODCMD_FUNC(cmd_status)
646 {
647 reply("MSMSG_STATUS_TOTAL", dict_size(memos));
648 reply("MSMSG_STATUS_EXPIRED", memosExpired);
649 reply("MSMSG_STATUS_SENT", memosSent);
650 return 1;
651 }
652
653 static void
654 memoserv_conf_read(void)
655 {
656 dict_t conf_node;
657 const char *str;
658
659 str = "modules/memoserv";
660 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
661 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
662 return;
663 }
664
665 str = database_get_data(conf_node, "limit", RECDB_QSTRING);
666 memoserv_conf.limit = atoi(str) ? : 50;
667
668 str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
669 memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
670 }
671
672 static int
673 memoserv_saxdb_read(struct dict *db)
674 {
675 char *str;
676 struct handle_info *sender, *recipient;
677 struct record_data *hir;
678 struct memo *memo;
679 dict_iterator_t it;
680 time_t sent;
681
682 for (it = dict_first(db); it; it = iter_next(it)) {
683 hir = iter_data(it);
684 if (hir->type != RECDB_OBJECT) {
685 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
686 continue;
687 }
688
689 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
690 log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", iter_key(it));
691 continue;
692 }
693 sent = atoi(str);
694
695 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
696 log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", iter_key(it));
697 continue;
698 } else if (!(recipient = get_handle_info(str))) {
699 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, iter_key(it));
700 continue;
701 }
702
703 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
704 log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", iter_key(it));
705 continue;
706 } else if (!(sender = get_handle_info(str))) {
707 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, iter_key(it));
708 continue;
709 }
710
711 if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
712 log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", iter_key(it));
713 continue;
714 }
715
716 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str);
717 if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
718 memo->is_read = 1;
719
720 if ((str = database_get_data(hir->d.object, KEY_RECIEPT, RECDB_QSTRING)))
721 memo->reciept = 1;
722 }
723 return 0;
724 }
725
726 static int
727 memoserv_saxdb_write(struct saxdb_context *ctx)
728 {
729 dict_iterator_t it;
730 struct memo_account *ma;
731 struct memo *memo;
732 char str[7];
733 unsigned int id = 0, ii;
734
735 for (it = dict_first(memos); it; it = iter_next(it)) {
736 ma = iter_data(it);
737 for (ii = 0; ii < ma->recvd.used; ++ii) {
738 memo = ma->recvd.list[ii];
739 saxdb_start_record(ctx, inttobase64(str, id++, sizeof(str)), 0);
740 saxdb_write_int(ctx, KEY_SENT, memo->sent);
741 saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
742 saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
743 saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
744 if (memo->is_read)
745 saxdb_write_int(ctx, KEY_READ, 1);
746
747 if (memo->reciept)
748 saxdb_write_int(ctx, KEY_RECIEPT, 1);
749 saxdb_end_record(ctx);
750 }
751 }
752 return 0;
753 }
754
755 static void
756 memoserv_cleanup(void)
757 {
758 dict_delete(memos);
759 }
760
761 static void
762 memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
763 {
764 unsigned int ii, unseen;
765 struct memo_account *ma;
766 struct memo *memo;
767
768 if (!user->uplink->burst) {
769 if (!(ma = memoserv_get_account(user->handle_info))
770 || !(ma->flags & MEMO_NOTIFY_LOGIN))
771 return;
772 for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
773 memo = ma->recvd.list[ii];
774 if (!memo->is_read)
775 unseen++;
776 }
777 if (ma->recvd.used && memoserv_conf.bot)
778 if(unseen) send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
779 }
780 }
781
782 static void
783 memoserv_rename_account(struct handle_info *hi, const char *old_handle)
784 {
785 struct memo_account *ma;
786 if (!(ma = dict_find(memos, old_handle, NULL)))
787 return;
788 dict_remove2(memos, old_handle, 1);
789 dict_insert(memos, hi->handle, ma);
790 }
791
792 static void
793 memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
794 {
795 dict_remove(memos, handle->handle);
796 }
797
798 int
799 memoserv_init(void)
800 {
801 MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
802 memos = dict_new();
803 dict_set_free_data(memos, delete_memo_account);
804 reg_auth_func(memoserv_check_messages);
805 reg_handle_rename_func(memoserv_rename_account);
806 reg_unreg_func(memoserv_unreg_account);
807 conf_register_reload(memoserv_conf_read);
808 reg_exit_func(memoserv_cleanup);
809 saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
810
811 memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
812 modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
813 modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
814 modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
815 modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
816 modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
817 modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
818 modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
819 modcmd_register(memoserv_module, "set", cmd_set, 1, MODCMD_REQUIRE_AUTHED, NULL);
820 modcmd_register(memoserv_module, "oset", cmd_oset, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
821
822 memoserv_opt_dict = dict_new();
823 dict_insert(memoserv_opt_dict, "AUTHNOTIFY", opt_authnotify);
824 dict_insert(memoserv_opt_dict, "NOTIFY", opt_notify);
825 dict_insert(memoserv_opt_dict, "PRIVATE", opt_private);
826 dict_insert(memoserv_opt_dict, "LIMIT", opt_limit);
827
828 message_register_table(msgtab);
829
830 if (memoserv_conf.message_expiry)
831 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
832
833 return 1;
834 }
835
836 int
837 memoserv_finalize(void) {
838 struct chanNode *chan;
839 unsigned int i;
840 dict_t conf_node;
841 const char *str;
842
843 str = "modules/memoserv";
844 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
845 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
846 return 0;
847 }
848
849 str = database_get_data(conf_node, "bot", RECDB_QSTRING);
850 if (str)
851 memoserv_conf.bot = GetUserH(str);
852
853 if (autojoin_channels && memoserv_conf.bot) {
854 for (i = 0; i < autojoin_channels->used; i++) {
855 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
856 AddChannelUser(memoserv_conf.bot, chan)->modes |= MODE_CHANOP;
857 }
858 }
859
860 return 1;
861 }