[project @ 2005-07-04 01:48:38 by unknown_lamer]
[clinton/bobotpp.git] / source / Bot.C
1 // Bot.C -*- C++ -*-
2 // Copyright (c) 1997, 1998 Etienne BERNARD
3 // Copyright (C) 2002,2003,2005 Clinton Ebadi
4
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // any later version.
9
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19 #include <fstream>
20 #include <algorithm>
21 #include <iomanip>
22 #include <cstring>
23 #include <cstdlib>
24 #include <cstdio>
25 #include <sys/time.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28
29 #include "Bot.H"
30 #include "DCCConnection.H"
31 #include "DCCChatConnection.H"
32 #include "StringTokenizer.H"
33 #include "ServerConnection.H"
34 #include "Utils.H"
35 #include "UserCommands.H"
36 #include "DCCManager.H"
37
38
39 unsigned int Bot::MAX_MESSAGES = 2;
40 unsigned int Bot::MAX_NICKLENGTH = 9;
41
42 #define DEFAULT_NICKNAME "Bobot"
43 #define DEFAULT_USERNAME "bobot"
44 #define DEFAULT_IRCNAME "I'm a bobot++!"
45 #define DEFAULT_COMMANDCHAR '!'
46 #define DEFAULT_USERLISTFILENAME "bot.users"
47 #define DEFAULT_SHITLISTFILENAME "bot.shit"
48 #define DEFAULT_HELPFILENAME "bot.help"
49 #define DEFAULT_SCRIPTLOGFILENAME "script.log"
50 #define DEFAULT_LOGFILENAME "bot.log"
51 #define DEFAULT_LOGDIR getenv ("HOME") + String("/.bobotpp/logs/")
52 #define DEFAULT_INITFILENAME "bot.init"
53 #ifdef USESCRIPTS
54 #define DEFAULT_AUTOEXECFILENAME "bot.autoexec"
55 #endif
56
57 Bot::Bot(String filename, bool debug_on)
58 : nickName(DEFAULT_NICKNAME),
59 wantedNickName(DEFAULT_NICKNAME),
60 userName(DEFAULT_USERNAME),
61 ircName(DEFAULT_IRCNAME),
62 versionString(VERSION_STRING),
63 userHost(""),
64 localIP(""),
65 commandChar(DEFAULT_COMMANDCHAR),
66 userListFileName(DEFAULT_USERLISTFILENAME),
67 shitListFileName(DEFAULT_SHITLISTFILENAME),
68 helpFileName(DEFAULT_HELPFILENAME),
69 initFileName(DEFAULT_INITFILENAME),
70 connected(false),
71 debug(debug_on),
72 stop(false),
73 sentPing(false),
74 startTime(time(NULL)),
75 currentTime(startTime),
76 lastNickNameChange(startTime),
77 lastChannelJoin(startTime),
78 serverConnection(0),
79 sentUserhostID(0),
80 receivedUserhostID(0),
81 logFileName(DEFAULT_LOGFILENAME),
82 logs_dir (DEFAULT_LOGDIR),
83 configFileName (filename)
84 #ifdef USESCRIPTS
85 ,scriptLogFileName(DEFAULT_SCRIPTLOGFILENAME),
86 autoexecFileName(DEFAULT_AUTOEXECFILENAME)
87 #endif
88 {
89 #ifdef HAVE_STL_CLEAR
90 wantedChannels.clear();
91 ignoredUserhosts.clear();
92 spyList.clear();
93 userhostMap.clear();
94 #endif
95
96 init_user_functions ();
97
98 set_log_dir (logs_dir);
99 set_log_file (logFileName);
100
101
102 channelList = new ChannelList();
103 serverList = new ServerList();
104 readConfig();
105 userList = new UserList(userListFileName);
106 shitList = new ShitList(shitListFileName);
107 todoList = new TodoList();
108 dccConnections = new DCCManager ();
109
110 // Let's read the alias file
111 std::ifstream initFile(initFileName);
112
113 if (initFile)
114 {
115 // FIXME: these variables are current String instead of
116 // std::string because String>> reads an entire line. This code
117 // needs to be rewritten to use std::string and std::getline (or
118 // better yet, be removed entirely once BotConfig is in place)
119 String temp, alias, command;
120 int line = 0;
121
122 while (initFile >> temp, temp.length() != 0)
123 {
124 StringTokenizer st(temp);
125
126 line++;
127 temp = Utils::trim_str (temp);
128
129 if (temp[0]=='#')
130 {
131 continue;
132 }
133
134 if (st.count_tokens (' ') != 2)
135 {
136 std::cerr << "Error when reading alias file (" << initFileName
137 << ") line " << line << "...\n";
138 continue;
139 }
140
141 alias = Utils::to_upper (st.next_token());
142 command = Utils::to_upper (st.next_token());
143
144 // Does the function already exist ?
145 if (!userFunctions[alias])
146 {
147 if (userFunction *u = userFunctions[command])
148 userFunctions[alias] =
149 new
150 userFunction(u->function,
151 u->minLevel,
152 u->needsChannelName);
153 }
154 }
155 }
156
157
158 std::srand (std::time (0)); // srand for bot-random
159
160 #ifdef USESCRIPTS
161 botInterp = new BotInterp(this, logs_dir + scriptLogFileName);
162 botInterp->LoadScript(autoexecFileName);
163 #endif
164 }
165
166 Bot::~Bot()
167 {
168 Person *p;
169 while (spyList.size() != 0) {
170 p = (*spyList.begin()).second;
171 spyList.erase(spyList.begin());
172 delete p;
173 }
174 delete dccConnections;
175 destroy_user_functions ();
176
177 wantedChannel *w;
178 while (wantedChannels.size() != 0) {
179 w = (*wantedChannels.begin()).second;
180 wantedChannels.erase(wantedChannels.begin());
181 delete w;
182 }
183 userList->save();
184 shitList->save();
185 delete channelList;
186 delete userList;
187 delete todoList;
188 delete serverList;
189 delete shitList;
190 delete serverConnection;
191 logLine("Stopping log.");
192 logFile.close();
193 }
194
195 void
196 Bot::logLine(String line)
197 {
198 tm *d;
199 std::time_t current_time = time(0);
200
201 d = localtime(&current_time);
202 logFile << "[" << std::setfill('0') << std::setw(2)
203 << d->tm_mday << "/" << std::setfill('0') << std::setw(2)
204 << d->tm_mon + 1 << "/"
205 << d->tm_year + 1900 << " - " << std::setfill('0') << std::setw(2)
206 << d->tm_hour << ":" << std::setfill('0') << std::setw(2)
207 << d->tm_min << ":" << std::setfill('0') << std::setw(2)
208 << d->tm_sec << "] "
209 << line
210 << std::endl;
211 }
212
213 void
214 Bot::readConfig()
215 {
216 std::ifstream file(configFileName);
217 String temp;
218 int line = 1;
219
220 if (!file) {
221 logLine(String("I cannot find the file ") + configFileName);
222 return;
223 }
224
225 while (!file.eof()) {
226
227 file >> temp;
228
229 if (temp.length() == 0 || temp[(unsigned int)0] == '#') {
230 line++;
231 continue;
232 }
233
234 StringTokenizer st(temp);
235 String command = Utils::to_upper (Utils::trim_str (st.next_token('=')));
236 String parameters = Utils::trim_str (st.next_token('='));
237
238 if (command == "NICK" || command == "NICKNAME")
239 nickName = wantedNickName = parameters;
240 else if (command == "USERNAME")
241 userName = parameters;
242 else if (command == "IRCNAME" || command == "REALNAME")
243 ircName = parameters;
244 else if (command == "CMDCHAR" || command == "COMMAND")
245 commandChar = parameters[(unsigned int)0];
246 else if (command == "USERLIST")
247 userListFileName = parameters;
248 else if (command == "SHITLIST")
249 shitListFileName = parameters;
250 else if (command == "CHANNEL") {
251 if (parameters.indexOf(':') == -1) {
252 std::cout << "Warning. The 'channel' syntax has changed."
253 << " Please see the README file for more information."
254 << " I will use compatibility mode, but you're really"
255 << " missing something.\n";
256 StringTokenizer st2(parameters);
257 String name = Utils::to_lower (st2.next_token());
258 String key = st2.next_token();
259 wantedChannels[name] = new wantedChannel("", "", key);
260 } else {
261 StringTokenizer st2(parameters);
262 String name = Utils::to_lower (st2.next_token(':'));
263 String mode = st2.next_token(':');
264 String keep = st2.next_token(':');
265 String key = st2.next_token(':');
266 wantedChannels[name] = new wantedChannel(mode, keep, key);
267 }
268 }
269 else if (command == "LOGFILE")
270 {
271 if (parameters != logFileName)
272 {
273 if (parameters[(unsigned int)0] == '/')
274 {
275 set_log_dir ("/");
276 set_log_file (parameters.subString (1));
277 }
278 else
279 set_log_file (parameters);
280 }
281 }
282 #ifdef USESCRIPTS
283 else if (command == "SCRIPTLOGFILE")
284 scriptLogFileName = parameters;
285 else if (command == "AUTOEXECFILE")
286 autoexecFileName = parameters;
287 #endif
288 else if (command == "INITFILE")
289 initFileName = parameters;
290 else if (command == "LOCALIP")
291 localIP = parameters;
292 else if (command == "MAXNICKLENGTH")
293 MAX_NICKLENGTH = std::atoi (parameters);
294 else if (command == "SERVER") {
295 if (parameters.indexOf(' ') == -1)
296 serverList->addServer(new Server(parameters));
297 else {
298 StringTokenizer st2(parameters);
299 String name = st2.next_token();
300 int port = std::atoi(st2.next_token().c_str());
301 serverList->addServer(new Server(name,
302 port,
303 st2.next_token()));
304 }
305 }
306 else {
307 logLine(String("Syntax error in file ") + configFileName +
308 ", line " + String((long)line));
309 file.close();
310 std::exit(1);
311 }
312
313 line++;
314 }
315
316 file.close();
317 }
318
319 void
320 Bot::run()
321 {
322 nextServer();
323
324 while (!stop)
325 {
326 waitForInput(); // This is the main event loop
327 dccConnections->checkStale ();
328
329 if (!serverConnection->queue->flush())
330 {
331 // Disconnected
332 #ifdef USESCRIPTS
333 // Run hooks/disconnect
334 this->botInterp->RunHooks
335 (Hook::DISCONNECT,
336 serverConnection->server->getHostName (),
337 scm_list_n
338 (Utils::str2scm (serverConnection->server->getHostName ()),
339 SCM_BOOL_F));
340 #endif
341 nextServer();
342 }
343 }
344 }
345
346 void
347 Bot::waitForInput()
348 {
349 #ifdef _HPUX_SOURCE
350 int rd;
351 #else
352 fd_set rd;
353 #endif
354 struct timeval timer;
355
356 int sock = serverConnection->getFileDescriptor();
357 int maxSocketNumber = sock;
358
359 #ifdef _HPUX_SOURCE
360 rd = sock;
361 #else
362 FD_ZERO(&rd);
363 FD_SET(sock, &rd);
364 #endif
365
366 DCC_MAP* dccmap = &dccConnections->dcc_map;
367 for (DCC_MAP::iterator it = dccmap->begin ();
368 it != dccmap->end(); ++it) {
369 int s = it->second->dcc->getFileDescriptor();
370 #ifdef _HPUX_SOURCE
371 rd |= s;
372 #else
373 FD_SET(s, &rd);
374 #endif
375 if (s > maxSocketNumber)
376 maxSocketNumber = s;
377 }
378
379 timer.tv_sec = 1;
380 timer.tv_usec = 0;
381
382 switch (select(maxSocketNumber + 1, &rd, NULL, NULL, &timer)) {
383 case 0: /* timeout */
384 break;
385 case -1: /* error */
386 break;
387 default: /* normal */
388 #ifdef _HPUX_SOURCE
389 if (rd & sock)
390 #else
391 if (FD_ISSET(sock, &rd))
392 #endif
393 if (serverConnection->handleInput())
394 nextServer();
395
396 dccConnections->checkInput (rd);
397 }
398
399 if (currentTime < std::time(0)) { // Actions that we do each second
400 currentTime = std::time(0);
401 for (std::map<String, unsigned int, std::less<String> >::iterator
402 it = ignoredUserhosts.begin();
403 it != ignoredUserhosts.end(); ++it)
404 if ((*it).second > 0)
405 (*it).second--;
406
407 String line;
408 while ((line = todoList->getNext()) != "") {
409 serverConnection->queue->sendChannelMode(line);
410 }
411 #ifdef USESCRIPTS
412 botInterp->RunTimers(currentTime);
413
414 tm *thisTime = localtime(&currentTime);
415 if (thisTime->tm_sec == 0)
416 {
417 char s[6];
418 std::snprintf(s, 6, "%02d:%02d", thisTime->tm_hour, thisTime->tm_min);
419
420 botInterp->RunHooks(Hook::TIMER, String(s),
421 scm_list_n (Utils::str2scm (std::string (s)),
422 SCM_UNDEFINED));
423 }
424 #endif
425
426 }
427
428 if (currentTime >= (time_t)(lastNickNameChange + Bot::NICK_CHANGE) &&
429 nickName != wantedNickName) {
430 lastNickNameChange = currentTime;
431 serverConnection->queue->sendNick(wantedNickName);
432 }
433
434 if (currentTime >= (std::time_t)(lastChannelJoin + Bot::CHANNEL_JOIN)) {
435 lastChannelJoin = currentTime;
436 for (std::map<String, wantedChannel *, std::less<String> >::iterator it =
437 wantedChannels.begin(); it != wantedChannels.end();
438 ++it)
439 if (channelList->getChannel((*it).first) == 0)
440 serverConnection->queue->sendJoin((*it).first, (*it).second->key);
441 }
442
443 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken
444 + Bot::PING_TIME) && !sentPing)
445 {
446 serverConnection->queue->sendPing("Testing connection");
447 sentPing = true;
448 }
449
450 if (currentTime >= (std::time_t)(serverConnection->serverLastSpoken
451 + Bot::TIMEOUT))
452 {
453 sentPing = false;
454 nextServer();
455 }
456 }
457
458 // We can change server if we will not lose op on a channel
459 bool
460 Bot::canChangeServer()
461 {
462 String channel;
463 Channel *c;
464
465 for (std::map<String, Channel *, std::less<String> >::iterator it =
466 channelList->begin();
467 it != channelList->end(); ++it) {
468 channel = (*it).first;
469 c = channelList->getChannel(channel);
470 if (c->countOp == 1 &&
471 c->count > 1 && this->iAmOp(channel))
472 return false;
473 }
474 return true;
475 }
476
477 void
478 Bot::nextServer()
479 {
480 bool cont = false;
481
482 if (channelList)
483 channelList->clear();
484
485 if (serverConnection)
486 userList->removeFirst();
487
488 delete serverConnection;
489
490 do {
491 Server * s = serverList->nextServer();
492 if (!s) {
493 std::cout << "No server found. Exiting..." << std::endl;
494 std::exit(1);
495 }
496 serverConnection = new ServerConnection(this, s, localIP);
497 if (!serverConnection->connect()) {
498 cont = true;
499 // We sleep 10 seconds, to avoid connection flood
500 sleep(10);
501 delete serverConnection;
502 } else {
503 cont = false;
504 }
505 } while (cont);
506 }
507
508 void
509 Bot::reconnect()
510 {
511 if (channelList)
512 channelList->clear();
513
514 userList->removeFirst();
515
516 delete serverConnection;
517
518 serverConnection =
519 new ServerConnection(this, serverList->currentServer(), localIP);
520
521 serverConnection->connect();
522 }
523
524 void
525 Bot::connect(int serverNumber)
526 {
527 if (channelList)
528 channelList->clear();
529
530 userList->removeFirst();
531
532 delete serverConnection;
533
534 serverConnection =
535 new ServerConnection(this, serverList->get(serverNumber), localIP);
536
537 serverConnection->connect();
538 }
539
540 void
541 Bot::addDCC(Person * from, unsigned long address, int port, int type)
542 {
543 DCCConnection *d = 0;
544
545 if (type == CHAT)
546 {
547 d = new DCCChatConnection(this, from->getAddress (),
548 address, port);
549 }
550 else
551 {
552 return;
553 }
554
555 if (!d->connect())
556 {
557 logLine ("DCC Connection failed from " + from->getAddress ());
558 return;
559 }
560
561 logLine ("DCC CHAT accepted from" + from->getAddress ());
562 dccConnections->addConnection (d);
563 }
564
565 void
566 Bot::rehash()
567 {
568 for (std::map<String, Channel *, std::less<String> >::iterator it =
569 channelList->begin();
570 it != channelList->end(); ++it)
571 serverConnection->queue->sendWho((*it).first);
572 }
573
574 String
575 Bot::getUserhost(String channel, String nick)
576 {
577 Channel *c;
578
579 if (channel == "")
580 c = 0;
581 else
582 c = channelList->getChannel(channel);
583
584 nick = nick.toLower();
585
586
587 if (c && c->hasNick(nick))
588 return c->getUser(nick)->userhost;
589
590 unsigned long num = sentUserhostID++;
591
592 serverConnection->queue->sendUserhost(nick);
593 userhostMap[num] = "+";
594
595 while (userhostMap[num] == "+") {
596 waitForInput();
597 serverConnection->queue->flush();
598 }
599
600 // We have got our answer
601 String res = userhostMap[num];
602 userhostMap.erase(num);
603
604 return res;
605 }
606
607 bool
608 Bot::iAmOp(String channel)
609 {
610 User * me = channelList->getChannel(channel)->getUser(nickName);
611 return (me->mode & User::OP_MODE);
612 }
613
614 void
615 Bot::init_user_functions ()
616 {
617 // User Functions
618 #define uf(f, l, b) new userFunction (f, l, b);
619 userFunctions["ACTION"] = uf (UserCommands::Action, User::USER, true);
620 userFunctions["ADDUSER"] = uf (UserCommands::AddUser, User::FRIEND, false);
621 userFunctions["ADDSERVER"] = uf (UserCommands::AddServer, User::FRIEND,
622 false);
623 userFunctions["ADDSHIT"] = uf (UserCommands::AddShit, User::FRIEND, false);
624 userFunctions["ALIAS"] = uf (UserCommands::Alias, User::MASTER, false);
625 userFunctions["BAN"] = uf (UserCommands::Ban, User::USER, true);
626 userFunctions["BANLIST"] = uf (UserCommands::BanList, User::USER, true);
627 userFunctions["CHANNELS"] =
628 uf (UserCommands::Channels, User::FRIEND, false);
629 userFunctions["CYCLE"] = uf (UserCommands::Cycle, User::FRIEND, true);
630 userFunctions["DCCLIST"] = uf (UserCommands::DCCList, User::FRIEND, false);
631 userFunctions["DEBAN"] = uf (UserCommands::Deban, User::USER, true);
632 userFunctions["DELSERVER"] = uf (UserCommands::DelServer, User::FRIEND,
633 false);
634 userFunctions["DELUSER"] = uf (UserCommands::DelUser, User::FRIEND, false);
635 userFunctions["DELSHIT"] = uf (UserCommands::DelShit, User::FRIEND, false);
636 userFunctions["DEOP"] = uf (UserCommands::Deop, User::TRUSTED_USER, true);
637 userFunctions["DIE"] = uf (UserCommands::Die, User::MASTER, false);
638 userFunctions["DO"] = uf (UserCommands::Do, User::MASTER, false);
639 #ifdef USESCRIPTS
640 userFunctions["EXECUTE"] = uf (UserCommands::Execute, User::MASTER, false);
641 #endif
642 userFunctions["HELP"] = uf (UserCommands::Help, User::NONE, false);
643 userFunctions["IDENT"] = uf (UserCommands::Ident, User::NONE, true);
644 userFunctions["INVITE"] = uf (UserCommands::Invite, User::USER, true);
645 userFunctions["JOIN"] = uf (UserCommands::Join, User::FRIEND, false);
646 userFunctions["KEEP"] = uf (UserCommands::Keep, User::FRIEND, true);
647 userFunctions["KICK"] = uf (UserCommands::Kick, User::USER, true);
648 userFunctions["KICKBAN"] = uf (UserCommands::KickBan, User::USER, true);
649 userFunctions["LOAD"] = uf (UserCommands::Load, User::FRIEND, false);
650 #ifdef USESCRIPTS
651 userFunctions["LOADSCRIPT"] = uf (UserCommands::LoadScript, User::MASTER,
652 false);
653 #endif
654 userFunctions["LOCK"] = uf (UserCommands::Lock, User::FRIEND, true);
655 userFunctions["MODE"] = uf (UserCommands::Mode, User::FRIEND, true);
656 userFunctions["MSG"] = uf (UserCommands::Msg, User::USER, false);
657 userFunctions["NAMES"] = uf (UserCommands::Names, User::USER, true);
658 userFunctions["NEXTSERVER"] = uf (UserCommands::NextServer, User::FRIEND,
659 false);
660 userFunctions["NICK"] = uf (UserCommands::Nick, User::FRIEND, false);
661 userFunctions["NSLOOKUP"] = uf (UserCommands::NsLookup, User::USER, false);
662 userFunctions["OP"] = uf (UserCommands::Op, User::TRUSTED_USER, true);
663 userFunctions["PART"] = uf (UserCommands::Part, User::FRIEND, true);
664 userFunctions["PASSWORD"] = uf (UserCommands::Password, User::USER, true);
665 userFunctions["RECONNECT"] =
666 uf (UserCommands::Reconnect, User::FRIEND, false);
667 userFunctions["RSPYMESSAGE"] =
668 uf (UserCommands::RSpyMessage, User::USER, false);
669 userFunctions["SAVE"] = uf (UserCommands::Save, User::FRIEND, false);
670 userFunctions["SAY"] = uf (UserCommands::Say, User::USER, true);
671 userFunctions["SERVER"] = uf (UserCommands::Server, User::FRIEND, false);
672 userFunctions["SERVERLIST"] =
673 uf (UserCommands::ServerList, User::FRIEND, false);
674 userFunctions["SETFLOODRATE"] =
675 uf (UserCommands::SetFloodRate, User::MASTER, false);
676 userFunctions["SETVERSION"] =
677 uf (UserCommands::SetVersion, User::MASTER, false);
678 userFunctions["SHITLIST"] =
679 uf (UserCommands::ShitList, User::FRIEND, false);
680 userFunctions["SPYLIST"] = uf (UserCommands::SpyList, User::USER, false);
681 userFunctions["SPYMESSAGE"] =
682 uf (UserCommands::SpyMessage, User::USER, false);
683 userFunctions["STATS"] = uf (UserCommands::Stats, User::FRIEND, true);
684 userFunctions["TBAN"] = uf (UserCommands::TBan, User::USER, true);
685 userFunctions["TKBAN"] = uf (UserCommands::TKBan, User::USER, true);
686 userFunctions["TOPIC"] = uf (UserCommands::Topic, User::USER, true);
687 userFunctions["UNLOCK"] = uf (UserCommands::Unlock, User::FRIEND, true);
688 userFunctions["USERLIST"] =
689 uf (UserCommands::UserList, User::FRIEND, false);
690 userFunctions["WHO"] = uf (UserCommands::Who, User::NONE, true);
691 userFunctions["WHOIS"] = uf (UserCommands::Whois, User::FRIEND, true);
692 #undef uf
693 }
694
695 namespace
696 {
697 void erase_userf (std::pair<std::string, class userFunction*> it)
698 {
699 delete it.second;
700 }
701 }
702
703 void
704 Bot::destroy_user_functions ()
705 {
706 std::for_each (userFunctions.begin (),
707 userFunctions.end (),
708 erase_userf);
709 userFunctions.erase (userFunctions.begin (),
710 userFunctions.end ());
711 }
712
713 void
714 Bot::set_log_file (String name)
715 {
716 logFileName = name;
717 logFile.close ();
718 logFile.clear ();
719 #if HAVE_IOSBASE
720 logFile.open(logs_dir + logFileName, std::ios_base::out |
721 std::ios_base::ate | std::ios_base::app);
722 #else
723 logFile.open(logs_dir + logFileName, ios::out | ios::ate
724 | ios::app);
725 #endif
726
727 logLine("Starting log.");
728 }
729
730 void
731 Bot::set_log_dir (String dir)
732 {
733 logs_dir = dir;
734 }