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