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/>.
19 * $Id: main-class.inc.php 28 2008-01-19 17:17:01Z xrogaan $
24 const CTCP_CLIENTINFO
= 'CLIENTINFO';
25 const CTCP_VERSION
= 'VERSION';
26 const CTCP_USERINFO
= 'USERINFO';
27 const CTCP_TIME
= 'TIME';
28 const CTCP_PING
= 'PING';
31 // Timeout avant une reconnexion
32 public $socketTimeout = 180;
35 static public $instance = FALSE;
36 static public $plugins = false;
40 static $botVersion = '1.0';
47 static $connection_password;
55 public $connected = false;
57 private $core_commands;
62 public static function GetInstance() {
63 if (!self
::$instance) {
64 self
::$instance = new bot();
65 //self::$instance->plugins = new plugins();
66 self
::$instance->irc
= new irc
;
67 self
::$instance->tick
= tick
::GetInstance();
69 return self
::$instance;
72 private function __construct() {
73 // load config and other things echo "oups main\n";die;
74 $this->formater
= text_format
::GetInstance();
75 $this->connected
= false;
78 static private function plugins() {
80 $plugins = new plugins($this);
84 private function load_core_plugin() {
85 // core plugin is an exeption in do_command
86 $this->plugins()->add_command('core','shownick',0,'Show the current nick used (debug)','mixed');
87 $this->plugins()->add_command('core','nick',1,'Change the current nick','mixed');
88 $this->plugins()->add_command('core','quit',0,'Disconnect and stop the process','mixed');
89 $this->plugins()->add_command('core','restart',0,'Disconnect and restart the process','mixed');
90 $this->plugins()->add_command('core','help',0,'Hmm, je dois recoder cette fonction','mixed');
93 public function launch() {
94 self
::load_core_plugin();
95 self
::plugins()->Init();
97 //$this->C = @fsockopen(self::$server, self::$port, $errno, $errstr, 10);
98 $this->lastData
= mktime();
99 $this->C
= socket_create(AF_INET
, SOCK_STREAM
, SOL_TCP
);
100 if ($this->C
=== false) {
101 throw new Exception('Impossible de créer le socket IRC !',0);
104 if (self
::$ip != "") { socket_bind($this->C
, self
::$ip); }
106 $x = socket_connect($this->C
, self
::$server, self
::$port);
108 throw new Exception('Impossible de se connecter au server IRC ! ('.socket_last_error().')' ,0);
111 $this->connected
= true;
113 if (self
::$connection_password !== false) {
114 $this->put('PASS '.self
::$connection_password);
116 // TODO : be sure for the validity of the connection password (what chain server return if fail ?)
118 $this->put('USER '.self
::$myBotName.' '.self
::$myBotName.'@'.self
::$ip.' '.self
::$server.' :'.self
::$myBotName);
119 $this->put('NICK '.self
::$myBotName);
121 /*$this->tick->setTick('all5sec',5);
122 $this->tick->addJob('all5sec','hello chan','privmsg',array(self::$channel,"I'm a tick. I show this msg all 5sec."),0,$this);
125 $this->msg
= $this->get();
127 if (is_array($this->msg
)) {
128 foreach ($this->msg
as $msg) {
130 $event = $this->event()->setIncoming($msg);
132 $action = $event->getAction();
135 case Event
::ACT_DISCONNECT
:
136 $this->disconnect('ERROR: Closing Link');
138 throw new Exception('Closing Link.',1);
140 case Event
::ACT_PING
:
141 $pong = split(':',$msg);
142 $this->put('PONG '.$pong[1]);
143 echo "PING :{$pong[1]}\nPONG {$pong[1]}\n\n";
145 case Event
::ACT_KICK
:
147 $this->joinChannel(bot
::$channel);
151 $this->msg_info
= $msg_info = $event->getData();
153 switch ($msg_info['type']) {
155 case Plugins_Command_Abstract
::EVENT_PRIVMSG
:
158 if ($msg_info['message'][0] == chr(001)) {
160 // we don't need this character
161 $msg_info['message'] = str_replace(chr(001), "", $msg_info['message']);
162 if (strstr($msg_info['message'],' ') === true) {
163 list($cmd,$query) = explode(" ",$msg_info['message']);
165 $cmd = $msg_info['message'];
168 // we don't need this character
169 //$cmd = str_replace(chr(001), "", $msg_info['message']);
173 case self
::CTCP_CLIENTINFO
:
174 self
::notice($msg_info['from'],$this->formater
->ctcp('PING VERSION TIME USERINFO CLIENTINFO'));
177 case self
::CTCP_VERSION
:
178 self
::notice($msg_info['from'],$this->formater
->ctcp('RPGBot version '.bot
::$botVersion.' - PHP '.phpversion().' -- on http://irstat.org'));
181 case self
::CTCP_USERINFO
:
182 self
::notice($msg_info['from'],$this->formater
->ctcp('RPGBot'));
185 case self
::CTCP_TIME
:
186 self
::notice($msg_info['from'],$this->formater
->ctcp(date('Y-m-d H:i:s')));
189 case self
::CTCP_PING
:
190 self
::notice($msg_info['from'],$this->formater
->ctcp("PING ".$query));
193 self
::notice($msg_info['from'],$this->formater
->ctcp("UNKNOWN CTCP REQUEST : '$cmd'"));
197 $msg_info['message'] = trim($msg_info['message']);
198 if ($msg_info['to'] == bot
::$myBotName) {
200 // TODO : create a better login process
201 if (preg_match('`^connect ([^ ]+) ([^ ]+)`',$msg_info['message'],$m)) {
202 if ($m[1] == 'admin' && $m[2] == 'mypass') {
204 self
::notice($msg_info['from'],'Vous êtes bien authentifié comme administrateur.');
206 self
::notice($msg_info['from'],'Erreur dans votre login et / ou mot de passe.');
207 echo debug() ?
'l: '.$m[1].' p: '.$m[2]."\n":'';
217 self
::$plugins->set_event($event);
222 $this->irc
->parse_get($this->msg
);
223 $this->work_on_listen();
226 } catch (myRuntimeException
$e) {
227 $x = $e->getMessage();
229 $x.= backtrace($e->getTrace());
230 file_put_contents('errorlog',$x);
231 //if ($e->_level < 2 || ($e->_level >= 256 && $e->_level < 1024)) {
232 echo 'Error level : '.$e->_level
."\n\n";
233 throw new Exception('Error occured',0);
235 echo 'Error level : '.$e->_level."\n\n\n\n";
236 throw new Exception("Error occured. Please see errorlog for details.",1);
238 } catch (Exception
$e) {
243 protected function event() {
244 return new Event($this);
247 private function work_on_listen () {
248 $this->msg_info
= $msg_info = $this->irc
->get_msg_info();
250 switch ($msg_info['type']) {
255 if ($msg_info['message'][0] == chr(001)) {
257 // we don't need this character
258 $msg_info['message'] = str_replace(chr(001), "", $msg_info['message']);
259 if (strstr($msg_info['message'],' ')===true) {
260 list($cmd,$query) = explode(" ",$msg_info['message']);
262 $cmd = $msg_info['message'];
265 // we don't need this character
266 //$cmd = str_replace(chr(001), "", $msg_info['message']);
271 self
::notice($msg_info['from'],$this->formater
->ctcp('PING VERSION TIME USERINFO CLIENTINFO'));
275 self
::notice($msg_info['from'],$this->formater
->ctcp('RPGBot version '.bot
::$botVersion.' - PHP '.phpversion().' -- on http://irstat.org'));
279 self
::notice($msg_info['from'],$this->formater
->ctcp('RPGBot'));
283 self
::notice($msg_info['from'],$this->formater
->ctcp(date('Y-m-d H:i:s')));
287 self
::notice($msg_info['from'],$this->formater
->ctcp("PING ".$query));
290 self
::notice($msg_info['from'],$this->formater
->ctcp("UNKNOWN CTCP REQUEST : '$cmd'"));
294 $msg_info['message'] = trim($msg_info['message']);
295 if ($msg_info['to'] == bot
::$myBotName) {
297 if (preg_match('`^connect ([^ ]+) ([^ ]+)`',$msg_info['message'],$m)) {
298 if ($m[1] == 'admin' && $m[2] == 'mypass') {
300 self
::notice($msg_info['from'],'Vous êtes bien authentifié comme administrateur.');
302 self
::notice($msg_info['from'],'Erreur dans votre login et / ou mot de passe.');
303 echo debug() ?
'l: '.$m[1].' p: '.$m[2]."\n":'';
309 if ($msg_info['message'][0] == '!') {
310 $message = substr($msg_info['message'],1);
311 $query = explode(' ',$message);
312 $query_count = count($query);
314 if (isset($this->plugins->commands[$query[0]])) {
315 if ($query_count > 1) {
316 if (isset($this->plugins->commands[$query[0]][$query[1]])) {
317 call_user_func_array(array($this->plugins,'do_command'),array_merge(array('msg_info'=>$msg_info),$query));
319 $this->privmsg($msg_info['from'],"This command do not exist. Try !{$query[0]} for the list of commands avaiable with this plugin.");
322 // no method : need a list of commands
325 $commands = $this->plugins->list_command($plugin);
326 foreach ($commands as $command_info) {
327 if ($command_info['type'] == 'public') {
328 $get_plugin_method = '!'.$plugin;
330 $get_plugin_method = '/msg ' . bot::$myBotName . ' !' . $plugin;
333 $msg = array_merge(array($this->formater->bold('Command :')." $get_plugin_method {$command_info['method']}".(($command_info['accepted_args']>0)?' [some args...]':'')),text_format::paragraphe($command_info['help']));
334 $this->mprivmsg($msg_info['from'],$msg);
338 //var_dump($this->plugins->commands);
347 self
::$plugins->set_event($msg_info);
350 public function joinChannel($channel) {
351 $this->put('JOIN '.$channel);
352 echo "Join channel $channel ...\n";
355 public function newNick($new=false) {
357 case self
::$myBotName:
358 echo "New nick : [ERR] no changes :". self
::$myBotName . ' == ' . $new ."\n";
361 self
::$myBotName .= '_';
365 self
::$myBotName = $new;
372 * Envoie une notice a un salon / utilisateur
375 * @param string $message
377 public function notice ($to,$message) {
378 self
::put('NOTICE '.$to.' :'.$message."\n");
382 * Envoie un message (PRIVMSG) a un salon / utilisateur
385 * @param string $message
387 public function privmsg ($to,$message) {
388 $search = array('#name');
389 $replace = array(self
::$myBotName);
390 $message = str_replace($search,$replace,$message);
391 self
::put('PRIVMSG '.$to.' :'.$message."\n");
394 public function mprivmsg($to,$messages,$interval=650000) {
395 if (is_array($messages)) {
396 foreach ($messages as $msg) {
397 $this->privmsg($to,$msg);
403 public function put($data) {
404 if (!is_resource($this->C
)) {
405 throw new Exception('Connection lost...',1);
409 echo debug() ?
'bot::put() -> ' . $data . "\n" : '';
411 $ok = socket_write($this->C
, $data ."\n");
418 // fputs($this->C, $command . "\n");
422 public function get() {
425 socket_set_nonblock($this->C
);
428 //echo "TOC - ",time(),"\n";
429 $this->tick
->doAllTicks();
430 $buf = @socket_read
($this->C
, 4096);
433 $this->CheckTimeout();
438 $this->lastData
= mktime();
440 if (!strpos($buf, "\n")) { //Si ne contient aucun retour, on bufferise
441 $buffer = $buffer.$buf;
442 $data = ""; //rien è envoyer
444 //Si contient au moins un retour,
445 //on vèrifie que le dernier caractère en est un
446 if (substr($buf, -1, 1) == "\n") {
447 //alors on additionne ces donnèes au buffer
448 $data = $buffer.$buf;
449 $buffer = ""; //on vide le buffer
451 //si le dernier caractère n'est pas un retour è la
452 //ligne, alors on envoit tout jusqu'au dernier retour
453 //puis on bufferise le reste
454 $buffer = $buffer.substr($buf, strrchr($buf, "\n"));
455 $data = substr($buf, 0, strrchr($buf, "\n"));
456 $data = $buffer.$data;
457 $buffer = ""; //on vide le buffer
462 $data = split("\n", $data);
471 public function shownick() {
472 $this->privmsg(bot
::$channel,bot
::$myBotName."\n");
475 public function nick($newNick) {
476 $this->newNick($newNick);
479 public function help() {
480 $this->privmsg($this->msg_info
['from'],"List of plugins :");
481 foreach($this->plugins
->plugin_list
as $pluginName => $object) {
482 $this->privmsg($this->msg_info
['from'],"!$pluginName");
487 public function restart() {
489 echo 'Restart...'."\n";
492 throw new Exception('Restart...',1);
494 $this->privmsg($this->msg_info
['from'],"Vous n'etes pas authentifie.");
498 public function quit() {
501 throw new Exception('Quit from',3);
503 $this->privmsg($this->msg_info
['from'],"Vous n'etes pas authentifie.");
507 public function disconnect($msg='EOL ;') {
508 $this->plugins
->unload_plugin('_all');
509 echo "Closing Link...";
510 if (is_resource($this->C
)) {
511 if ($this->put('QUIT :'.$msg)) {
512 $this->connected
= false;
513 socket_close($this->C
);
515 echo "\t\t\tdone.\n";
517 echo "\t\t\tError.\nConnection already breack down. ".'bot::disconnect'."\n";
522 private function nick_change() {
523 echo "New nick : ".self
::$myBotName."\n";
524 if ($this->put('NICK :'.self
::$myBotName)) {
530 private function CheckTimeout() {
532 if ($this->lastData+
$this->socketTimeout
< mktime()) {
533 throw new Exception('Connection lost (Timeout).',1);
535 $lag = $now-$this->lastData
;
536 if ( $lag > 20 && ($lag %
10) == 0) {
537 echo "Lag: ".$lag." seconds\n";
541 private function load_user($file='users.db') {
542 echo "Loading users ...";
543 $ulist = explode("\n",file_get_contents($file));
544 foreach ($ulist as $user) {
545 list($uname,$upass,$uright) = explode(':',$user);
546 $this->users_list
[$uname] = array($upass,$uright);
548 echo "\t\t\tdone.\n";
551 function __destruct() {
552 if (is_resource($this->C
)) {
553 if ($this->put('QUIT :Error occured')) {
554 socket_close($this->C
);