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