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