21
[irbot.git] / sources / main-class.inc.php
blobfeaf0e37b9b1b411f4219d2809dbea058e465ec9
1 <?php
2 /**
3 * This file is part of IrBot, irc robot.
4 * Copyright (C) 2007 Bellière Ludovic
6 * This program 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 3 of the License, or
9 * (at your option) any later version.
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.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 final class bot {
22 // Timeout avant une reconnexion
23 public $socketTimeout = 280;
25 // Variables privées
26 static public $instance = FALSE;
28 protected $C;
30 static $botVersion = '1.0';
31 static $server;
32 static $port;
33 static $channel;
34 static $myBotName;
35 static $ip;
36 static $domain;
37 static $connection_password;
39 public $irc;
40 public $tick;
41 public $plugins;
42 public $formater;
43 public $base;
44 public $msg;
45 public $connected = false;
47 private $core_commands;
48 private $users_list;
49 private $msg_info;
51 public static function GetInstance() {
52 if (!self::$instance) {
53 self::$instance = new bot();
54 self::$instance->plugins = new plugins();
55 self::$instance->irc = new irc;
56 self::$instance->tick = tick::GetInstance();
58 return self::$instance;
61 private function __construct() {
62 // load config and other things echo "oups main\n";die;
63 $this->formater = text_format::GetInstance();
64 $this->connected = false;
67 private function load_core_plugin() {
68 // core plugin is an exeption in do_command
69 $this->plugins->add_command('core','shownick',0,'Show the current nick used (debug)','mixed');
70 $this->plugins->add_command('core','nick',1,'Change the current nick','mixed');
71 $this->plugins->add_command('core','quit',0,'Disconnect and stop the process','mixed');
72 $this->plugins->add_command('core','restart',0,'Disconnect and restart the process','mixed');
73 $this->plugins->add_command('core','help',0,'Hmm, je dois recoder cette fonction','mixed');
76 public function launch() {
77 self::load_core_plugin();
78 try {
79 //$this->C = @fsockopen(self::$server, self::$port, $errno, $errstr, 10);
80 $this->C = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
81 if ($this->C === false) {
82 throw new Exception('Impossible de créer le socket IRC !',0);
85 if (self::$ip != "") { socket_bind($this->C, self::$ip); }
87 $x = socket_connect($this->C, self::$server, self::$port);
88 if ($x===false) {
89 throw new Exception('Impossible de se connecter au server IRC ! ('.socket_last_error().')' ,0);
92 $this->connected = true;
94 if (self::$connection_password !== false) {
95 $this->put('PASS '.self::$connection_password);
97 // TODO : be sure for the validity of the connection password (what chain server return if fail ?)
99 $this->put('USER '.self::$myBotName.' '.self::$myBotName.'@'.self::$ip.' '.self::$server.' :'.self::$myBotName);
100 $this->put('NICK '.self::$myBotName);
102 /*$this->tick->setTick('all5sec',5);
103 $this->tick->addJob('all5sec','hello chan','privmsg',array(self::$channel,"I'm a tick. I show this msg all 5sec."),0,$this);
105 while (1) {
106 $this->msg = $this->get();
107 if (is_array($this->msg)) {
108 foreach ($this->msg as $msg) {
109 if (!empty($msg)) {
110 $this->irc->parse_get($msg);
111 $this->work_on_listen();
114 } else {
115 $this->irc->parse_get($this->msg);
116 $this->work_on_listen();
119 } catch (myRuntimeException $e) {
120 $x = $e->getMessage();
121 echo $x;
122 $x.= backtrace($e->getTrace());
123 file_put_contents('errorlog',$x);
124 //if ($e->_level < 2 || ($e->_level >= 256 && $e->_level < 1024)) {
125 echo 'Error level : '.$e->_level."\n\n";
126 throw new Exception('Error occured',0);
127 /*} else {
128 echo 'Error level : '.$e->_level."\n\n\n\n";
129 throw new Exception("Error occured. Please see errorlog for details.",1);
131 } catch (Exception $e) {
132 throw $e;
136 private function work_on_listen () {
137 $this->msg_info = $msg_info = $this->irc->get_msg_info();
139 switch ($msg_info['type']) {
141 case 'PRIVMSG':
143 // ctcp
144 if ($msg_info['message'][0] == chr(001)) {
146 // we don't need this character
147 $msg_info['message'] = str_replace(chr(001), "", $msg_info['message']);
148 if (strstr($msg_info['message'],' ')===true) {
149 list($cmd,$query) = explode(" ",$msg_info['message']);
150 } else {
151 $cmd = $msg_info['message'];
154 // we don't need this character
155 //$cmd = str_replace(chr(001), "", $msg_info['message']);
156 $cmd = trim($cmd);
158 switch ($cmd) {
159 case 'CLIENTINFO':
160 self::notice($msg_info['from'],$this->formater->ctcp('PING VERSION TIME USERINFO CLIENTINFO'));
161 break;
163 case 'VERSION':
164 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot version '.bot::$botVersion.' - PHP '.phpversion().' -- par Tornald et Bloodshed'));
165 break;
167 case 'USERINFO':
168 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot'));
169 break;
171 case 'TIME':
172 self::notice($msg_info['from'],$this->formater->ctcp(date('Y-m-d H:i:s')));
173 break;
175 case 'PING':
176 self::notice($msg_info['from'],$this->formater->ctcp("PING ".$query));
177 break;
178 default:
179 self::notice($msg_info['from'],$this->formater->ctcp("UNKNOWN CTCP REQUEST : '$cmd'"));
180 break;
182 } else {
183 $msg_info['message'] = trim($msg_info['message']);
184 if ($msg_info['to'] == bot::$myBotName) {
185 // auth proccess
186 if (preg_match('`^connect ([^ ]+) ([^ ]+)`',$msg_info['message'],$m)) {
187 if ($m[1] == 'admin' && $m[2] == 'mypass') {
188 $this->auth = true;
189 self::notice($msg_info['from'],'Vous êtes bien authentifié comme administrateur.');
190 } else {
191 self::notice($msg_info['from'],'Erreur dans votre login et / ou mot de passe.');
192 echo debug() ? 'l: '.$m[1].' p: '.$m[2]."\n":'';
194 continue;
198 if ($msg_info['message'][0] == '!') {
199 $message = substr($msg_info['message'],1);
200 $query = explode(' ',$message);
201 $query_count = count($query);
203 if (isset($this->plugins->commands[$query[0]])) {
204 if ($query_count > 1) {
206 if ($this->plugins->commands[$query[0]][$query[1]]['type'] == 'mixed' ||
207 ($msg_info['to'] == bot::$myBotName && $this->plugins->commands[$query[0]][$query[1]]['type'] == 'private') ||
208 ($msg_info['to'] == bot::$channel && $this->plugins->commands[$query[0]][$query[1]]['type'] == 'public')) {
210 * $query = array( plugins, method[, arg[, ...]] )
211 * !plugin method[ arg[ ...]]
213 call_user_func_array(array($this->plugins,'do_command'),array_merge(array('msg_info'=>$msg_info),$query));
214 } else {
215 print_r($msg_info);
216 echo $this->plugins->commands[$query[0]][$query[1]]['type']."\n";
217 $this->privmsg($msg_info['from'],"Error with the request.");
219 } else {
220 // no method : need a list of commands
221 $plugin = $message;
223 $commands = $this->plugins->list_command($plugin);
224 foreach ($commands as $command_info) {
225 if ($command_info['type'] == 'public') {
226 $get_plugin_method = '!'.$plugin;
227 } else {
228 $get_plugin_method = '/msg ' . bot::$myBotName . ' !' . $plugin;
231 $this->privmsg($msg_info['from'],$this->formater->bold('Command :')." $get_plugin_method {$command_info['method']}".(($command_info['accepted_args']>0)?' [some args...]':''));
233 $help = explode("\n",$command_info['help']);
234 foreach($help as $help_msg) {
235 $this->privmsg($msg_info['from'],$help_msg);
239 } else {
240 //var_dump($this->plugins->commands);
244 break;
245 case 'NOTICE':
246 break;
250 public function joinChannel($channel) {
251 $this->put('JOIN '.$channel);
252 echo "Join channel $channel ...\n";
255 public function newNick($new=false) {
256 switch ($new) {
257 case self::$myBotName:
258 echo "New nick : [ERR] no changes :". self::$myBotName . ' == ' . $new ."\n";
259 break;
260 case false:
261 self::$myBotName .= '_';
262 self::nick_change();
263 break;
264 default:
265 self::$myBotName = $new;
266 self::nick_change();
267 break;
272 * Envoie une notice a un salon / utilisateur
274 * @param string $to
275 * @param string $message
277 public function notice ($to,$message) {
278 self::put('NOTICE '.$to.' :'.$message."\n");
282 * Envoie un message (PRIVMSG) a un salon / utilisateur
284 * @param string $to
285 * @param string $message
287 public function privmsg ($to,$message) {
288 self::put('PRIVMSG '.$to.' :'.$message."\n");
291 public function put($data) {
292 if (!is_resource($this->C)) {
293 throw new Exception('Connection lost...',1);
296 echo debug() ? '[->' . $data . "\n" : '';
298 $ok = socket_write($this->C, $data ."\n");
299 if ($ok) {
300 return true;
301 } else {
302 return false;
305 // fputs($this->C, $command . "\n");
306 return true;
309 public function get() {
311 $buffer = '';
312 socket_set_nonblock($this->C);
314 while (1) {
315 //echo "TOC - ",time(),"\n";
316 $this->tick->doAllTicks();
317 $time1 = time();
318 $buf = @socket_read($this->C, 4096);
320 if (empty($buf)) {
321 $this->CheckTimeout($time1);
322 sleep(1);
323 continue;
326 if (!strpos($buf, "\n")) { //Si ne contient aucun retour, on bufferise
327 $buffer = $buffer.$buf;
328 $data = ""; //rien à envoyer
329 } else {
330 //Si contient au moins un retour,
331 //on vérifie que le dernier caractère en est un
332 if (substr($buf, -1, 1) == "\n") {
333 //alors on additionne ces données au buffer
334 $data = $buffer.$buf;
335 $buffer = ""; //on vide le buffer
336 } else {
337 //si le dernier caractère n'est pas un retour à la
338 //ligne, alors on envoit tout jusqu'au dernier retour
339 //puis on bufferise le reste
340 $buffer = $buffer.substr($buf, strrchr($buf, "\n"));
341 $data = substr($buf, 0, strrchr($buf, "\n"));
342 $data = $buffer.$data;
343 $buffer = ""; //on vide le buffer
347 if ($data != '') {
348 $data = split("\n", $data);
349 return $data;
352 continue;
355 echo "TOC - ",time(),"\n";
356 $this->tick->doAllTicks();
357 // Stream Timeout
358 stream_set_timeout($this->C, $this->socketTimeout);
359 $tmp1 = time();
360 $content = fgets($this->C, 1024);
362 echo debug() ? "<-]$content" : '';
364 if (empty($content)) {
365 $this->CheckTimeout($tmp1);
366 sleep(1);
367 continue;
368 } else {
369 return $content;
376 public function shownick() {
377 $this->privmsg(bot::$channel,bot::$myBotName."\n");
380 public function nick($newNick) {
381 $this->newNick($newNick);
384 public function help() {
385 $this->privmsg($this->msg_info['from'],"List of plugins :");
386 foreach($this->plugins->plugin_list as $pluginName => $object) {
387 $this->privmsg($this->msg_info['from'],"!$pluginName");
388 usleep(500000);
392 public function restart() {
393 if ($this->auth) {
394 echo 'Restart...'."\n";
395 $this->disconnect();
396 sleep(5);
397 throw new Exception('Restart...',1);
398 } else {
399 $this->privmsg($this->msg_info['from'],"Vous n'etes pas authentifie.");
403 public function quit() {
404 if ($this->auth) {
405 $this->disconnect();
406 throw new Exception('Quit from',3);
407 } else {
408 $this->privmsg($this->msg_info['from'],"Vous n'etes pas authentifie.");
412 public function disconnect($msg='EOL ;') {
413 $this->plugins->unload_plugin('_all');
414 echo "Closing Link...";
415 if (is_resource($this->C)) {
416 if ($this->put('QUIT :'.$msg)) {
417 $this->connected = false;
418 socket_close($this->C);
420 echo "\t\t\tdone.\n";
421 } else {
422 echo "\t\t\tError.\nConnection already breack down. ".'bot::disconnect'."\n";
424 return true;
427 private function nick_change() {
428 echo "New nick : ".self::$myBotName."\n";
429 if ($this->put('NICK :'.self::$myBotName)) {
430 return true;
432 return false;
435 private function CheckTimeout($time1) {
436 if (time()-$time1 >= $this->socketTimeout) {
437 throw new Exception('TIMEOUT',0);
441 private function load_user($file='users.db') {
442 echo "Loading users ...";
443 $ulist = explode("\n",file_get_contents($file));
444 foreach ($ulist as $user) {
445 list($uname,$upass,$uright) = explode(':',$user);
446 $this->users_list[$uname] = array($upass,$uright);
448 echo "\t\t\tdone.\n";
451 function __destruct() {
452 if (is_resource($this->C)) {
453 if ($this->put('QUIT :Error occured')) {
454 socket_close($this->C);