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