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