renaming plugins
[irbot.git] / sources / main-class.inc.php
bloba64f93c60eaeb79e2575cca20128be0dc118e9ec
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/>.
19 * $Id: main-class.inc.php 28 2008-01-19 17:17:01Z xrogaan $
22 final class bot {
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;
34 // Variables privèes
35 static public $instance = FALSE;
36 static public $plugins = false;
38 protected $C;
40 static $botVersion = '1.0';
41 static $server;
42 static $port;
43 static $channel;
44 static $myBotName;
45 static $ip;
46 static $domain;
47 static $connection_password;
49 public $irc;
50 public $tick;
51 // public $plugins;
52 public $formater;
53 public $base;
54 public $msg;
55 public $connected = false;
57 private $core_commands;
58 private $users_list;
59 private $msg_info;
60 private $lastData;
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() {
79 if (!$plugins)
80 $plugins = new plugins($this);
81 return $plugins;
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();
96 try {
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);
107 if ($x===false) {
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);
124 while (1) {
125 $this->msg = $this->get();
127 if (is_array($this->msg)) {
128 foreach ($this->msg as $msg) {
129 if (!empty($msg)) {
130 $event = $this->event()->setIncoming($msg);
132 $action = $event->getAction();
134 switch ($action) {
135 case Event::ACT_DISCONNECT:
136 $this->disconnect('ERROR: Closing Link');
137 sleep(3);
138 throw new Exception('Closing Link.',1);
139 break;
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";
144 break;
145 case Event::ACT_KICK:
146 sleep(1);
147 $this->joinChannel(bot::$channel);
148 break;
151 $this->msg_info = $msg_info = $event->getData();
153 switch ($msg_info['type']) {
155 case Plugins_Command_Abstract::EVENT_PRIVMSG:
157 // ctcp
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']);
164 } else {
165 $cmd = $msg_info['message'];
168 // we don't need this character
169 //$cmd = str_replace(chr(001), "", $msg_info['message']);
170 $cmd = trim($cmd);
172 switch ($cmd) {
173 case self::CTCP_CLIENTINFO:
174 self::notice($msg_info['from'],$this->formater->ctcp('PING VERSION TIME USERINFO CLIENTINFO'));
175 break;
177 case self::CTCP_VERSION:
178 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot version '.bot::$botVersion.' - PHP '.phpversion().' -- on http://irstat.org'));
179 break;
181 case self::CTCP_USERINFO:
182 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot'));
183 break;
185 case self::CTCP_TIME:
186 self::notice($msg_info['from'],$this->formater->ctcp(date('Y-m-d H:i:s')));
187 break;
189 case self::CTCP_PING:
190 self::notice($msg_info['from'],$this->formater->ctcp("PING ".$query));
191 break;
192 default:
193 self::notice($msg_info['from'],$this->formater->ctcp("UNKNOWN CTCP REQUEST : '$cmd'"));
194 break;
196 } else {
197 $msg_info['message'] = trim($msg_info['message']);
198 if ($msg_info['to'] == bot::$myBotName) {
199 // auth proccess
200 // TODO : create a better login process
201 if (preg_match('`^connect ([^ ]+) ([^ ]+)`',$msg_info['message'],$m)) {
202 if ($m[1] == 'admin' && $m[2] == 'mypass') {
203 $this->auth = true;
204 self::notice($msg_info['from'],'Vous êtes bien authentifié comme administrateur.');
205 } else {
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":'';
209 continue;
213 break;
214 case 'NOTICE':
215 break;
217 self::$plugins->set_event($event);
221 } else {
222 $this->irc->parse_get($this->msg);
223 $this->work_on_listen();
226 } catch (myRuntimeException $e) {
227 $x = $e->getMessage();
228 echo $x;
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);
234 /*} else {
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) {
239 throw $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']) {
252 case 'PRIVMSG':
254 // ctcp
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']);
261 } else {
262 $cmd = $msg_info['message'];
265 // we don't need this character
266 //$cmd = str_replace(chr(001), "", $msg_info['message']);
267 $cmd = trim($cmd);
269 switch ($cmd) {
270 case 'CLIENTINFO':
271 self::notice($msg_info['from'],$this->formater->ctcp('PING VERSION TIME USERINFO CLIENTINFO'));
272 break;
274 case 'VERSION':
275 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot version '.bot::$botVersion.' - PHP '.phpversion().' -- on http://irstat.org'));
276 break;
278 case 'USERINFO':
279 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot'));
280 break;
282 case 'TIME':
283 self::notice($msg_info['from'],$this->formater->ctcp(date('Y-m-d H:i:s')));
284 break;
286 case 'PING':
287 self::notice($msg_info['from'],$this->formater->ctcp("PING ".$query));
288 break;
289 default:
290 self::notice($msg_info['from'],$this->formater->ctcp("UNKNOWN CTCP REQUEST : '$cmd'"));
291 break;
293 } else {
294 $msg_info['message'] = trim($msg_info['message']);
295 if ($msg_info['to'] == bot::$myBotName) {
296 // auth proccess
297 if (preg_match('`^connect ([^ ]+) ([^ ]+)`',$msg_info['message'],$m)) {
298 if ($m[1] == 'admin' && $m[2] == 'mypass') {
299 $this->auth = true;
300 self::notice($msg_info['from'],'Vous êtes bien authentifié comme administrateur.');
301 } else {
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":'';
305 continue;
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));
318 } else {
319 $this->privmsg($msg_info['from'],"This command do not exist. Try !{$query[0]} for the list of commands avaiable with this plugin.");
321 } else {
322 // no method : need a list of commands
323 $plugin = $message;
325 $commands = $this->plugins->list_command($plugin);
326 foreach ($commands as $command_info) {
327 if ($command_info['type'] == 'public') {
328 $get_plugin_method = '!'.$plugin;
329 } else {
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);
337 } else {
338 //var_dump($this->plugins->commands);
343 break;
344 case 'NOTICE':
345 break;
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) {
356 switch ($new) {
357 case self::$myBotName:
358 echo "New nick : [ERR] no changes :". self::$myBotName . ' == ' . $new ."\n";
359 break;
360 case false:
361 self::$myBotName .= '_';
362 self::nick_change();
363 break;
364 default:
365 self::$myBotName = $new;
366 self::nick_change();
367 break;
372 * Envoie une notice a un salon / utilisateur
374 * @param string $to
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
384 * @param string $to
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);
398 usleep($interval);
403 public function put($data) {
404 if (!is_resource($this->C)) {
405 throw new Exception('Connection lost...',1);
406 return;
409 echo debug() ? 'bot::put() -> ' . $data . "\n" : '';
411 $ok = socket_write($this->C, $data ."\n");
412 if ($ok) {
413 return true;
414 } else {
415 return false;
418 // fputs($this->C, $command . "\n");
419 return true;
422 public function get() {
424 $buffer = '';
425 socket_set_nonblock($this->C);
427 while (1) {
428 //echo "TOC - ",time(),"\n";
429 $this->tick->doAllTicks();
430 $buf = @socket_read($this->C, 4096);
432 if (empty($buf)) {
433 $this->CheckTimeout();
434 sleep(1);
435 continue;
438 $this->lastData = mktime();
440 if (!strpos($buf, "\n")) { //Si ne contient aucun retour, on bufferise
441 $buffer = $buffer.$buf;
442 $data = ""; //rien è envoyer
443 } else {
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
450 } else {
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
461 if ($data != '') {
462 $data = split("\n", $data);
463 return $data;
466 continue;
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");
483 usleep(500000);
487 public function restart() {
488 if ($this->auth) {
489 echo 'Restart...'."\n";
490 $this->disconnect();
491 sleep(5);
492 throw new Exception('Restart...',1);
493 } else {
494 $this->privmsg($this->msg_info['from'],"Vous n'etes pas authentifie.");
498 public function quit() {
499 if ($this->auth) {
500 $this->disconnect();
501 throw new Exception('Quit from',3);
502 } else {
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";
516 } else {
517 echo "\t\t\tError.\nConnection already breack down. ".'bot::disconnect'."\n";
519 return true;
522 private function nick_change() {
523 echo "New nick : ".self::$myBotName."\n";
524 if ($this->put('NICK :'.self::$myBotName)) {
525 return true;
527 return false;
530 private function CheckTimeout() {
531 $now = mktime();
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);