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