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