]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-memoserv.c
bc6a6cb8e54da3ad70f1368aebd3697d6489bc3f
[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
53 static const struct message_entry msgtab[] = {
54 { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
55 { "MSMSG_MEMO_SENT", "Message sent to $b%s$b." },
56 { "MSMSG_NO_MESSAGES", "You have no messages." },
57 { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
58 { "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." },
59 { "MSMSG_LIST_HEAD", "$bID$b $bFrom$b $bTime Sent$b" },
60 { "MSMSG_LIST_FORMAT", "%-2u %s %s" },
61 { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
62 { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
63 { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
64 { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
65 { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
66 { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
67 { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
68 { "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." },
69 { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b. /msg $S LIST" },
70 { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
71 { "MSMSG_USE_CONFIRM", "Please use /msg $S DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
72
73 { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
74 { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
75 { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
76
77 { "MSMSG_INVALID_OPTION", "$b%s$b is not a valid %s option." },
78 { "MSMSG_INVALID_BINARY", "$b%s$b is an invalid binary value." },
79 { "MSMSG_SET_NOTIFY", "$bNotify: $b %s" },
80 { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify: $b %s" },
81 { "MSMSG_SET_PRIVATE", "$bPrivate: $b %s" },
82 { "MSMSG_SET_LIMIT", "$bLimit: $b %d" },
83 { "MSMSG_SET_OPTIONS", "$bMessaging Options$b" },
84 { "MSMSG_SET_OPTIONS_END", "-------------End of Options-------------" },
85
86 { "MSMSG_LIST_END", "--------------End of Memos--------------" },
87 { "MSMSG_BAR", "----------------------------------------"},
88
89 { NULL, NULL }
90 };
91
92 struct memo {
93 struct memo_account *recipient;
94 struct memo_account *sender;
95 char *message;
96 time_t sent;
97 unsigned int is_read : 1;
98 };
99
100 DECLARE_LIST(memoList, struct memo*);
101 DEFINE_LIST(memoList, struct memo*);
102
103 /* memo_account.flags fields */
104 #define MEMO_NOTIFY_NEW 1
105 #define MEMO_NOTIFY_LOGIN 2
106 #define MEMO_DENY_NONCHANNEL 4
107
108 struct memo_account {
109 struct handle_info *handle;
110 unsigned int flags;
111 int limit;
112 struct memoList sent;
113 struct memoList recvd;
114 };
115
116 static struct {
117 struct userNode *bot;
118 int message_expiry;
119 int limit;
120 } memoserv_conf;
121
122 extern struct string_list *autojoin_channels;
123 const char *memoserv_module_deps[] = { NULL };
124 static struct module *memoserv_module;
125 static struct log_type *MS_LOG;
126 static unsigned long memosSent, memosExpired;
127 static struct dict *memos; /* memo_account->handle->handle -> memo_account */
128 static dict_t memoserv_opt_dict; /* contains option_func_t* */
129
130 #define OPTION_FUNC(NAME) int NAME(struct userNode *user, UNUSED_ARG(struct handle_info *hi), UNUSED_ARG(unsigned int override), unsigned int argc, char *argv[])
131 typedef OPTION_FUNC(option_func_t);
132
133 static struct memo_account *
134 memoserv_get_account(struct handle_info *hi)
135 {
136 struct memo_account *ma;
137 if (!hi)
138 return NULL;
139 ma = dict_find(memos, hi->handle, NULL);
140 if (ma)
141 return ma;
142 ma = calloc(1, sizeof(*ma));
143 if (!ma)
144 return ma;
145 ma->handle = hi;
146 ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
147 dict_insert(memos, ma->handle->handle, ma);
148 return ma;
149 }
150
151 static void
152 delete_memo(struct memo *memo)
153 {
154 memoList_remove(&memo->recipient->recvd, memo);
155 memoList_remove(&memo->sender->sent, memo);
156 free(memo->message);
157 free(memo);
158 }
159
160 static void
161 delete_memo_account(void *data)
162 {
163 struct memo_account *ma = data;
164
165 while (ma->recvd.used)
166 delete_memo(ma->recvd.list[0]);
167 while (ma->sent.used)
168 delete_memo(ma->sent.list[0]);
169 memoList_clean(&ma->recvd);
170 memoList_clean(&ma->sent);
171 free(ma);
172 }
173
174 void
175 do_expire(void)
176 {
177 dict_iterator_t it;
178 for (it = dict_first(memos); it; it = iter_next(it)) {
179 struct memo_account *acct = iter_data(it);
180 unsigned int ii;
181 for (ii = 0; ii < acct->sent.used; ++ii) {
182 struct memo *memo = acct->sent.list[ii];
183 if ((now - memo->sent) > memoserv_conf.message_expiry) {
184 delete_memo(memo);
185 memosExpired++;
186 ii--;
187 }
188 }
189 }
190 }
191
192 static void
193 expire_memos(UNUSED_ARG(void *data))
194 {
195 if (memoserv_conf.message_expiry) {
196 do_expire();
197 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
198 }
199 }
200
201 static struct memo*
202 add_memo(time_t sent, struct memo_account *recipient, struct memo_account *sender, char *message)
203 {
204 struct memo *memo;
205
206 memo = calloc(1, sizeof(*memo));
207 if (!memo)
208 return NULL;
209
210 memo->recipient = recipient;
211 memoList_append(&recipient->recvd, memo);
212 memo->sender = sender;
213 memoList_append(&sender->sent, memo);
214 memo->sent = sent;
215 memo->message = strdup(message);
216 memosSent++;
217 return memo;
218 }
219
220 static int
221 memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *acct)
222 {
223 extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
224 struct userData *dest;
225
226 if (!user->handle_info)
227 return 0;
228 if (!(acct->flags & MEMO_DENY_NONCHANNEL))
229 return 1;
230 for (dest = acct->handle->channels; dest; dest = dest->u_next)
231 if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
232 return 1;
233 send_message(user, bot, "MSMSG_CANNOT_SEND", acct->handle->handle);
234 return 0;
235 }
236
237 static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
238 {
239 unsigned int memoid;
240 if (!isdigit(msgid[0])) {
241 if (ma->recvd.used)
242 reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
243 else
244 reply("MSMSG_NO_MESSAGES");
245 return NULL;
246 }
247 memoid = atoi(msgid);
248 if (memoid >= ma->recvd.used) {
249 reply("MSMSG_NO_SUCH_MEMO");
250 return NULL;
251 }
252 return ma->recvd.list[*id = memoid];
253 }
254
255 static MODCMD_FUNC(cmd_send)
256 {
257 char *message;
258 struct handle_info *hi;
259 struct memo_account *ma, *sender;
260
261 if (!(hi = modcmd_get_handle_info(user, argv[1])))
262 return 0;
263 if (!(sender = memoserv_get_account(user->handle_info))
264 || !(ma = memoserv_get_account(hi))) {
265 reply("MSG_INTERNAL_FAILURE");
266 return 0;
267 }
268 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
269 return 0;
270 message = unsplit_string(argv + 2, argc - 2, NULL);
271 add_memo(now, ma, sender, message);
272 if (ma->flags & MEMO_NOTIFY_NEW) {
273 struct userNode *other;
274 for (other = ma->handle->users; other; other = other->next_authed)
275 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
276 }
277 reply("MSMSG_MEMO_SENT", ma->handle->handle);
278 return 1;
279 }
280
281 static MODCMD_FUNC(cmd_list)
282 {
283 struct memo_account *ma;
284 struct memo *memo;
285 unsigned int ii;
286 char posted[24];
287 struct tm tm;
288
289 if (!(ma = memoserv_get_account(user->handle_info)))
290 return 0;
291
292 reply("MSMSG_LIST_HEAD");
293
294 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
295 reply("MSMSG_BAR");
296
297 for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
298 memo = ma->recvd.list[ii];
299 localtime_r(&memo->sent, &tm);
300 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
301 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, posted);
302 }
303 if (ii == 0)
304 reply("MSG_NONE");
305 else if (ii == 15)
306 reply("MSMSG_CLEAN_INBOX", ii);
307 else
308 reply("MSMSG_MEMOS_FOUND", ii);
309
310 reply("MSMSG_LIST_END");
311
312 return 1;
313 }
314
315 static MODCMD_FUNC(cmd_read)
316 {
317 struct memo_account *ma;
318 unsigned int memoid;
319 struct memo *memo;
320 char posted[24];
321 struct tm tm;
322
323 if (!(ma = memoserv_get_account(user->handle_info)))
324 return 0;
325 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
326 return 0;
327 localtime_r(&memo->sent, &tm);
328 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
329 reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
330 send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
331 memo->is_read = 1;
332 return 1;
333 }
334
335 static MODCMD_FUNC(cmd_delete)
336 {
337 struct memo_account *ma;
338 struct memo *memo;
339 unsigned int memoid;
340
341 if (!(ma = memoserv_get_account(user->handle_info)))
342 return 0;
343 if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
344 if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
345 reply("MSMSG_USE_CONFIRM");
346 return 0;
347 }
348 while (ma->recvd.used)
349 delete_memo(ma->recvd.list[0]);
350 reply("MSMSG_DELETED_ALL");
351 return 1;
352 }
353
354 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
355 return 0;
356 delete_memo(memo);
357 reply("MSMSG_MEMO_DELETED", memoid);
358 return 1;
359 }
360
361 static MODCMD_FUNC(cmd_expire)
362 {
363 unsigned long old_expired = memosExpired;
364 do_expire();
365 reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
366 return 1;
367 }
368
369 static MODCMD_FUNC(cmd_expiry)
370 {
371 char interval[INTERVALLEN];
372
373 if (!memoserv_conf.message_expiry) {
374 reply("MSMSG_EXPIRY_OFF");
375 return 1;
376 }
377
378 intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
379 reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
380 return 1;
381 }
382
383
384 static void
385 set_list(struct userNode *user, struct handle_info *hi, int override)
386 {
387 option_func_t *opt;
388 unsigned int i = 0;
389 char *set_display[] = {"AUTHNOTIFY", "NOTIFY", "PRIVATE", "LIMIT"};
390
391 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS");
392
393 if(user->handle_info && user->handle_info->userlist_style != HI_STYLE_CLEAN)
394 send_message(user, memoserv_conf.bot, "MSMSG_BAR");
395
396 /* Do this so options are presented in a consistent order. */
397 while(i < ArrayLength(set_display))
398 {
399 if((opt = dict_find(memoserv_opt_dict, set_display[i++], NULL)))
400 {
401 opt(user, hi, override, 0, NULL);
402 }
403 }
404
405 send_message(user, memoserv_conf.bot, "MSMSG_SET_OPTIONS_END");
406 }
407
408 static MODCMD_FUNC(cmd_set)
409 {
410 struct handle_info *hi;
411 option_func_t *opt;
412
413 hi = user->handle_info;
414
415 if (argc < 2)
416 {
417 set_list(user, hi, 0);
418 return 1;
419 }
420
421 if (!(opt = dict_find(memoserv_opt_dict, argv[1], NULL)))
422 {
423 reply("MSMSG_INVALID_OPTION", argv[1]);
424 return 0;
425 }
426 return opt(user, hi, 0, argc-1, argv+1);
427 }
428
429 static MODCMD_FUNC(cmd_oset)
430 {
431 struct handle_info *hi;
432 option_func_t *opt;
433
434 if (!(hi = get_victim_oper(user, argv[1]))) return 0;
435
436 if (argc < 3)
437 {
438 set_list(user, hi, 0);
439 return 1;
440 }
441
442 if (!(opt = dict_find(memoserv_opt_dict, argv[2], NULL)))
443 {
444 reply("MSMSG_INVALID_OPTION", argv[2]);
445 return 0;
446 }
447
448 return opt(user, hi, 1, argc-2, argv+2);
449 }
450
451 static OPTION_FUNC(opt_notify)
452 {
453 struct memo_account *ma;
454 char *choice;
455
456 if (!(ma = memoserv_get_account(user->handle_info)))
457 return 0;
458 if (argc > 1) {
459 choice = argv[1];
460 if (enabled_string(choice)) {
461 ma->flags |= MEMO_NOTIFY_NEW;
462 } else if (disabled_string(choice)) {
463 ma->flags &= ~MEMO_NOTIFY_NEW;
464 } else {
465 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
466 return 0;
467 }
468 }
469
470 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
471 send_message(user, memoserv_conf.bot, "MSMSG_SET_NOTIFY", choice);
472 return 1;
473 }
474
475 static MODCMD_FUNC(opt_authnotify)
476 {
477 struct memo_account *ma;
478 char *choice;
479
480 if (!(ma = memoserv_get_account(user->handle_info)))
481 return 0;
482 if (argc > 1) {
483 choice = argv[1];
484 if (enabled_string(choice)) {
485 ma->flags |= MEMO_NOTIFY_LOGIN;
486 } else if (disabled_string(choice)) {
487 ma->flags &= ~MEMO_NOTIFY_LOGIN;
488 } else {
489 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
490 return 0;
491 }
492 }
493
494 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
495 send_message(user, memoserv_conf.bot, "MSMSG_SET_AUTHNOTIFY", choice);
496 return 1;
497 }
498
499 static MODCMD_FUNC(opt_private)
500 {
501 struct memo_account *ma;
502 char *choice;
503
504 if (!(ma = memoserv_get_account(user->handle_info)))
505 return 0;
506 if (argc > 1) {
507 choice = argv[1];
508 if (enabled_string(choice)) {
509 ma->flags |= MEMO_DENY_NONCHANNEL;
510 } else if (disabled_string(choice)) {
511 ma->flags &= ~MEMO_DENY_NONCHANNEL;
512 } else {
513 send_message(user, memoserv_conf.bot, "MSMSG_INVALID_BINARY", choice);
514 return 0;
515 }
516 }
517
518 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
519 send_message(user, memoserv_conf.bot, "MSMSG_SET_PRIVATE", choice);
520 return 1;
521 }
522
523 static MODCMD_FUNC(opt_limit)
524 {
525 struct memo_account *ma;
526 int choice;
527
528 if (!(ma = memoserv_get_account(user->handle_info)))
529 return 0;
530 if (argc > 1) {
531 choice = atoi(argv[1]);
532 if (choice > memoserv_conf.limit)
533 choice = memoserv_conf.limit;
534
535 ma->limit = choice;
536 }
537
538 send_message(user, memoserv_conf.bot, "MSMSG_SET_LIMIT", choice);
539 return 1;
540 }
541
542 static MODCMD_FUNC(cmd_status)
543 {
544 reply("MSMSG_STATUS_TOTAL", dict_size(memos));
545 reply("MSMSG_STATUS_EXPIRED", memosExpired);
546 reply("MSMSG_STATUS_SENT", memosSent);
547 return 1;
548 }
549
550 static void
551 memoserv_conf_read(void)
552 {
553 dict_t conf_node;
554 const char *str;
555
556 str = "modules/memoserv";
557 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
558 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
559 return;
560 }
561
562 str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
563 memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
564 }
565
566 static int
567 memoserv_saxdb_read(struct dict *db)
568 {
569 char *str;
570 struct handle_info *sender, *recipient;
571 struct record_data *hir;
572 struct memo *memo;
573 dict_iterator_t it;
574 time_t sent;
575
576 for (it = dict_first(db); it; it = iter_next(it)) {
577 hir = iter_data(it);
578 if (hir->type != RECDB_OBJECT) {
579 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
580 continue;
581 }
582
583 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
584 log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", iter_key(it));
585 continue;
586 }
587 sent = atoi(str);
588
589 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
590 log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", iter_key(it));
591 continue;
592 } else if (!(recipient = get_handle_info(str))) {
593 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, iter_key(it));
594 continue;
595 }
596
597 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
598 log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", iter_key(it));
599 continue;
600 } else if (!(sender = get_handle_info(str))) {
601 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, iter_key(it));
602 continue;
603 }
604
605 if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
606 log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", iter_key(it));
607 continue;
608 }
609
610 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str);
611 if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
612 memo->is_read = 1;
613 }
614 return 0;
615 }
616
617 static int
618 memoserv_saxdb_write(struct saxdb_context *ctx)
619 {
620 dict_iterator_t it;
621 struct memo_account *ma;
622 struct memo *memo;
623 char str[7];
624 unsigned int id = 0, ii;
625
626 for (it = dict_first(memos); it; it = iter_next(it)) {
627 ma = iter_data(it);
628 for (ii = 0; ii < ma->recvd.used; ++ii) {
629 memo = ma->recvd.list[ii];
630 saxdb_start_record(ctx, inttobase64(str, id++, sizeof(str)), 0);
631 saxdb_write_int(ctx, KEY_SENT, memo->sent);
632 saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
633 saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
634 saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
635 if (memo->is_read)
636 saxdb_write_int(ctx, KEY_READ, 1);
637 saxdb_end_record(ctx);
638 }
639 }
640 return 0;
641 }
642
643 static void
644 memoserv_cleanup(void)
645 {
646 dict_delete(memos);
647 }
648
649 static void
650 memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
651 {
652 unsigned int ii, unseen;
653 struct memo_account *ma;
654 struct memo *memo;
655
656 if (!user->uplink->burst) {
657 if (!(ma = memoserv_get_account(user->handle_info))
658 || !(ma->flags & MEMO_NOTIFY_LOGIN))
659 return;
660 for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
661 memo = ma->recvd.list[ii];
662 if (!memo->is_read)
663 unseen++;
664 }
665 if (ma->recvd.used && memoserv_conf.bot)
666 if(unseen) send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
667 }
668 }
669
670 static void
671 memoserv_rename_account(struct handle_info *hi, const char *old_handle)
672 {
673 struct memo_account *ma;
674 if (!(ma = dict_find(memos, old_handle, NULL)))
675 return;
676 dict_remove2(memos, old_handle, 1);
677 dict_insert(memos, hi->handle, ma);
678 }
679
680 static void
681 memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
682 {
683 dict_remove(memos, handle->handle);
684 }
685
686 int
687 memoserv_init(void)
688 {
689 MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
690 memos = dict_new();
691 dict_set_free_data(memos, delete_memo_account);
692 reg_auth_func(memoserv_check_messages);
693 reg_handle_rename_func(memoserv_rename_account);
694 reg_unreg_func(memoserv_unreg_account);
695 conf_register_reload(memoserv_conf_read);
696 reg_exit_func(memoserv_cleanup);
697 saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
698
699 memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
700 modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
701 modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
702 modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
703 modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
704 modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
705 modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
706 modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
707 modcmd_register(memoserv_module, "set", cmd_set, 1, MODCMD_REQUIRE_AUTHED, NULL);
708 modcmd_register(memoserv_module, "oset", cmd_oset, 1, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
709
710 memoserv_opt_dict = dict_new();
711 dict_insert(memoserv_opt_dict, "AUTHNOTIFY", opt_authnotify);
712 dict_insert(memoserv_opt_dict, "NOTIFY", opt_notify);
713 dict_insert(memoserv_opt_dict, "PRIVATE", opt_private);
714 /* dict_insert(memoserv_opt_dict, "LIMIT", opt_limit); */
715
716 message_register_table(msgtab);
717
718 if (memoserv_conf.message_expiry)
719 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
720
721 return 1;
722 }
723
724 int
725 memoserv_finalize(void) {
726 struct chanNode *chan;
727 unsigned int i;
728 dict_t conf_node;
729 const char *str;
730
731 str = "modules/memoserv";
732 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
733 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
734 return 0;
735 }
736
737 str = database_get_data(conf_node, "bot", RECDB_QSTRING);
738 if (str)
739 memoserv_conf.bot = GetUserH(str);
740
741 if (autojoin_channels && memoserv_conf.bot) {
742 for (i = 0; i < autojoin_channels->used; i++) {
743 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
744 AddChannelUser(memoserv_conf.bot, chan)->modes |= MODE_CHANOP;
745 }
746 }
747
748 return 1;
749 }