Added tag 1.0beta1 for changeset 48ee1351a513
[irbot.git] / sources / main-class.inc.php
blob7c42e3bf230a3c7a840ce3ac8fa50121242b0f63
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 // Timeout avant une reconnexion
25 public $socketTimeout = 180;
27 // Variables privèes
28 static public $instance = FALSE;
30 protected $C;
32 static $botVersion = '1.0';
33 static $server;
34 static $port;
35 static $channel;
36 static $myBotName;
37 static $ip;
38 static $domain;
39 static $connection_password;
41 public $irc;
42 public $tick;
43 public $plugins;
44 public $formater;
45 public $base;
46 public $msg;
47 public $connected = false;
49 private $core_commands;
50 private $users_list;
51 private $msg_info;
52 private $lastData;
54 public static function GetInstance() {
55 if (!self::$instance) {
56 self::$instance = new bot();
57 self::$instance->plugins = new plugins();
58 self::$instance->irc = new irc;
59 self::$instance->tick = tick::GetInstance();
61 return self::$instance;
64 private function __construct() {
65 // load config and other things echo "oups main\n";die;
66 $this->formater = text_format::GetInstance();
67 $this->connected = false;
70 private function load_core_plugin() {
71 // core plugin is an exeption in do_command
72 $this->plugins->add_command('core','shownick',0,'Show the current nick used (debug)','mixed');
73 $this->plugins->add_command('core','nick',1,'Change the current nick','mixed');
74 $this->plugins->add_command('core','quit',0,'Disconnect and stop the process','mixed');
75 $this->plugins->add_command('core','restart',0,'Disconnect and restart the process','mixed');
76 $this->plugins->add_command('core','help',0,'Hmm, je dois recoder cette fonction','mixed');
79 public function launch() {
80 self::load_core_plugin();
81 try {
82 //$this->C = @fsockopen(self::$server, self::$port, $errno, $errstr, 10);
83 $this->lastData = mktime();
84 $this->C = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
85 if ($this->C === false) {
86 throw new Exception('Impossible de créer le socket IRC !',0);
89 if (self::$ip != "") { socket_bind($this->C, self::$ip); }
91 $x = socket_connect($this->C, self::$server, self::$port);
92 if ($x===false) {
93 throw new Exception('Impossible de se connecter au server IRC ! ('.socket_last_error().')' ,0);
96 $this->connected = true;
98 if (self::$connection_password !== false) {
99 $this->put('PASS '.self::$connection_password);
101 // TODO : be sure for the validity of the connection password (what chain server return if fail ?)
103 $this->put('USER '.self::$myBotName.' '.self::$myBotName.'@'.self::$ip.' '.self::$server.' :'.self::$myBotName);
104 $this->put('NICK '.self::$myBotName);
106 /*$this->tick->setTick('all5sec',5);
107 $this->tick->addJob('all5sec','hello chan','privmsg',array(self::$channel,"I'm a tick. I show this msg all 5sec."),0,$this);
109 while (1) {
110 $this->msg = $this->get();
111 if (is_array($this->msg)) {
112 foreach ($this->msg as $msg) {
113 if (!empty($msg)) {
114 $this->irc->parse_get($msg);
115 $this->work_on_listen();
118 } else {
119 $this->irc->parse_get($this->msg);
120 $this->work_on_listen();
123 } catch (myRuntimeException $e) {
124 $x = $e->getMessage();
125 echo $x;
126 $x.= backtrace($e->getTrace());
127 file_put_contents('errorlog',$x);
128 //if ($e->_level < 2 || ($e->_level >= 256 && $e->_level < 1024)) {
129 echo 'Error level : '.$e->_level."\n\n";
130 throw new Exception('Error occured',0);
131 /*} else {
132 echo 'Error level : '.$e->_level."\n\n\n\n";
133 throw new Exception("Error occured. Please see errorlog for details.",1);
135 } catch (Exception $e) {
136 throw $e;
140 private function work_on_listen () {
141 $this->msg_info = $msg_info = $this->irc->get_msg_info();
143 switch ($msg_info['type']) {
145 case 'PRIVMSG':
147 // ctcp
148 if ($msg_info['message'][0] == chr(001)) {
150 // we don't need this character
151 $msg_info['message'] = str_replace(chr(001), "", $msg_info['message']);
152 if (strstr($msg_info['message'],' ')===true) {
153 list($cmd,$query) = explode(" ",$msg_info['message']);
154 } else {
155 $cmd = $msg_info['message'];
158 // we don't need this character
159 //$cmd = str_replace(chr(001), "", $msg_info['message']);
160 $cmd = trim($cmd);
162 switch ($cmd) {
163 case 'CLIENTINFO':
164 self::notice($msg_info['from'],$this->formater->ctcp('PING VERSION TIME USERINFO CLIENTINFO'));
165 break;
167 case 'VERSION':
168 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot version '.bot::$botVersion.' - PHP '.phpversion().' -- on http://irstat.org'));
169 break;
171 case 'USERINFO':
172 self::notice($msg_info['from'],$this->formater->ctcp('RPGBot'));
173 break;
175 case 'TIME':
176 self::notice($msg_info['from'],$this->formater->ctcp(date('Y-m-d H:i:s')));
177 break;
179 case 'PING':
180 self::notice($msg_info['from'],$this->formater->ctcp("PING ".$query));
181 break;
182 default:
183 self::notice($msg_info['from'],$this->formater->ctcp("UNKNOWN CTCP REQUEST : '$cmd'"));
184 break;
186 } else {
187 $msg_info['message'] = trim($msg_info['message']);
188 if ($msg_info['to'] == bot::$myBotName) {
189 // auth proccess
190 if (preg_match('`^connect ([^ ]+) ([^ ]+)`',$msg_info['message'],$m)) {
191 if ($m[1] == 'admin' && $m[2] == 'mypass') {
192 $this->auth = true;
193 self::notice($msg_info['from'],'Vous êtes bien authentifié comme administrateur.');
194 } else {
195 self::notice($msg_info['from'],'Erreur dans votre login et / ou mot de passe.');
196 echo debug() ? 'l: '.$m[1].' p: '.$m[2]."\n":'';
198 continue;
202 if ($msg_info['message'][0] == '!') {
203 $message = substr($msg_info['message'],1);
204 $query = explode(' ',$message);
205 $query_count = count($query);
207 if (isset($this->plugins->commands[$query[0]])) {
208 if ($query_count > 1) {
209 if (isset($this->plugins->commands[$query[0]][$query[1]])) {
210 call_user_func_array(array($this->plugins,'do_command'),array_merge(array('msg_info'=>$msg_info),$query));
211 } else {
212 $this->privmsg($msg_info['from'],"This command do not exist. Try !{$query[0]} for the list of commands avaiable with this plugin.");
214 } else {
215 // no method : need a list of commands
216 $plugin = $message;
218 $commands = $this->plugins->list_command($plugin);
219 foreach ($commands as $command_info) {
220 if ($command_info['type'] == 'public') {
221 $get_plugin_method = '!'.$plugin;
222 } else {
223 $get_plugin_method = '/msg ' . bot::$myBotName . ' !' . $plugin;
226 $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']));
227 $this->mprivmsg($msg_info['from'],$msg);
230 } else {
231 //var_dump($this->plugins->commands);
235 break;
236 case 'NOTICE':
237 break;
241 public function joinChannel($channel) {
242 $this->put('JOIN '.$channel);
243 echo "Join channel $channel ...\n";
246 public function newNick($new=false) {
247 switch ($new) {
248 case self::$myBotName:
249 echo "New nick : [ERR] no changes :". self::$myBotName . ' == ' . $new ."\n";
250 break;
251 case false:
252 self::$myBotName .= '_';
253 self::nick_change();
254 break;
255 default:
256 self::$myBotName = $new;
257 self::nick_change();
258 break;
263 * Envoie une notice a un salon / utilisateur
265 * @param string $to
266 * @param string $message
268 public function notice ($to,$message) {
269 self::put('NOTICE '.$to.' :'.$message."\n");
273 * Envoie un message (PRIVMSG) a un salon / utilisateur
275 * @param string $to
276 * @param string $message
278 public function privmsg ($to,$message) {
279 $search = array('#name');
280 $replace = array(self::$myBotName);
281 $message = str_replace($search,$replace,$message);
282 self::put('PRIVMSG '.$to.' :'.$message."\n");
285 public function mprivmsg($to,$messages,$interval=650000) {
286 if (is_array($messages)) {
287 foreach ($messages as $msg) {
288 $this->privmsg($to,$msg);
289 usleep($interval);
294 public function put($data) {
295 if (!is_resource($this->C)) {
296 throw new Exception('Connection lost...',1);
297 return;
300 echo debug() ? 'bot::put() -> ' . $data . "\n" : '';
302 $ok = socket_write($this->C, $data ."\n");
303 if ($ok) {
304 return true;
305 } else {
306 return false;
309 // fputs($this->C, $command . "\n");
310 return true;
313 public function get() {
315 $buffer = '';
316 socket_set_nonblock($this->C);
318 while (1) {
319 //echo "TOC - ",time(),"\n";
320 $this->tick->doAllTicks();
321 $buf = @socket_read($this->C, 4096);
323 if (empty($buf)) {
324 $this->CheckTimeout();
325 sleep(1);
326 continue;
329 $this->lastData = mktime();
331 if (!strpos($buf, "\n")) { //Si ne contient aucun retour, on bufferise
332 $buffer = $buffer.$buf;
333 $data = ""; //rien è envoyer
334 } else {
335 //Si contient au moins un retour,
336 //on vèrifie que le dernier caractère en est un
337 if (substr($buf, -1, 1) == "\n") {
338 //alors on additionne ces donnèes au buffer
339 $data = $buffer.$buf;
340 $buffer = ""; //on vide le buffer
341 } else {
342 //si le dernier caractère n'est pas un retour è la
343 //ligne, alors on envoit tout jusqu'au dernier retour
344 //puis on bufferise le reste
345 $buffer = $buffer.substr($buf, strrchr($buf, "\n"));
346 $data = substr($buf, 0, strrchr($buf, "\n"));
347 $data = $buffer.$data;
348 $buffer = ""; //on vide le buffer
352 if ($data != '') {
353 $data = split("\n", $data);
354 return $data;
357 continue;
362 public function shownick() {
363 $this->privmsg(bot::$channel,bot::$myBotName."\n");
366 public function nick($newNick) {
367 $this->newNick($newNick);
370 public function help() {
371 $this->privmsg($this->msg_info['from'],"List of plugins :");
372 foreach($this->plugins->plugin_list as $pluginName => $object) {
373 $this->privmsg($this->msg_info['from'],"!$pluginName");
374 usleep(500000);
378 public function restart() {
379 if ($this->auth) {
380 echo 'Restart...'."\n";
381 $this->disconnect();
382 sleep(5);
383 throw new Exception('Restart...',1);
384 } else {
385 $this->privmsg($this->msg_info['from'],"Vous n'etes pas authentifie.");
389 public function quit() {
390 if ($this->auth) {
391 $this->disconnect();
392 throw new Exception('Quit from',3);
393 } else {
394 $this->privmsg($this->msg_info['from'],"Vous n'etes pas authentifie.");
398 public function disconnect($msg='EOL ;') {
399 $this->plugins->unload_plugin('_all');
400 echo "Closing Link...";
401 if (is_resource($this->C)) {
402 if ($this->put('QUIT :'.$msg)) {
403 $this->connected = false;
404 socket_close($this->C);
406 echo "\t\t\tdone.\n";
407 } else {
408 echo "\t\t\tError.\nConnection already breack down. ".'bot::disconnect'."\n";
410 return true;
413 private function nick_change() {
414 echo "New nick : ".self::$myBotName."\n";
415 if ($this->put('NICK :'.self::$myBotName)) {
416 return true;
418 return false;
421 private function CheckTimeout() {
422 $now = mktime();
423 if ($this->lastData+$this->socketTimeout < mktime()) {
424 throw new Exception('Connection lost (Timeout).',1);
426 $lag = $now-$this->lastData;
427 if ( $lag > 20 && ($lag % 10) == 0) {
428 echo "Lag: ".$lag." seconds\n";
432 private function load_user($file='users.db') {
433 echo "Loading users ...";
434 $ulist = explode("\n",file_get_contents($file));
435 foreach ($ulist as $user) {
436 list($uname,$upass,$uright) = explode(':',$user);
437 $this->users_list[$uname] = array($upass,$uright);
439 echo "\t\t\tdone.\n";
442 function __destruct() {
443 if (is_resource($this->C)) {
444 if ($this->put('QUIT :Error occured')) {
445 socket_close($this->C);