Report the number of memos, not recipients, in /msg MemoServ status.
[srvx.git] / src / mod-memoserv.c
1 /* mod-memoserv.c - MemoServ module for srvx
2  * Copyright 2003-2004 Martijn Smit and srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 /* Adds new section to srvx.conf:
22  * "modules" {
23  *     "memoserv" {
24  *         "bot" "NickServ";
25  *         "message_expiry" "30d"; // age when messages are deleted; set
26  *                                 // to 0 to disable message expiration
27  *     };
28  *  };
29  *
30  * After that, to make the module active on an existing bot:
31  * /msg opserv bind nickserv * *memoserv.*
32  *
33  * If you want a dedicated MemoServ bot, make sure the service control
34  * commands are bound to OpServ:
35  * /msg opserv bind opserv service *modcmd.joiner
36  * /msg opserv bind opserv service\ add *modcmd.service\ add
37  * /msg opserv bind opserv service\ rename *modcmd.service\ rename
38  * /msg opserv bind opserv service\ trigger *modcmd.service\ trigger
39  * /msg opserv bind opserv service\ remove *modcmd.service\ remove
40  * Add the bot:
41  * /msg opserv service add MemoServ User-to-user Memorandum Service
42  * /msg opserv bind memoserv help *modcmd.help
43  * Restart srvx with the updated conf file (as above, butwith "bot"
44  * "MemoServ"), and bind the commands to it:
45  * /msg opserv bind memoserv * *memoserv.*
46  * /msg opserv bind memoserv set *modcmd.joiner
47  */
48
49 #include "chanserv.h"
50 #include "conf.h"
51 #include "modcmd.h"
52 #include "saxdb.h"
53 #include "timeq.h"
54
55 #define KEY_SENT "sent"
56 #define KEY_RECIPIENT "to"
57 #define KEY_FROM "from"
58 #define KEY_MESSAGE "msg"
59 #undef KEY_READ /* thanks microsoft! */
60 #define KEY_READ "read"
61
62 static const struct message_entry msgtab[] = {
63     { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
64     { "MSMSG_MEMO_SENT", "Message sent to $b%s$b." },
65     { "MSMSG_NO_MESSAGES", "You have no messages." },
66     { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
67     { "MSMSG_CLEAN_INBOX", "You have $b%d$b or more messages, please clean out your inbox.\nUse /msg $S READ <ID> to read a message." },
68     { "MSMSG_LIST_HEAD", "$bID$b   $bFrom$b       $bTime Sent$b" },
69     { "MSMSG_LIST_FORMAT", "%-2u     %s           %s" },
70     { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
71     { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
72     { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
73     { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
74     { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
75     { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
76     { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
77     { "MSMSG_MEMOS_INBOX", "You have $b%d$b new message(s) in your inbox and %d old messages.  Use /msg $S LIST to list them." },
78     { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b." },
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." },
81     { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
82     { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
83     { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
84     { "MSMSG_SET_NOTIFY",     "$bNotify:       $b %s" },
85     { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify:   $b %s" },
86     { "MSMSG_SET_PRIVATE",    "$bPrivate:      $b %s" },
87     { NULL, NULL }
88 };
89
90 struct memo {
91     struct memo_account *recipient;
92     struct memo_account *sender;
93     char *message;
94     unsigned long sent;
95     unsigned int is_read : 1;
96 };
97
98 DECLARE_LIST(memoList, struct memo*);
99 DEFINE_LIST(memoList, struct memo*)
100
101 /* memo_account.flags fields */
102 #define MEMO_NOTIFY_NEW   1
103 #define MEMO_NOTIFY_LOGIN 2
104 #define MEMO_DENY_NONCHANNEL 4
105
106 struct memo_account {
107     struct handle_info *handle;
108     unsigned int flags;
109     struct memoList sent;
110     struct memoList recvd;
111 };
112
113 static struct {
114     struct userNode *bot;
115     unsigned long message_expiry;
116 } memoserv_conf;
117
118 const char *memoserv_module_deps[] = { NULL };
119 static struct module *memoserv_module;
120 static struct log_type *MS_LOG;
121 static unsigned long memoCount;
122 static unsigned long memosSent;
123 static unsigned long memosExpired;
124 static struct dict *memos; /* memo_account->handle->handle -> memo_account */
125
126 static struct memo_account *
127 memoserv_get_account(struct handle_info *hi)
128 {
129     struct memo_account *ma;
130     if (!hi)
131         return NULL;
132     ma = dict_find(memos, hi->handle, NULL);
133     if (ma)
134         return ma;
135     ma = calloc(1, sizeof(*ma));
136     if (!ma)
137         return ma;
138     ma->handle = hi;
139     ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
140     dict_insert(memos, ma->handle->handle, ma);
141     return ma;
142 }
143
144 static void
145 delete_memo(struct memo *memo)
146 {
147     memoList_remove(&memo->recipient->recvd, memo);
148     memoList_remove(&memo->sender->sent, memo);
149     free(memo->message);
150     free(memo);
151     memoCount--;
152 }
153
154 static void
155 delete_memo_account(void *data)
156 {
157     struct memo_account *ma = data;
158
159     while (ma->recvd.used)
160         delete_memo(ma->recvd.list[0]);
161     while (ma->sent.used)
162         delete_memo(ma->sent.list[0]);
163     memoList_clean(&ma->recvd);
164     memoList_clean(&ma->sent);
165     free(ma);
166 }
167
168 void
169 do_expire(void)
170 {
171     dict_iterator_t it;
172     for (it = dict_first(memos); it; it = iter_next(it)) {
173         struct memo_account *account = iter_data(it);
174         unsigned int ii;
175         for (ii = 0; ii < account->sent.used; ++ii) {
176             struct memo *memo = account->sent.list[ii];
177             if ((now - memo->sent) > memoserv_conf.message_expiry) {
178                 delete_memo(memo);
179                 memosExpired++;
180                 ii--;
181             }
182         }
183     }
184 }
185
186 static void
187 expire_memos(UNUSED_ARG(void *data))
188 {
189     if (memoserv_conf.message_expiry) {
190         do_expire();
191         timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
192     }
193 }
194
195 static struct memo*
196 add_memo(unsigned long sent, struct memo_account *recipient, struct memo_account *sender, char *message)
197 {
198     struct memo *memo;
199
200     memo = calloc(1, sizeof(*memo));
201     if (!memo)
202         return NULL;
203
204     memo->recipient = recipient;
205     memoList_append(&recipient->recvd, memo);
206     memo->sender = sender;
207     memoList_append(&sender->sent, memo);
208     memo->sent = sent;
209     memo->message = strdup(message);
210     memosSent++;
211     memoCount++;
212     return memo;
213 }
214
215 static int
216 memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *account)
217 {
218     extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
219     struct userData *dest;
220
221     if (!user->handle_info)
222         return 0;
223     if (!(account->flags & MEMO_DENY_NONCHANNEL))
224         return 1;
225     for (dest = account->handle->channels; dest; dest = dest->u_next)
226         if (_GetChannelUser(dest->channel, user->handle_info, 1, 0))
227             return 1;
228     send_message(user, bot, "MSMSG_CANNOT_SEND", account->handle->handle);
229     return 0;
230 }
231
232 static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
233 {
234     unsigned int memoid;
235     if (!isdigit(msgid[0])) {
236         if (ma->recvd.used)
237             reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
238         else
239             reply("MSMSG_NO_MESSAGES");
240         return NULL;
241     }
242     memoid = atoi(msgid);
243     if (memoid >= ma->recvd.used) {
244         reply("MSMSG_NO_SUCH_MEMO");
245         return NULL;
246     }
247     return ma->recvd.list[*id = memoid];
248 }
249
250 static MODCMD_FUNC(cmd_send)
251 {
252     char *message;
253     struct handle_info *hi;
254     struct memo_account *ma, *sender;
255
256     if (!(hi = modcmd_get_handle_info(user, argv[1])))
257         return 0;
258     if (!(sender = memoserv_get_account(user->handle_info))
259         || !(ma = memoserv_get_account(hi))) {
260         reply("MSG_INTERNAL_FAILURE");
261         return 0;
262     }
263     if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
264         return 0;
265     message = unsplit_string(argv + 2, argc - 2, NULL);
266     add_memo(now, ma, sender, message);
267     if (ma->flags & MEMO_NOTIFY_NEW) {
268         struct userNode *other;
269         for (other = ma->handle->users; other; other = other->next_authed)
270             send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick);
271     }
272     reply("MSMSG_MEMO_SENT", ma->handle->handle);
273     return 1;
274 }
275
276 static MODCMD_FUNC(cmd_list)
277 {
278     struct memo_account *ma;
279     struct memo *memo;
280     unsigned int ii;
281     char posted[24];
282     time_t feh;
283
284     if (!(ma = memoserv_get_account(user->handle_info)))
285         return 0;
286     reply("MSMSG_LIST_HEAD");
287     for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
288         memo = ma->recvd.list[ii];
289         feh = memo->sent;
290         strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", localtime(&feh));
291         reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, posted);
292     }
293     if (ii == 0)
294         reply("MSG_NONE");
295     else if (ii == 15)
296         reply("MSMSG_CLEAN_INBOX", ii);
297     else
298         reply("MSMSG_MEMOS_FOUND", ii);
299     return 1;
300 }
301
302 static MODCMD_FUNC(cmd_read)
303 {
304     struct memo_account *ma;
305     unsigned int memoid;
306     struct memo *memo;
307     char posted[24];
308     time_t feh;
309
310     if (!(ma = memoserv_get_account(user->handle_info)))
311         return 0;
312     if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
313         return 0;
314     strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", localtime(&feh));
315     reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
316     send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
317     memo->is_read = 1;
318     return 1;
319 }
320
321 static MODCMD_FUNC(cmd_delete)
322 {
323     struct memo_account *ma;
324     struct memo *memo;
325     unsigned int memoid;
326
327     if (!(ma = memoserv_get_account(user->handle_info)))
328         return 0;
329     if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
330         if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
331             reply("MSMSG_USE_CONFIRM");
332             return 0;
333         }
334         while (ma->recvd.used)
335             delete_memo(ma->recvd.list[0]);
336         reply("MSMSG_DELETED_ALL");
337         return 1;
338     }
339
340     if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
341         return 0;
342     delete_memo(memo);
343     reply("MSMSG_MEMO_DELETED", memoid);
344     return 1;
345 }
346
347 static MODCMD_FUNC(cmd_expire)
348 {
349     unsigned long old_expired = memosExpired;
350     do_expire();
351     reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
352     return 1;
353 }
354
355 static MODCMD_FUNC(cmd_expiry)
356 {
357     char interval[INTERVALLEN];
358
359     if (!memoserv_conf.message_expiry) {
360         reply("MSMSG_EXPIRY_OFF");
361         return 1;
362     }
363
364     intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
365     reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
366     return 1;
367 }
368
369 static MODCMD_FUNC(cmd_set_notify)
370 {
371     struct memo_account *ma;
372     char *choice;
373
374     if (!(ma = memoserv_get_account(user->handle_info)))
375         return 0;
376     if (argc > 1) {
377         choice = argv[1];
378         if (enabled_string(choice)) {
379             ma->flags |= MEMO_NOTIFY_NEW;
380         } else if (disabled_string(choice)) {
381             ma->flags &= ~MEMO_NOTIFY_NEW;
382         } else {
383             reply("MSG_INVALID_BINARY", choice);
384             return 0;
385         }
386     }
387
388     choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
389     reply("MSMSG_SET_NOTIFY", choice);
390     return 1;
391 }
392
393 static MODCMD_FUNC(cmd_set_authnotify)
394 {
395     struct memo_account *ma;
396     char *choice;
397
398     if (!(ma = memoserv_get_account(user->handle_info)))
399         return 0;
400     if (argc > 1) {
401         choice = argv[1];
402         if (enabled_string(choice)) {
403             ma->flags |= MEMO_NOTIFY_LOGIN;
404         } else if (disabled_string(choice)) {
405             ma->flags &= ~MEMO_NOTIFY_LOGIN;
406         } else {
407             reply("MSG_INVALID_BINARY", choice);
408             return 0;
409         }
410     }
411
412     choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
413     reply("MSMSG_SET_AUTHNOTIFY", choice);
414     return 1;
415 }
416
417 static MODCMD_FUNC(cmd_set_private)
418 {
419     struct memo_account *ma;
420     char *choice;
421
422     if (!(ma = memoserv_get_account(user->handle_info)))
423         return 0;
424     if (argc > 1) {
425         choice = argv[1];
426         if (enabled_string(choice)) {
427             ma->flags |= MEMO_DENY_NONCHANNEL;
428         } else if (disabled_string(choice)) {
429             ma->flags &= ~MEMO_DENY_NONCHANNEL;
430         } else {
431             reply("MSG_INVALID_BINARY", choice);
432             return 0;
433         }
434     }
435
436     choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
437     reply("MSMSG_SET_PRIVATE", choice);
438     return 1;
439 }
440
441 static MODCMD_FUNC(cmd_status)
442 {
443     reply("MSMSG_STATUS_TOTAL", memoCount);
444     reply("MSMSG_STATUS_EXPIRED", memosExpired);
445     reply("MSMSG_STATUS_SENT", memosSent);
446     return 1;
447 }
448
449 static void
450 memoserv_conf_read(void)
451 {
452     dict_t conf_node;
453     const char *str;
454
455     str = "modules/memoserv";
456     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
457         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
458         return;
459     }
460
461     str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
462     memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
463 }
464
465 static int
466 memoserv_saxdb_read(struct dict *db)
467 {
468     char *str;
469     struct handle_info *sender, *recipient;
470     struct record_data *hir;
471     struct memo *memo;
472     dict_iterator_t it;
473     unsigned long sent;
474
475     for (it = dict_first(db); it; it = iter_next(it)) {
476         hir = iter_data(it);
477         if (hir->type != RECDB_OBJECT) {
478             log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
479             continue;
480         }
481
482         if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
483             log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", iter_key(it));
484             continue;
485         }
486         sent = strtoul(str, NULL, 0);
487
488         if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
489             log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", iter_key(it));
490             continue;
491         } else if (!(recipient = get_handle_info(str))) {
492             log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, iter_key(it));
493             continue;
494         }
495
496         if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
497             log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", iter_key(it));
498             continue;
499         } else if (!(sender = get_handle_info(str))) {
500             log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, iter_key(it));
501             continue;
502         }
503
504         if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
505             log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", iter_key(it));
506             continue;
507         }
508
509         memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str);
510         if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
511             memo->is_read = 1;
512     }
513     return 0;
514 }
515
516 static int
517 memoserv_saxdb_write(struct saxdb_context *ctx)
518 {
519     dict_iterator_t it;
520     struct memo_account *ma;
521     struct memo *memo;
522     char str[7];
523     unsigned int id = 0, ii;
524
525     for (it = dict_first(memos); it; it = iter_next(it)) {
526         ma = iter_data(it);
527         for (ii = 0; ii < ma->recvd.used; ++ii) {
528             memo = ma->recvd.list[ii];
529             saxdb_start_record(ctx, inttobase64(str, id++, sizeof(str)), 0);
530             saxdb_write_int(ctx, KEY_SENT, memo->sent);
531             saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
532             saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
533             saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
534             if (memo->is_read)
535                 saxdb_write_int(ctx, KEY_READ, 1);
536             saxdb_end_record(ctx);
537         }
538     }
539     return 0;
540 }
541
542 static void
543 memoserv_cleanup(void)
544 {
545     dict_delete(memos);
546 }
547
548 static void
549 memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
550 {
551     unsigned int ii, unseen;
552     struct memo_account *ma;
553     struct memo *memo;
554
555     if (!(ma = memoserv_get_account(user->handle_info))
556         || !(ma->flags & MEMO_NOTIFY_LOGIN))
557         return;
558     for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
559         memo = ma->recvd.list[ii];
560         if (!memo->is_read)
561             unseen++;
562     }
563     if (ma->recvd.used && memoserv_conf.bot)
564         send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
565 }
566
567 static void
568 memoserv_rename_account(struct handle_info *hi, const char *old_handle)
569 {
570     struct memo_account *ma;
571     if (!(ma = dict_find(memos, old_handle, NULL)))
572         return;
573     dict_remove2(memos, old_handle, 1);
574     dict_insert(memos, hi->handle, ma);
575 }
576
577 static void
578 memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
579 {
580     dict_remove(memos, handle->handle);
581 }
582
583 int
584 memoserv_init(void)
585 {
586     MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
587     memos = dict_new();
588     dict_set_free_data(memos, delete_memo_account);
589     reg_auth_func(memoserv_check_messages);
590     reg_handle_rename_func(memoserv_rename_account);
591     reg_unreg_func(memoserv_unreg_account);
592     conf_register_reload(memoserv_conf_read);
593     reg_exit_func(memoserv_cleanup);
594     saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
595
596     memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
597     modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
598     modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
599     modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
600     modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
601     modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
602     modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
603     modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
604     modcmd_register(memoserv_module, "set notify", cmd_set_notify, 1, 0, NULL);
605     modcmd_register(memoserv_module, "set authnotify", cmd_set_authnotify, 1, 0, NULL);
606     modcmd_register(memoserv_module, "set private", cmd_set_private, 1, 0, NULL);
607     message_register_table(msgtab);
608
609     if (memoserv_conf.message_expiry)
610         timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
611     return 1;
612 }
613
614 int
615 memoserv_finalize(void) {
616     dict_t conf_node;
617     const char *str;
618
619     str = "modules/memoserv";
620     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
621         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
622         return 0;
623     }
624
625     str = database_get_data(conf_node, "bot", RECDB_QSTRING);
626     if (str)
627         memoserv_conf.bot = GetUserH(str);
628     return 1;
629 }