/quit now handled properly.
[KiwiIRC.git] / class_irc.php
1 <?php
2
3 class SocketStream {
4 var $stream;
5 function __construct($host = null, $port = null, $blocking = true) {
6 if(!$host && !$port)
7 return;
8 $scheme = 'tcp';
9 if($host{0}=='!')
10 {
11 $scheme = 'ssl';
12 $port = 6697;
13 $host = substr($host, 1);
14 }
15 $this->stream = @stream_socket_client("{$scheme}://$host:$port", $errno, $errstr, 4);
16 if(!$this->stream)
17 {
18 debug("Error creating socket: $host $errstr\n");
19 return;
20 }
21 // socket_set_timeout($this->stream, 30);
22 stream_set_blocking($this->stream, $blocking);
23 }
24 function __destruct()
25 {
26 if($this->stream)
27 {
28 fclose($this->stream);
29 $this->stream = null;
30 }
31 }
32 function SetBlocking($blocking = true)
33 {
34 if(!$this->stream)
35 return false;
36 stream_set_blocking($this->stream, $blocking);
37 }
38 function SetTimeout($timeout, $ms=0)
39 {
40 if(!$this->stream)
41 return false;
42 stream_set_timeout($this->stream, $timeout, $ms);
43 }
44 function Eof()
45 {
46 if(!$this->stream)
47 return true;
48 $x = stream_get_meta_data($this->stream);
49 if($x['eof'] && $x['unread_bytes']==0 && feof($this->stream))
50 return true;
51 return false;
52 }
53 function Read($length = 512, $type = PHP_BINARY_READ)
54 {
55 if(!$this->stream) return false;
56 if($type == PHP_NORMAL_READ){
57 $ret = fgets($this->stream, $length);
58 } elseif($type == PHP_BINARY_READ) {
59 $ret = fread($this->stream, $length);
60 }
61 //file_put_contents('/home/wok/public_html/kiwi/rawlog', '> '.$ret, FILE_APPEND);
62 return $ret;
63 }
64 function Write($data)
65 {
66 global $totalup;
67 if(!$this->stream) return false;
68
69 //file_put_contents('/home/wok/public_html/kiwi/rawlog', '< '.$data, FILE_APPEND);
70 $x = @fwrite($this->stream, $data);
71 return $x;
72 }
73 function GetInfo()
74 {
75 if(!$this->stream)
76 return false;
77 return array('local'=>stream_socket_get_name($this->stream,false),'remote'=>stream_socket_get_name($this->stream, true));
78 }
79 }
80 class SocketStreamSSL extends SocketStream
81 {
82 function __construct($host = null, $port = null, $blocking = true)
83 {
84 if(!$host && !$port)
85 return;
86 $this->stream = @stream_socket_client("ssl://$host:$port", $errno, $errstr, 4);
87 if(!$this->stream)
88 {
89 debug("Error creating socket: $host $errstr\n");
90 return;
91 }
92 // socket_set_timeout($this->stream, 30);
93 stream_set_blocking($this->stream, $blocking);
94 }
95 }
96 class IRC
97 {
98 var $stream;
99 var $url, $urlinfo;
100 var $nick;
101 protected $msgqueue = array();
102 protected $dccs = array();
103 private $reconnect=0;
104 private $nextping=0;
105 public $modes;
106 public $chanlist;
107 public $connected;
108 function __construct($url, $kiwi=null, $opts=array()){
109 $this->url = $url;
110 $urlinfo = parse_url($url);
111 $this->urlinfo = $urlinfo;
112 if(!ereg("^ircs?$", $urlinfo['scheme']))
113 return false;
114 $ssl = ($urlinfo['scheme']=='ircs');
115 $host = $urlinfo['host'];
116 $port = isset($urlinfo['port'])?$urlinfo['port']:6667;
117 $this->nick = $nick = isset($urlinfo['user'])?$urlinfo['user']:'kiwi_user|'.(string)rand(0,9999);
118 //$ident = isset($urlinfo['pass'])?$urlinfo['pass']:$nick;
119 $ident = (isset($opts['ident'])) ? "{$opts['ident']}_kiwi" : "{$nick}_kiwi";
120 $chans = false;
121 if(isset($urlinfo['path'])){
122 $path = trim($urlinfo['path'], "/");
123 $chans = explode(",", $path); //"#".str_replace(array(":", ","), array(" ", ",#"), $path);
124 }
125 $this->connected = false;
126 if($ssl)
127 $this->stream = new SocketStreamSSL($host, $port, false);
128 else
129 $this->stream = new SocketStream($host, $port, false);
130 if(!$this->stream || !$this->stream->stream){
131 return false;
132 }
133
134 // Send the login data
135 if($kiwi != null) $this->stream->Write($kiwi."\r\n");
136 if(isset($urlinfo['fragment'])) $this->stream->Write("PASS {$urlinfo['fragment']}\r\n");
137
138 $this->stream->Write("NICK $nick\r\n");
139 $this->stream->Write("USER $ident 0 0 :$nick\r\n");
140 //if($chans){
141 // foreach($chans as $chan)
142 // $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n");
143 //} else {
144 $chans = array();
145 //}
146 $this->chans = $chans;
147 $this->connected = true;
148 return true;
149 }
150 function __destruct(){
151 if($this->stream){
152 $this->stream->Write("QUIT :kiwi\r\n");
153 }
154 }
155 function Process(){
156 if((!$this->stream || !$this->stream->stream)){
157 if(time() > $this->reconnect) {
158 $this->__construct($this->url);
159 $this->reconnect = time() + 10;
160 }
161 usleep(50000);
162 return;
163 }
164 if($this->stream->Eof()){
165 $this->stream = null;
166 return;
167 }
168 $r=array($this->stream->stream);
169 $w = $e = array();
170 if(($num=@stream_select($r, $w,$e,0,50000))===false){
171
172 } elseif($num>0){
173 $data=$this->stream->Read(512, PHP_NORMAL_READ);
174 //deb($data);
175 if(preg_match("/^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@([a-z0-9\.\-:]+)) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i", $data, $regs))
176 {
177 unset($prefix, $nick, $ident, $hostname, $command, $params, $trailing, $flarp);
178 $prefix = $regs[1];
179 $nick = $regs[2];
180 $ident = $regs[3];
181 $hostname = $regs[4];
182 $command = $regs[5];
183 $params = isset($regs[6]) ? $regs[6] : '';
184 $trailing = trim(isset($regs[7]) ? $regs[7] : '');
185 $flarp = compact('prefix', 'nick', 'ident', 'hostname', 'command', 'params', 'trailing');
186 $this->ProcessMessage($flarp);
187 }
188 }
189 else
190 {
191 if(sizeof($this->msgqueue) && (microtime(true)>($this->lastqueuewrite+0.1)) && ($msg=array_shift($this->msgqueue)))
192 { $this->lastqueuewrite = microtime(true); $this->stream->Write($msg); }
193 foreach($this->dccs as $k=>&$dcc)
194 {
195 if(!$dcc->Process())
196 {
197 if($dcc->errorinfo)
198 $this->SendNotice($dcc->errorinfo['nick'], $dcc->errorinfo['text']);
199 unset($this->dccs[$k]);
200 }
201 }
202 if(($now=microtime(1))>$this->nextping)
203 {
204 $this->stream->Write("PONG x\r\n");
205 $this->nextping = $now + 150;
206 }
207 }
208 }
209 function ProcessMessage($message)
210 {
211 //echo $message['command']."\n";
212 switch($message['command'])
213 {
214 case "PING":
215 $this->stream->Write("PONG {$message['trailing']}\r\n");
216 break;
217 case "PRIVMSG":
218 $this->ProcessPrivMsg($message);
219 break;
220 case "001":
221 if($this->chans)
222 foreach($this->chans as $chan)
223 $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n");
224 break;
225 case "443":
226 $newnick = 'kiwi_user|'.rand(0,99999);
227 $this->SendCmd("NICK ".$newnick);
228 $this->nick = $newnick;
229 break;
230 case "MODE":
231 $this->OnMODE($message);
232 break;
233 case "353":
234 $this->On353($message);
235 break;
236 case "QUIT":
237 $chan = $message['params'];
238 foreach($this->chanlist as &$chan){
239 if(isset($chan['userlist'][$message['nick']]))
240 unset($chan['userlist'][$message['nick']]);
241 }
242 break;
243 case "PART":
244 $chan = $message['params'];
245 //debug("Parting {$message['nick']} from $chan");
246 if(isset($this->chanlist[ltrim($chan, "#")])){
247 unset($this->chanlist[ltrim($chan, "#")]['userlist'][$message['nick']]);
248 }
249 break;
250 case "JOIN":
251 $chan = $message['trailing'];
252 $nick = array($message['nick'] => '');
253 $nicklist = is_array($this->chanlist[ltrim($chan, "#")]['userlist']) ? $this->chanlist[ltrim($chan, "#")]['userlist'] :
254 array();
255
256 $this->chanlist[ltrim($chan, "#")]['userlist'] = array_merge($nicklist, $nick);
257 break;
258 case "NICK":
259 if($message['nick'] == $this->nick){
260 $this->nick = $message['trailing'];
261 }
262 foreach($this->chanlist as $chan_name => $chan){
263 foreach($chan['userlist'] as $nick => $status){
264 if($nick == $message['nick']){
265 $this->chanlist[$chan_name]['userlist'][$message['trailing']] = $this->chanlist[$chan_name]['userlist'][$message['nick']];
266 unset($this->chanlist[$chan_name]['userlist'][$message['nick']]);
267 break;
268 }
269 }
270 }
271 break;
272 }
273 }
274 function OnMODE($message){
275 if($message['params'] == $this->nick) {
276 $modes = $message['trailing'];
277 $size = strlen($modes);
278 $add = 1;
279 for($i=0;$i<$size;$i++){
280 if($modes{$i} == '+'){
281 $add = 1;
282 continue;
283 } elseif($modes{$i} == '-'){
284 $add = 0;
285 continue;
286 }
287 if($add && strpos($this->modes, $modes{$i}) === false)
288 $this->modes.=$modes{$i};
289 if(!$add && strpos($this->modes, $modes{$i}))
290 $this->modes = str_replace($modes{$i}, "", $this->modes);
291 }
292 }
293 $params = $message['params'];
294 $chan = trim(strtok($params, ' '));
295 if(in_array(trim($chan,'#'), $this->chans)){
296 $modes = strtok(' ');
297 $therest = explode(" ", trim(strtok('')));
298 $size = strlen($modes);
299 $add = 1;
300 $offset = 0;
301 for($i=0;$i<$size;$i++){
302 if($modes{$i} == '+'){
303 $add = 1;
304 $offset--;
305 continue;
306 } elseif($modes{$i} == '-'){
307 $add = 0;
308 $offset--;
309 continue;
310 }
311 if($modes[$i]=='v')
312 {
313 $user = $therest[$i+$offset];
314 if($add)
315 {
316 if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '+')===false)
317 $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '+';
318 }
319 else
320 $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('+','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);
321 continue;
322 }
323 if($modes[$i]=='o')
324 {
325 $user = $therest[$i+$offset];
326 if($add)
327 {
328 if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '@')===false)
329 $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '@';
330 }
331 else
332 $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('@','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);
333 continue;
334 }
335 if($modes[$i]=='h')
336 {
337 $user = $therest[$i+$offset];
338 if($add)
339 {
340 if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '%')===false)
341 $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '%';
342 }
343 else
344 $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('%','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);
345 continue;
346 }
347 if($modes[$i]=='q')
348 {
349 $user = $therest[$i+$offset];
350 if($add)
351 {
352 if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '~')===false)
353 $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '~';
354 }
355 else
356 $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('~','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);
357 continue;
358 }
359 }
360 }
361
362 }
363 function On353($message){
364 // Response to NAMES command
365 list($nick,,$chan) = explode(" ", $message['params']);
366 $nicks = explode(" ", $message['trailing']);
367 $prefixes = array('~', '&', '@','%','+');
368 $nicklist = array();
369 foreach($nicks as $nick){
370 if(in_array($nick{0}, $prefixes)){
371 $prefix = $nick{0};
372 $nick = substr($nick,1);
373 }
374 else
375 $prefix = '';
376 $nicklist[$nick] = $prefix;
377 }
378 if(sizeof($nicklist)){
379 // If we havn't got a list of nicks for this channel yet, create it
380 if(!isset($this->chanlist[ltrim($chan, "#")]['userlist'])){
381 $this->chanlist[ltrim($chan, "#")]['userlist'] = array();
382 }
383 $this->chanlist[ltrim($chan, "#")]['userlist'] = array_merge($this->chanlist[ltrim($chan, "#")]['userlist'], $nicklist);
384 }
385 }
386 function ProcessPrivMsg($message){
387 //$cmd = strtok($message['trailing'], ' ');
388 //switch($cmd){
389 //}
390 }
391 function SendMessage($dest, $text){
392 if(!$this->stream)
393 return false;
394 $this->stream->Write("PRIVMSG $dest :$text\r\n");
395 }
396 function Join($chan){
397 $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n");
398 }
399 function SendNotice($dest, $text)
400 {
401 $this->stream->Write("NOTICE $dest :$text\r\n");
402 }
403 function QueueMessage($dest, $text)
404 {
405 $this->msgqueue[] = "PRIVMSG $dest :$text\r\n";
406 }
407 function QueueNotice($dest, $text)
408 {
409 $this->msgqueue[] = "NOTICE $dest :$text\r\n";
410 }
411 function GetMainChan()
412 {
413 return '#'.$this->chans[0];
414 }
415 }