From: Darren Date: Tue, 12 Jul 2011 21:01:42 +0000 (+0100) Subject: Alpha commit. Time to tidy up.. X-Git-Url: https://vcs.fsf.org/?a=commitdiff_plain;h=54f4a22eb16ca39ebef1a57449634f8d236fd8a7;p=KiwiIRC.git Alpha commit. Time to tidy up.. --- diff --git a/class_irc.php b/class_irc.php new file mode 100644 index 0000000..9903031 --- /dev/null +++ b/class_irc.php @@ -0,0 +1,415 @@ +stream = @stream_socket_client("{$scheme}://$host:$port", $errno, $errstr, 4); + if(!$this->stream) + { + debug("Error creating socket: $host $errstr\n"); + return; + } +// socket_set_timeout($this->stream, 30); + stream_set_blocking($this->stream, $blocking); + } + function __destruct() + { + if($this->stream) + { + fclose($this->stream); + $this->stream = null; + } + } + function SetBlocking($blocking = true) + { + if(!$this->stream) + return false; + stream_set_blocking($this->stream, $blocking); + } + function SetTimeout($timeout, $ms=0) + { + if(!$this->stream) + return false; + stream_set_timeout($this->stream, $timeout, $ms); + } + function Eof() + { + if(!$this->stream) + return true; + $x = stream_get_meta_data($this->stream); + if($x['eof'] && $x['unread_bytes']==0 && feof($this->stream)) + return true; + return false; + } + function Read($length = 512, $type = PHP_BINARY_READ) + { + if(!$this->stream) return false; + if($type == PHP_NORMAL_READ){ + $ret = fgets($this->stream, $length); + } elseif($type == PHP_BINARY_READ) { + $ret = fread($this->stream, $length); + } + //file_put_contents('/home/wok/public_html/kiwi/rawlog', '> '.$ret, FILE_APPEND); + return $ret; + } + function Write($data) + { + global $totalup; + if(!$this->stream) return false; + + //file_put_contents('/home/wok/public_html/kiwi/rawlog', '< '.$data, FILE_APPEND); + $x = @fwrite($this->stream, $data); + return $x; + } + function GetInfo() + { + if(!$this->stream) + return false; + return array('local'=>stream_socket_get_name($this->stream,false),'remote'=>stream_socket_get_name($this->stream, true)); + } +} +class SocketStreamSSL extends SocketStream +{ + function __construct($host = null, $port = null, $blocking = true) + { + if(!$host && !$port) + return; + $this->stream = @stream_socket_client("ssl://$host:$port", $errno, $errstr, 4); + if(!$this->stream) + { + debug("Error creating socket: $host $errstr\n"); + return; + } +// socket_set_timeout($this->stream, 30); + stream_set_blocking($this->stream, $blocking); + } +} +class IRC +{ + var $stream; + var $url, $urlinfo; + var $nick; + protected $msgqueue = array(); + protected $dccs = array(); + private $reconnect=0; + private $nextping=0; + public $modes; + public $chanlist; + public $connected; + function __construct($url, $kiwi=null, $opts=array()){ + $this->url = $url; + $urlinfo = parse_url($url); + $this->urlinfo = $urlinfo; + if(!ereg("^ircs?$", $urlinfo['scheme'])) + return false; + $ssl = ($urlinfo['scheme']=='ircs'); + $host = $urlinfo['host']; + $port = isset($urlinfo['port'])?$urlinfo['port']:6667; + $this->nick = $nick = isset($urlinfo['user'])?$urlinfo['user']:'kiwi_user|'.(string)rand(0,9999); + //$ident = isset($urlinfo['pass'])?$urlinfo['pass']:$nick; + $ident = (isset($opts['ident'])) ? "{$opts['ident']}_kiwi" : "{$nick}_kiwi"; + $chans = false; + if(isset($urlinfo['path'])){ + $path = trim($urlinfo['path'], "/"); + $chans = explode(",", $path); //"#".str_replace(array(":", ","), array(" ", ",#"), $path); + } + $this->connected = false; + if($ssl) + $this->stream = new SocketStreamSSL($host, $port, false); + else + $this->stream = new SocketStream($host, $port, false); + if(!$this->stream || !$this->stream->stream){ + return false; + } + + // Send the login data + if($kiwi != null) $this->stream->Write($kiwi."\r\n"); + if(isset($urlinfo['fragment'])) $this->stream->Write("PASS {$urlinfo['fragment']}\r\n"); + + $this->stream->Write("NICK $nick\r\n"); + $this->stream->Write("USER $ident 0 0 :$nick\r\n"); + //if($chans){ + // foreach($chans as $chan) + // $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n"); + //} else { + $chans = array(); + //} + $this->chans = $chans; + $this->connected = true; + return true; + } + function __destruct(){ + if($this->stream){ + $this->stream->Write("QUIT :kiwi\r\n"); + } + } + function Process(){ + if((!$this->stream || !$this->stream->stream)){ + if(time() > $this->reconnect) { + $this->__construct($this->url); + $this->reconnect = time() + 10; + } + usleep(50000); + return; + } + if($this->stream->Eof()){ + $this->stream = null; + return; + } + $r=array($this->stream->stream); + $w = $e = array(); + if(($num=@stream_select($r, $w,$e,0,50000))===false){ + + } elseif($num>0){ + $data=$this->stream->Read(512, PHP_NORMAL_READ); + //deb($data); + 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)) + { + unset($prefix, $nick, $ident, $hostname, $command, $params, $trailing, $flarp); + $prefix = $regs[1]; + $nick = $regs[2]; + $ident = $regs[3]; + $hostname = $regs[4]; + $command = $regs[5]; + $params = isset($regs[6]) ? $regs[6] : ''; + $trailing = trim(isset($regs[7]) ? $regs[7] : ''); + $flarp = compact('prefix', 'nick', 'ident', 'hostname', 'command', 'params', 'trailing'); + $this->ProcessMessage($flarp); + } + } + else + { + if(sizeof($this->msgqueue) && (microtime(true)>($this->lastqueuewrite+0.1)) && ($msg=array_shift($this->msgqueue))) + { $this->lastqueuewrite = microtime(true); $this->stream->Write($msg); } + foreach($this->dccs as $k=>&$dcc) + { + if(!$dcc->Process()) + { + if($dcc->errorinfo) + $this->SendNotice($dcc->errorinfo['nick'], $dcc->errorinfo['text']); + unset($this->dccs[$k]); + } + } + if(($now=microtime(1))>$this->nextping) + { + $this->stream->Write("PONG x\r\n"); + $this->nextping = $now + 150; + } + } + } + function ProcessMessage($message) + { + //echo $message['command']."\n"; + switch($message['command']) + { + case "PING": + $this->stream->Write("PONG {$message['trailing']}\r\n"); + break; + case "PRIVMSG": + $this->ProcessPrivMsg($message); + break; + case "001": + if($this->chans) + foreach($this->chans as $chan) + $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n"); + break; + case "443": + $newnick = 'kiwi_user|'.rand(0,99999); + $this->SendCmd("NICK ".$newnick); + $this->nick = $newnick; + break; + case "MODE": + $this->OnMODE($message); + break; + case "353": + $this->On353($message); + break; + case "QUIT": + $chan = $message['params']; + foreach($this->chanlist as &$chan){ + if(isset($chan['userlist'][$message['nick']])) + unset($chan['userlist'][$message['nick']]); + } + break; + case "PART": + $chan = $message['params']; + //debug("Parting {$message['nick']} from $chan"); + if(isset($this->chanlist[ltrim($chan, "#")])){ + unset($this->chanlist[ltrim($chan, "#")]['userlist'][$message['nick']]); + } + break; + case "JOIN": + $chan = $message['trailing']; + $nick = array($message['nick'] => ''); + $nicklist = is_array($this->chanlist[ltrim($chan, "#")]['userlist']) ? $this->chanlist[ltrim($chan, "#")]['userlist'] : + array(); + + $this->chanlist[ltrim($chan, "#")]['userlist'] = array_merge($nicklist, $nick); + break; + case "NICK": + if($message['nick'] == $this->nick){ + $this->nick = $message['trailing']; + } + foreach($this->chanlist as $chan_name => $chan){ + foreach($chan['userlist'] as $nick => $status){ + if($nick == $message['nick']){ + $this->chanlist[$chan_name]['userlist'][$message['trailing']] = $this->chanlist[$chan_name]['userlist'][$message['nick']]; + unset($this->chanlist[$chan_name]['userlist'][$message['nick']]); + break; + } + } + } + break; + } + } + function OnMODE($message){ + if($message['params'] == $this->nick) { + $modes = $message['trailing']; + $size = strlen($modes); + $add = 1; + for($i=0;$i<$size;$i++){ + if($modes{$i} == '+'){ + $add = 1; + continue; + } elseif($modes{$i} == '-'){ + $add = 0; + continue; + } + if($add && strpos($this->modes, $modes{$i}) === false) + $this->modes.=$modes{$i}; + if(!$add && strpos($this->modes, $modes{$i})) + $this->modes = str_replace($modes{$i}, "", $this->modes); + } + } + $params = $message['params']; + $chan = trim(strtok($params, ' ')); + if(in_array(trim($chan,'#'), $this->chans)){ + $modes = strtok(' '); + $therest = explode(" ", trim(strtok(''))); + $size = strlen($modes); + $add = 1; + $offset = 0; + for($i=0;$i<$size;$i++){ + if($modes{$i} == '+'){ + $add = 1; + $offset--; + continue; + } elseif($modes{$i} == '-'){ + $add = 0; + $offset--; + continue; + } + if($modes[$i]=='v') + { + $user = $therest[$i+$offset]; + if($add) + { + if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '+')===false) + $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '+'; + } + else + $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('+','',$this->chanlist[trim($chan,'#')]['userlist'][$user]); + continue; + } + if($modes[$i]=='o') + { + $user = $therest[$i+$offset]; + if($add) + { + if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '@')===false) + $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '@'; + } + else + $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('@','',$this->chanlist[trim($chan,'#')]['userlist'][$user]); + continue; + } + if($modes[$i]=='h') + { + $user = $therest[$i+$offset]; + if($add) + { + if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '%')===false) + $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '%'; + } + else + $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('%','',$this->chanlist[trim($chan,'#')]['userlist'][$user]); + continue; + } + if($modes[$i]=='q') + { + $user = $therest[$i+$offset]; + if($add) + { + if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '~')===false) + $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '~'; + } + else + $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('~','',$this->chanlist[trim($chan,'#')]['userlist'][$user]); + continue; + } + } + } + + } + function On353($message){ + // Response to NAMES command + list($nick,,$chan) = explode(" ", $message['params']); + $nicks = explode(" ", $message['trailing']); + $prefixes = array('~', '&', '@','%','+'); + $nicklist = array(); + foreach($nicks as $nick){ + if(in_array($nick{0}, $prefixes)){ + $prefix = $nick{0}; + $nick = substr($nick,1); + } + else + $prefix = ''; + $nicklist[$nick] = $prefix; + } + if(sizeof($nicklist)){ + // If we havn't got a list of nicks for this channel yet, create it + if(!isset($this->chanlist[ltrim($chan, "#")]['userlist'])){ + $this->chanlist[ltrim($chan, "#")]['userlist'] = array(); + } + $this->chanlist[ltrim($chan, "#")]['userlist'] = array_merge($this->chanlist[ltrim($chan, "#")]['userlist'], $nicklist); + } + } + function ProcessPrivMsg($message){ + //$cmd = strtok($message['trailing'], ' '); + //switch($cmd){ + //} + } + function SendMessage($dest, $text){ + if(!$this->stream) + return false; + $this->stream->Write("PRIVMSG $dest :$text\r\n"); + } + function Join($chan){ + $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n"); + } + function SendNotice($dest, $text) + { + $this->stream->Write("NOTICE $dest :$text\r\n"); + } + function QueueMessage($dest, $text) + { + $this->msgqueue[] = "PRIVMSG $dest :$text\r\n"; + } + function QueueNotice($dest, $text) + { + $this->msgqueue[] = "NOTICE $dest :$text\r\n"; + } + function GetMainChan() + { + return '#'.$this->chans[0]; + } +} \ No newline at end of file diff --git a/class_ircconnection.php b/class_ircconnection.php new file mode 100644 index 0000000..62b8b2a --- /dev/null +++ b/class_ircconnection.php @@ -0,0 +1,394 @@ +'connect', + 'connected'=>true, + 'host'=>$args['server'] + )); + break; + + case '005': + $opts = explode(' ', $message['params']); + $to_client = array(); + foreach($opts as $pair){ + $opt = explode('=', $pair, 2); + $name = strtoupper($opt[0]); + $val = isset($opt[1]) ? $opt[1] : true; + $this->server_options[$name] = $val; + + if(in_array($name, array('NETWORK', 'PREFIX', 'CHANTYPES'))){ + // Put the user prefixes (~&@ etc etc) into a more usable format + if($name == 'PREFIX'){ + $matches = null; + preg_match('/\(([^)]*)\)(.*)/', $val, $matches); + + $val = array(); + if(count($matches) == 3){ + for($i=0; $i 'options', + 'server' => '', + 'options' => $to_client + ); + $buffer[] = json_encode($data); + break; + + case RPL_WHOISUSER: + case RPL_WHOISSERVER: + case RPL_WHOISOPERATOR: + case RPL_WHOISIDLE: + case RPL_ENDOFWHOIS: + case RPL_WHOISCHANNELS: + case RPL_WHOISMODES: + $tmp = explode(' ', $message['params']); + $tmp = $tmp[1]; + $data = array( + 'event' => 'whois', + 'server' => '', + 'nick' => $tmp, + 'msg' => $message['trailing'] + ); + $buffer[] = json_encode($data); + break; + + case RPL_MOTD: + $data = array( + 'event' => 'motd', + 'server' => '', + 'msg' => $message['trailing'] + ); + $buffer[] = json_encode($data); + break; + + case "353": + // NAMES command reply + list($nick,,$chan) = explode(" ", $message['params']); + $nicks = explode(" ", $message['trailing']); + + $data = array( + 'event' => 'userlist', + 'server' => '', + 'users' => array(), + 'channel' => $chan + ); + + $prefixes = array('~', '&', '@','%','+'); + $nicklist = array(); + $i = 0; + foreach($nicks as $nick){ + if(in_array($nick{0}, $prefixes)){ + $prefix = $nick{0}; + $nick = substr($nick,1); + } else { + $prefix = ''; + } + $nicklist[$nick] = $prefix; + + if($i==50){ + $tmp = $data; + $tmp['users'] = $nicklist; + $buffer[] = json_encode($tmp); + unset($tmp); + $nicklist = array(); + } + + $i++; + } + + if(count($nicklist)){ + $tmp = $data; + $tmp['users'] = $nicklist; + $buffer[] = json_encode($tmp); + } + //deb(print_r($data, 1)); + break; + + case '366': + list(,$chan) = explode(' ', $message['params']); + $data = array( + 'event' => 'userlist_end', + 'server' => '', + 'channel' => $chan + ); + $buffer[] = json_encode($data); + break; + + case ERR_LINKCHANNEL: + list($nick, $source_chan, $dest_chan) = explode(' ', $message['params']); + $data = array( + 'event' => 'channel_redirect', + 'from' => $source_chan, + 'to' => $dest_chan + ); + $buffer[] = json_encode($data); + break; + + case ERR_NOSUCHNICK: + //TODO: shit + break; + + case "JOIN": + $data = array( + 'event' => 'join', + 'nick' => $message['nick'], + 'ident' => $message['ident'], + 'hostname' => $message['hostname'], + 'channel' => $message['trailing'], + ); + $buffer[] = json_encode($data); + + deb("JOIN: {$message['nick']} / {$this->nick}"); + if($message['nick'] == $this->nick){ + $this->stream->Write("NAMES {$message['trailing']}\r\n"); + } + + break; + + case "PART": + $data = array( + 'event' => 'part', + 'nick' => $message['nick'], + 'ident' => $message['ident'], + 'hostname' => $message['hostname'], + 'channel' => trim($message['params']), + 'message' => $message['trailing'], + ); + $buffer[] = json_encode($data); + break; + + case "KICK": + $tmp = explode(' ', $message['params']); + + $data = array( + 'event' => 'kick', + 'kicked' => $tmp[1], + 'nick' => $message['nick'], + 'ident' => $message['ident'], + 'hostname' => $message['hostname'], + 'channel' => trim($tmp[0]), + 'message' => $message['trailing'], + ); + $buffer[] = json_encode($data); + break; + + case "QUIT": + $data = array( + 'event' => 'quit', + 'nick' => $message['nick'], + 'ident' => $message['ident'], + 'hostname' => $message['hostname'], + 'message' => $message['trailing'], + ); + $buffer[] = json_encode($data); + break; + + case "NOTICE": + $data = array( + 'event' => 'notice', + 'nick' => $message['nick'], + 'ident' => $message['ident'], + 'hostname' => $message['hostname'], + 'msg' => $message['trailing'], + ); + $buffer[] = json_encode($data); + break; + + case "NICK": + $data = array( + 'event' => 'nick', + 'nick' => $message['nick'], + 'ident' => $message['ident'], + 'hostname' => $message['hostname'], + 'newnick' => $message['trailing'], + ); + $buffer[] = json_encode($data); + break; + + case 'TOPIC': + $data = array( + 'event' => 'topic', + 'nick' => $message['nick'], + 'channel' => $message['params'], + 'topic' => $message['trailing'] + ); + $buffer[] = json_encode($data); + break; + + case '332': + $tmp = explode(' ', $message['params']); + $data = array( + 'event' => 'topic', + 'nick' => '', + 'channel' => $tmp[1], + 'topic' => $message['trailing'] + ); + $buffer[] = json_encode($data); + break; + + case 'ACTION': + $data = array( + 'event' => 'action', + 'nick' => $message['nick'], + 'msg' => trim(substr($message['trailing'], 8), chr(1)), + 'channel' => $message['params'], + 'time' => time() + ); + $buffer[] = json_encode($data); + break; + + case 'CTCP': + $data = array( + 'event' => 'ctcp', + 'nick' => $message['nick'], + 'msg' => trim(substr($message['trailing'], 8), chr(1)), + 'channel' => $message['params'], + 'time' => time() + ); + $buffer[] = json_encode($data); + break; + + case 'MODE': + $opts = explode(' ', $message['params']); + if(count($opts) == 1){ + $effected_nick = $opts[0]; + $mode = $message['trailing']; + } elseif(count($opts) == 2){ + $channel = $opts[0]; + $mode = $opts[1]; + } else { + $channel = $opts[0]; + $mode = $opts[1]; + $effected_nick = $opts[2]; + } + + $data = array( + 'event' => 'mode', + 'nick' => $message['nick'], + 'mode' => $mode + ); + + if(isset($effected_nick)) $data['effected_nick'] = $effected_nick; + if(isset($channel)) $data['channel'] = $channel; + $buffer[] = json_encode($data); + + break; + } + + if($send_debug){ + $ret = str_replace('\n', ' ', print_r($message, 1)); + $data = array( + 'event' => 'debug', + 'msg' => $ret, + 'time' => time() + ); + $buffer[] = json_encode($data); + } + + parent::ProcessMessage($message); + } + + + + + function ProcessPrivMsg($message){ + global $buffer; + global $timeout; + global $config; + + $msg = $message['trailing']; + $cmd = strtok($msg, ' '); + //debug(print_r($message)); + //deb(print_r($message, 1)); + $data = array( + 'event' => 'msg', + 'nick' => $message['nick'], + 'msg' => $message['trailing'], + 'channel' => $message['params'], + 'time' => time() + ); + + // Save it into the scrollback + if($config['scrollback_size']){ + deb('Size: '.count($this->scrollback)); + if(count($this->scrollback) >= $config['scrollback_size']){ + + for($i=1; $iscrollback); $i++){ + $this->scrollback[$i-1] = $this->scrollback[$i]; + } + /* + for($j=$i; $jscrollback); $j++){ + unset($this->scrollback[$i]); + } + //unset($this->scrollback[$i]); + */ + $pos = count($this->scrollback)-1; + deb('Popped'); + } else { + $pos = count($this->scrollback); + } + + $this->scrollback[$pos] = $data; + } + + $buffer[] = json_encode($data); + parent::ProcessPrivMsg($message); + + return; + } + + + private function isChannel($name){ + return ($name[0] == "#"); + } + + + private function formatMsgToHtml($inp){ + $tmp = $inp; + + // Bold + if(strpos($tmp, chr(2))){ + $next = ''; + while(strpos($tmp, chr(2)) !== false){ + $pos = strpos($tmp, chr(2)); + $tmp = str_replace($tmp, chr(2), $next); + $next = ($next=='') ? '' : ''; + } + if($next == '') $tmp = $tmp . ''; + } + + return $tmp; + } + + } \ No newline at end of file diff --git a/class_session.php b/class_session.php new file mode 100644 index 0000000..2a31d5d --- /dev/null +++ b/class_session.php @@ -0,0 +1,115 @@ +get('sid_'.$session_id) ? true : false; + return $ret; + } else { + $session_sok = $config['sok_dir'].$config['sok_prefix'].$session_id; + return file_exists($session_sok); + } + } + + + + static function create($client_key){ + global $config; + + $temp_id = md5(microtime().rand()); + + if($config['memcache_use']){ + $host_key = 'hostcount_'.$client_key; + $count = (int)GLOB::$mc->get($host_key); + if($count > $config['connections_per_host']) return false; + + // Save the sid into memcached for the ircbot to pick up + GLOB::$mc->add('sid_'.$temp_id, $temp_id); + } + + $session_cmd = "{$config['php_path']} {$config['session_script_path']} $temp_id $host_key -h$client_key"; + //die($session_cmd); + $session_status = `$session_cmd`; + + if($session_status != 'ok'){ + debug("Failed creating session socket with: $session_status"); + GLOB::$mc->delete('sid_'.$temp_id); + return false; + } + + if($config['memcache_use']){ + GLOB::$mc->add($host_key, 0); + GLOB::$mc->increment($host_key); + } + + return $temp_id; + } + + + + static function open($session_id){ + global $config; + + if($config['memcache_use']){ + $session_sok = GLOB::$mc->get('sid_'.$session_id); + if(!$session_sok) return false; + } else { + $session_sok = $session_id; + } + $session_sok = $config['sok_dir'].$config['sok_prefix'].$session_sok; + + $sok = @stream_socket_client('unix://'.$session_sok, $errno, $errstr); + + if(!$sok) return false; + return $sok; + } + + + + static function close($session){ + fclose($session); + } + + + static function read($session, $block=true){ + fwrite($session, json_encode(array('method'=>'read'))); + + if(!$block){ + stream_set_timeout($session, 0, 100000); + } else { + stream_set_timeout($session, 120, 0); + } + $data = fgets($session); + + return $data; + } + + + + + + static function clStart($session_id){ + global $config; + + if($config['memcache_use']){ + $session_sok = GLOB::$mc->get('sid_'.$session_id); + if(!$session_sok) return false; + } else { + $session_sok = $session_id; + } + $session_sok = $config['sok_dir'].$config['sok_prefix'].$session_sok; + + $sok = stream_socket_server('unix://'.$session_sok, $errno, $errstr); + if(!$sok) return false; + + return $sok; + + } + + + static function clStop($session){ + fclose($session); + } + } diff --git a/common.php b/common.php new file mode 100644 index 0000000..7085d76 --- /dev/null +++ b/common.php @@ -0,0 +1,24 @@ +addServer('localhost', 11211, true, 1); + } + + + + function deb($what){ + //if(DEBUG){ + // echo "$what\n"; + //} else { + file_put_contents('/tmp/kiwi_err.log', "$what\n", FILE_APPEND); + //} + } + function debug($what){ deb($what); } \ No newline at end of file diff --git a/config.php b/config.php new file mode 100644 index 0000000..4783f0a --- /dev/null +++ b/config.php @@ -0,0 +1,25 @@ + '/tmp/', + 'sok_prefix' => 'kiwi_', + + 'memcache_use' => true, + 'memcache_addr' => 'localhost', + 'memcache_port' => 11211, + + 'php_path' => '/usr/bin/php5', + 'session_script_path' => dirname(__FILE__).'/irc_session.php', + //Unused 'cons_per_host' => 5, + + 'messages_per_poll' => 30, + 'max_time_per_poll' => 30, //60*2, + 'timeout' => 5, // minimum time in seconds in which the webclient needs to poll the server + 'scrollback_size' => 15, + 'connections_per_host' => 20, + + 'websocket' => array( + 'bind_addr'=>'0.0.0.0', + 'bind_port'=>7777 + ) + ); \ No newline at end of file diff --git a/css/border-radius.htc b/css/border-radius.htc new file mode 100644 index 0000000..c3ae104 --- /dev/null +++ b/css/border-radius.htc @@ -0,0 +1,143 @@ +--Do not remove this if you are using-- +Original Author: Remiz Rahnas +Original Author URL: http://www.htmlremix.com +Published date: 2008/09/24 + +Changes by Nick Fetchak: +- IE8 standards mode compatibility +- VML elements now positioned behind original box rather than inside of it - should be less prone to breakage +Published date : 2009/11/18 + + + + + diff --git a/css/default.css b/css/default.css new file mode 100644 index 0000000..c3a4839 --- /dev/null +++ b/css/default.css @@ -0,0 +1,174 @@ +body, html { + height: 100%; margin:0px; + font-size:14px; +} + + + + + +/* Style resets */ +#kiwi * { + padding:0px; margin:0px; +} +#kiwi ul { list-style:none; } + +#kiwi #login a { + color: #8D9713; + text-decoration:none; +} + + +/* The main app container */ +#kiwi { + overflow:hidden; + position:relative; + height:100%; + font-family:monospace; font-size:1em; + background:#EBEBEB; +} +#kiwi.small_kiwi .userlist { right:-100px; } +#kiwi.small_kiwi .messages { right:10px; } + + +#kiwi .windowlist { + /*height: 65px;*/ + background-color:#1B1B1B; + font-size:0.9em; +} +#kiwi .windowlist ul { + overflow: hidden; + margin-right: 200px; + white-space: nowrap; + display:block; + /*height: 35px;*/ +} +#kiwi .windowlist ul li { + float: left; + list-style: inline; + padding:5px; + margin:3px; + border: 1px solid #333; + background-color: #eee; + display: inline; + line-height: 1.4em; + vertical-align: middle; + + position:relative; + + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + -khtml-border-radius:5px; + behavior: url(border-radius.htc); +} +#kiwi .windowlist .active { padding-right:23px; } +#kiwi .windowlist .highlight { + background-color: #990000; + font-weight: bold; +} +#kiwi .windowlist .activity { font-weight: bold; background-color: #009900; } + +#kiwi .windowlist ul li img { width:1em; height:1em; top:7px; right:5px; position:absolute; } +/*#kiwi .windowlist ul li a { + margin:3px; + border: 1px solid #333; + background-color: #eee; + display: inline; +}*/ + +#kiwi .poweredby { float:right; margin:8px 15px; cursor:pointer; color:#EBEBEB; font-family: arial;} + + +#kiwi .userlist { + position: absolute; + width: 100px; + top:92px; right:0px; + bottom: 55px; + margin: 5px; + overflow-y:auto; +} +#kiwi .userlist ul { display: none; } +#kiwi .userlist ul.active { display:block; } +#kiwi .userlist ul li { padding:1px 2px; cursor:pointer; } +#kiwi .userlist ul li:hover { background-color:#FAF7D3; } +#kiwi .userlist ul li a.nick { display:block; } + +#kiwi .userlist ul li .userbox { margin:0px 5px 5px 5px; font-size:.9em; } +#kiwi .userlist ul li .userbox a { display:block; text-decoration:none; border-bottom: 1px dashed #aaa; } + +#kiwi .cur_topic { + font-family:Verdana, Geneva, sans-serif; + font-size:.85em; + line-height:18px; vertical-align:middle; + height:18px; + text-align:center; + white-space: nowrap; overflow: hidden; + padding: 3px 5px; + border-bottom:3px solid #1B1B1B; + +} + +#kiwi .messages { + position: absolute; + top:92px; left:0px; + right: 110px; bottom:30px; + overflow-y:scroll; + overflow-x:wrap; + border:none; + display: none; +} +#kiwi .messages a { + text-decoration:none; +} +#kiwi .messages.active { display:block; } + +#kiwi .messages .msg { border-bottom: 1px solid #CCC; padding:1px; font-size:0.9em; } +#kiwi .messages .msg .time { width:6em; float:left; color:#777; } +#kiwi .messages .msg .nick { width:7em; text-align:right; float:left; font-family:arial; font-size:12px; } +#kiwi .messages .msg .text { margin-left:15em; white-space:pre-wrap; word-wrap:break-word; } + +#kiwi .messages .msg.action .nick { display:none; } +#kiwi .messages .msg.action .text { margin-left:9em; } +#kiwi .messages .msg.status .nick { display:none; } +#kiwi .messages .msg.status .text { color:#990000; margin-left:9em; font-weight:bold; } +#kiwi .messages .msg.topic .nick { display:none; } +#kiwi .messages .msg.topic .text { color:#009900; margin-left:9em; font-style: italic; } +/*#kiwi .messages .msg.motd .nick { display:none; }*/ +#kiwi .messages .msg.motd { border:none; } +#kiwi .messages .msg.motd .text { color:#666; } +#kiwi .messages .msg.whois .nick { font-weight:normal; } +#kiwi .messages .msg.whois .text { margin-left:18em; padding-left:1em; border-left:1px dashed #999; } + + + +#kiwi .control { + position: absolute; + bottom:0px; left: 0px; + /*height:55px;*/ + /*padding: 3px;*/ + width: 100%; + background-color:#1B1B1B; +} +#kiwi .control .msginput { + background-color:#fff; margin:3px; + position:relative; + + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + -khtml-border-radius:5px; + behavior: url(border-radius.htc); +} +#kiwi .control .nick { text-align: right; width:11em; float:left; padding:2px; } +#kiwi .control .nick a { text-decoration:none; } +#kiwi .control input { + border:none; + width:70%; display:block; + margin-left:12em; + padding: 2px 0px; +} +#kiwi .control .plugins { margin:3px; margin-top:7px; } +#kiwi .control .plugins ul li { + display:inline; font-size:0.8em; margin-left:1em; +} \ No newline at end of file diff --git a/css/default_1.css b/css/default_1.css new file mode 100644 index 0000000..007ff7e --- /dev/null +++ b/css/default_1.css @@ -0,0 +1,30 @@ +body, html { height: 100%; margin:0px; } + + + +#kiwi { overflow:hidden; font-family:monospace; font-size:1em; height:100%; } +#kiwi * { + padding:0px; margin:0px; +} +#kiwi ul { list-style:none; } + +#kiwi .userlist { + float:right; + width: 100px; + margin: 5px; + overflow-y:auto; +} + +#kiwi .messages { overflow-y:scroll; height:90%; border:1px solid #999; } + +#kiwi .messages .msg { border-bottom: 1px solid #CCC; } +#kiwi .messages .msg .time { width:6em; float:left; } +#kiwi .messages .msg .nick { width:7em; text-align:right; float:left; } +#kiwi .messages .msg .text { margin-left:15em; } + +#kiwi .control { margin: 3px;} +#kiwi .control .msginput { padding:3px; padding-left: 7em; background-color:#F2F9FD; } +#kiwi .control .msginput .nick { float:left; width:6em; text-align:right; } +#kiwi .control .msginput .nick a { text-decoration:none; } +#kiwi .control .msginput input { margin-left: 1em; border:none; background-color:inherit; padding:3px; display:block;} +#kiwi .control .plugins ul li { display:inline; font-size:0.9em; margin-left:1em; } \ No newline at end of file diff --git a/css/touchscreen_tweaks.css b/css/touchscreen_tweaks.css new file mode 100644 index 0000000..5e74a5d --- /dev/null +++ b/css/touchscreen_tweaks.css @@ -0,0 +1,11 @@ +#kiwi.touchscreen.portrait .userlist { right:-100px; } + +#kiwi.touchscreen.portrait .messages { right:10px; } + +#kiwi.touchscreen .windowlist ul li { padding:10px; } + +#kiwi .windowlist ul { + /* border: 1px solid blue;*/ + margin-right: 0px; /* i* safaris seem to behave differently here :/ */ + height: 50px; +} \ No newline at end of file diff --git a/css/ui.css b/css/ui.css new file mode 100644 index 0000000..838cc40 --- /dev/null +++ b/css/ui.css @@ -0,0 +1,114 @@ +/* + #################### + Common + #################### +*/ +#kiwi .link { cursor:pointer; color:blue; } +#kiwi .newnick { + bottom:40px; + left:20px; +} + + +#kiwi .plugins a { cursor:pointer; color:#EBEBEB; font-family:verdana; } +#kiwi .plugin_file { left:20px; } + +#kiwi .plugin_file .list select { width:150px; height:200px; } +#kiwi .plugin_file .list button { float:right; } + + + + + + + +#kiwi .connectwindow { + background:#1b1b1b; + color: #D4D4D4; + z-index:50; + position:absolute; + top:0px; bottom:0px; + width:100%; height:100%; +} +#kiwi h1.logo { display:block; width:264px; height:90px; text-indent:-1000px; margin:70px auto 100px; background:url(../img/logo.png) no-repeat; } +#kiwi #login { width:665px; margin:0px auto; } +#kiwi #login ul { list-style:none; } +#kiwi #login ul li { overflow:hidden; margin-bottom:10px; } +#kiwi #login ul li label { float:left; text-align:right; padding-right:.5em; } +#kiwi #login input { border:none; } +#kiwi #login .more { border-top: 1px dotted #888888; } +#kiwi #login .more_link { + font-size:12px; + position:relative; + top:-16px; + background: url(../img/more.png) no-repeat right 5px; + padding-right: 12px; + color:#888; +} + +#kiwi #login .content.top { + width:470px; + margin:30px auto 60px; +} +#kiwi #login .content.top ul { font-size:20px; } +#kiwi #login .content.top label { width:40%; } +#kiwi #login .content.top input { + width:240px; padding:.2em .5em; + font-size:.8em; text-align:center; + background:#D4D4D4; + border-radius:8px; -moz-border-radius:8px; +} +#kiwi #login .content.top a { display:block; text-align:right; font-size:22px; margin-top:30px; } + +#kiwi #login .content.bottom { width:350px; margin:30px auto; display:none; } +#kiwi #login .content.bottom ul { font-size:18px; } +#kiwi #login .content.bottom label { width:25%; } +#kiwi #login .content.bottom input { width:216px; font-size:.8em; no-repeat; padding:.2em .5em; background:#D4D4D4; border-radius:8px; -moz-border-radius:8px; } +#kiwi #login .content.bottom a { display:block; text-align:right; font-size:16px; margin-top:30px; } + + + + +#kiwi .about { + top: 100px; + left: 200px; + width: 400px; + z-index: 1; + display: none; +} +#kiwi .about button { float:right; } + +#kiwi .box { + min-width:150px; + background: url(../img/trans_back.png) repeat; + border-radius:5px; + -moz-border-radius:5px; + -webkit-border-radius:5px; + -khtml-border-radius:5px; + padding:7px; + + /*background-color: #819463; + padding: 10px;*/ + position:absolute; + + /* + box-shadow: 0px 0px 20px #555; + -webkit-box-shadow: 0px 0px 20px #555; + -moz-box-shadow: 0px 0px 20px #555; + filter: progid:DXImageTransform.Microsoft.dropShadow(color=#555, offX=0, offY=0, positive=true); + + border-radius: 4px; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + */ +} + +.box .boxarea { + padding:15px !important; + background-color:#D4D4D4; + border-radius:3px; + -moz-border-radius:3px; + -webkit-border-radius:3px; + -khtml-border-radius:3px; + overflow:hidden; +} \ No newline at end of file diff --git a/dev_processClientMessage.php b/dev_processClientMessage.php new file mode 100644 index 0000000..fffc041 --- /dev/null +++ b/dev_processClientMessage.php @@ -0,0 +1,142 @@ +'invalid_args')); + break; + } else { + $kiwi = null; + /*if($config['memcache_use']){ + $conf_key = 'kiwi_conf_'.$args['server']; + $conf = (int)GLOB::$mc->get($conf_key); + if($conf){ + $c = @unserialize($conf); + if($c){ + //$hostname = GET HOSTNAME HERE + //$ip = GET IP HERE + $kiwi = "WEBIRC {$c['password']} {$c['user']} $hostname $ip"; + } + } + }*/ + deb("ARGSLOL: ".print_r($app_args, 1)); + $opts = array(); + if(isset($app_args['remote_host'])) $opts['ident'] = md5($app_args['remote_host']); + + $bot = new IRCConnection("irc://{$args['nick']}@{$args['server']}:{$args['port']}", $kiwi, $opts); + if(isset($args['channels'])) $bot->chans = explode(',', $args['channels']); + if($bot->connected){ + // We're.. connected! + } else { + $buffer[] = json_encode(array('event'=>'server_connect', 'connected'=>false, 'host'=>$args['server'])); + unset($bot); + } + } + break; + + case 'join': + $args = $d['args']; + if(!isset($args['channel'])){ + $buffer[] = json_encode(array('error'=>'invalid_args')); + break; + } else { + $chans = explode(',', $args['channel']); + foreach($chans as $c) + $bot->Join($c); + } + break; + + case 'msg': + $args = $d['args']; + deb('msg with: '.print_r($args, 1)); + if(!isset($args['target'],$args['msg'])){ + $buffer[] = json_encode(array('error'=>'invalid_args')); + break; + } else { + $bot->SendMessage($args['target'], $args['msg']); + $buffer[] = json_encode($data); + } + break; + + case 'action': + $args = $d['args']; + deb('action with: '.print_r($args, 1)); + if(!isset($args['target'],$args['msg'])){ + $buffer[] = json_encode(array('error'=>'invalid_args')); + break; + } else { + $bot->SendMessage($args['target'], chr(1)."ACTION {$args['msg']}".chr(1)); + $buffer[] = json_encode($data); + } + break; + + case 'raw': + $args = $d['args']; + if(!isset($args['data'])){ + $buffer[] = json_encode(array('error'=>'invalid_args')); + break; + } else { + $bot->stream->Write("{$args['data']}\r\n"); + //$bot->SendMessage($args['target'], $args['msg']); + } + break; + + + case 'debug': + $send_debug = !$send_debug; + $data = array( + 'event' => 'debug', + 'msg' => 'Debugging '.($send_debug)?'on':'off', + 'time' => time() + ); + $buffer[] = json_encode($data); + break; + + + case 'sync': + // Clear the current buffer as we're gonna send only scrollback + $buffer = array(); + + // Send the settings and channels over + if($bot){ + $data = array('event'=>'sync'); + $data['nick'] = $bot->nick; + $data['tabviews'] = array(); + if($bot->chanlist){ + foreach($bot->chanlist as $chan_name => $chan){ + $data['tabviews'][] = array('name'=>$chan_name, 'userlist'=>$chan['userlist']); + } + $buffer[] = json_encode($data); + } + + // Send the message scrollback + foreach($bot->scrollback as $line) $buffer[] = json_encode($line); + //$bot->scrollback = array(); + } else { + $data = array('error'=>'no_data'); + } + + $buffer[] = json_encode($data); + + break; + } \ No newline at end of file diff --git a/embed.php b/embed.php new file mode 100644 index 0000000..3e286ae --- /dev/null +++ b/embed.php @@ -0,0 +1,97 @@ + + + + +kiwi + + + + 0: + case stripos($_SERVER['HTTP_USER_AGENT'], 'iphone') > 0: + case stripos($_SERVER['HTTP_USER_AGENT'], 'ipod') > 0: +?> + + + + + + + + + + + + + + + +
+
+
+ +
+ + + + + + + + + + +
+
+ + + + +
+
    +
    Powered by kiwi
    +
    + +
    +
      +
      + + +
      +
      +
      :
      + +
      + +
      +
      + + + diff --git a/img/logo.png b/img/logo.png new file mode 100644 index 0000000..59d5aa4 Binary files /dev/null and b/img/logo.png differ diff --git a/img/more.png b/img/more.png new file mode 100644 index 0000000..a0d4dd7 Binary files /dev/null and b/img/more.png differ diff --git a/img/redcross.png b/img/redcross.png new file mode 100644 index 0000000..e5f5fe0 Binary files /dev/null and b/img/redcross.png differ diff --git a/img/trans_back.png b/img/trans_back.png new file mode 100644 index 0000000..f17dc9d Binary files /dev/null and b/img/trans_back.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..e69de29 diff --git a/index.lighttpd.html b/index.lighttpd.html new file mode 100644 index 0000000..e639955 --- /dev/null +++ b/index.lighttpd.html @@ -0,0 +1,57 @@ + + + + +Welcome page + + + +
      + +
      +

      You should replace this page with your own web pages as soon as possible.

      + Unless you changed its configuration, your new server is configured as follows: +
        +
      • Configuration files can be found in /etc/lighttpd. Please read /etc/lighttpd/conf-available/README file.
      • +
      • The DocumentRoot, which is the directory under which all your HTML files should exist, is set to /var/www.
      • +
      • CGI scripts are looked for in /usr/lib/cgi-bin, which is where Ubuntu packages will place their scripts. You can enable cgi module by using command "lighty-enable-mod cgi".
      • +
      • Log files are placed in /var/log/lighttpd, and will be rotated weekly. The frequency of rotation can be easily changed by editing /etc/logrotate.d/lighttpd.
      • +
      • The default directory index is index.html, meaning that requests for a directory /foo/bar/ will give the contents of the file /var/www/foo/bar/index.html if it exists (assuming that /var/www is your DocumentRoot).
      • +
      • You can enable user directories by using command "lighty-enable-mod userdir"
      • +
      +

      About this page

      +

      + This is a placeholder page installed by the Ubuntu release of the Lighttpd server package. +

      +

      + This computer has installed the Ubuntu operating system, but it has nothing to do with the Ubuntu Project. Please do not contact the Ubuntu Project about it. +

      +

      + If you find a bug in this Lighttpd package, or in Lighttpd itself, please file a bug report on it. Instructions on doing this, and the list of known bugs of this package, can be found in the + Ubuntu Bug Tracking System. +

      +

      + Valid XHTML 1.0 Transitional +

      +
      +
      + + + diff --git a/index.php b/index.php new file mode 100644 index 0000000..d1b1f45 --- /dev/null +++ b/index.php @@ -0,0 +1,155 @@ + 0: + $agent = "android"; $touchscreen = true; + break; + + case stripos($_SERVER['HTTP_USER_AGENT'], 'iphone') > 0: + $agent = "iphone"; $touchscreen = true; + break; + + case stripos($_SERVER['HTTP_USER_AGENT'], 'ipod') > 0: + $agent = "ipod"; $touchscreen = true; + break; + + case stripos($_SERVER['HTTP_USER_AGENT'], 'ipad') > 0: + $agent = "ipad"; $touchscreen = true; + break; + + default: + $agent = "normal"; + $touchscreen = false; + } + + define("SERVER_SET", isset($_GET['server'])); + $server = isset($_GET['server']) ? $_GET['server'] : "irc.anonnet.org"; + $nick = isset($_GET['nick']) ? $_GET['nick'] : ""; + // Channel is set via javascript using location.hash + +?> + + + + + + + + + +Kiwi IRC + + + + + + + + + + + + + + + + + + + + + + + + + +
      +
      +

      Kiwi IRC

      +
      +
      +
      +
        +
      • +
      • +
      + Connect.. +
      + +
      "> + More +
      +
        +
      • +
      • +
      • +
      • +
      + Connect.. +
      +
      +
      +
      +
      + + + +
      +
      Powered by Kiwi IRC
      +
        +
        + +
        + +
        +
          +
          + +
          +
          +
          :
          + +
          +
          + +
          +
          +
          + + + \ No newline at end of file diff --git a/index_old.php b/index_old.php new file mode 100644 index 0000000..7f4d827 --- /dev/null +++ b/index_old.php @@ -0,0 +1,130 @@ + 0: + $agent = "android"; $touchscreen = true; + break; + + case stripos($_SERVER['HTTP_USER_AGENT'], 'iphone') > 0: + $agent = "iphone"; $touchscreen = true; + break; + + case stripos($_SERVER['HTTP_USER_AGENT'], 'ipod') > 0: + $agent = "ipod"; $touchscreen = true; + break; + + case stripos($_SERVER['HTTP_USER_AGENT'], 'ipad') > 0: + $agent = "ipad"; $touchscreen = true; + break; + + default: + $agent = "normal"; + $touchscreen = false; + } +?> + + + + + + + + + +kiwi + + + + + + + + + + + + + + + + + + + + + + + + +
          +
          +

          kiwi

          +

          An alternative to downloading an irc client. This web app is the best thing you'll use in the next couple years.

          + +
          + +
          +
          + +
          + + + + + + + + + + +
          +
          + + + +
          +
          Powered by kiwi
          +
            +
            + +
            +
              +
              + +
              + +
              +
              +
              :
              + +
              + +
              +
              + + + \ No newline at end of file diff --git a/inf.php b/inf.php new file mode 100644 index 0000000..9c66bb6 --- /dev/null +++ b/inf.php @@ -0,0 +1,5 @@ += $config['timeout']) break; + } else { + $timeout = 0; + } + + processClients(); + + if($bot != null){ + // $bot handles the sleep time here + $bot->Process(); + } else { + usleep(5000); + } + } + + + // We've quit, lower the counter for this host + if($config['memcache_use']){ + @GLOB::$mc->decrement($host_key); + } + + deb('Exiting client'); + + + ############################################ + ## Functions / Classes + ############################################ + + + + function processClients(){ + global $ipc_srv; + global $ipc; + global $ipc_read; + global $buffer; + global $timeout; + + // Check for any new web client connections... + $read = array($ipc_srv); + $write = $excep = array(); + $read_changed = stream_select($read, $write, $excep, 0); + for($i=0; $i<$read_changed; $i++) { + if($read[$i] === $ipc_srv){ + $ipc[] = stream_socket_accept($ipc_srv); + deb("Connection..."); + } + } + + + // Check for any changed web clients.. + $read = $ipc; + $read_changed = (count($read)) ? stream_select($read, $write, $excep, 0) : array(); + if($read_changed){ + foreach($read as $cl){ + $data = fread($cl, 1024); + if(!$data){ + // Web client has disconnected + deb("Removing closed socket.."); + $key = array_search($cl, $ipc); + unset($ipc[$key]); + + $key = array_search($cl, $ipc_read); + if($key !== false) unset($ipc_read[$key]); + } else { + //deb('Got data: '.$data); + processClientMessage($data, $cl); + } + } + } + + + + // Send the buffer messages to any connected web clients.. + // 1 message at a time... + if(count($buffer) && count($ipc_read)){ + //deb(print_r($ipc_read, 1)); + $msg = array_shift($buffer); + //deb("Sending '$msg' to ".count($ipc_read)." clients.."); + foreach($ipc_read as $cl){ + if($cl) fwrite($cl, $msg."\n"); + } + } + + // The whole buffer at a time... + /* + while(count($buffer)){ + $msg = array_shift($buffer); + foreach($ipc as $cl) frwite($cl, $msg); + } + */ + + } + + + function processClientMessage($data, $cl){ + global $config; + global $buffer; + global $bot, $ipc_read; + global $timeout; + global $send_debug; + global $app_args; + + require('dev_processClientMessage.php'); + return true; + } + + + + + function errorHandler($errno, $errstr, $errfile, $errline){ + $ret = ''; + switch ($errno) { + case E_USER_ERROR: + $ret .= "USER [$errno] $errstr\n"; + $ret .= " Fatal error on line $errline in file $errfile"; + $ret .= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")\n"; + $ret .= "Aborting...
              \n"; + exit(1); + break; + + case E_USER_WARNING: + $ret .= "WARNING on line $errline in file $errfile: [$errno] $errstr\n"; + break; + + case E_USER_NOTICE: + $ret .= "NOTICE on line $errline in file $errfile: [$errno] $errstr\n"; + break; + + default: + $ret .= "UNKOWN ERR on line $errline in file $errfile: [$errno] $errstr\n"; + break; + } + + if(!empty($ret)) deb($ret); + + + /* Don't execute PHP internal error handler */ + return true; + } \ No newline at end of file diff --git a/ircd_profiles/hybrid.php b/ircd_profiles/hybrid.php new file mode 100644 index 0000000..f5284b4 --- /dev/null +++ b/ircd_profiles/hybrid.php @@ -0,0 +1,27 @@ + '401', + 'ERR_NOSUCHSERVER' => '402', + 'ERR_NOSUCHCHANNEL' => '403', + 'ERR_CANNOTSENDTOCHAN' => '404', + 'ERR_TOOMANYCHANNELS' => '405', + 'ERR_WASNOSUCHNICK' => '406', + 'ERR_TOOMANYTARGETS' => '407', + ); \ No newline at end of file diff --git a/ircd_profiles/rfc_2812.php b/ircd_profiles/rfc_2812.php new file mode 100644 index 0000000..4db4751 --- /dev/null +++ b/ircd_profiles/rfc_2812.php @@ -0,0 +1,6 @@ + '001', + 'RPL_YOURHOST' => '002', + ); \ No newline at end of file diff --git a/js/front.js b/js/front.js new file mode 100644 index 0000000..172749a --- /dev/null +++ b/js/front.js @@ -0,0 +1,953 @@ +var front = { + revision: 38, + + cur_channel: '', + windows: {}, + tabviews: {}, + boxes: {}, + + buffer: [], + buffer_pos: 0, + + init: function(){ + gateway.nick = 'kiwi_'+Math.ceil(100*Math.random())+Math.ceil(100*Math.random()); + gateway.session_id = null; + + $(gateway).bind("onmsg", front.onMsg); + $(gateway).bind("onnotice", front.onNotice); + $(gateway).bind("onaction", front.onAction); + $(gateway).bind("onmotd", front.onMOTD); + $(gateway).bind("onoptions", front.onOptions); + $(gateway).bind("onconnect", front.onConnect); + $(gateway).bind("onnick", front.onNick); + $(gateway).bind("onuserlist", front.onUserList); + $(gateway).bind("onuserlist_end", front.onUserListEnd); + $(gateway).bind("onjoin", front.onJoin); + $(gateway).bind("ontopic", front.onTopic); + $(gateway).bind("onpart", front.onPart); + $(gateway).bind("onkick", front.onKick); + $(gateway).bind("onquit", front.onQuit); + $(gateway).bind("onwhois", front.onWhois); + $(gateway).bind("onsync", front.onSync); + $(gateway).bind("onchannel_redirect", front.onChannelRedirect); + $(gateway).bind("ondebug", front.onDebug); + + this.buffer = new Array(); + + // Build the about box + front.boxes.about = new box("about"); + front.boxes.about.content.html('

              Kiwi IRC

              '); + front.boxes.about.content.append("

              An alternative to downloading an irc client. Kiwi IRC is the best web app you'll use for the next couple years.

              "); + front.boxes.about.content.append(''); + + var about_info = 'UI adapted for '+agent; + if(touchscreen) about_info += ' touchscreen '; + about_info += 'usage'; + + front.boxes.about.content.append('

              '+about_info+'

              '); + front.boxes.about.content.append('

              Front: '+front.revision+'
              Gateway: '+gateway.revision+'

              '); + + //$(window).bind("beforeunload", function(){ gateway.quit(); }); + + front.registerKeys(); + + + $('#kiwi .formconnectwindow').submit(function(){ + var netsel = $('#kiwi .formconnectwindow .network'); + var nick = $('#kiwi .formconnectwindow .nick'); + + if(nick.val() == ''){ + nick.val('Nick please!'); + nick.focus(); + return false; + } + + var tmp = nick.val().split(' '); + gateway.nick = tmp[0]; + front.doLayout(); + try { + front.run('/connect '+netsel.val()); + } catch(e){ + alert(e); + } + + $('#kiwi .connectwindow').slideUp(); + return false; + }); + + var supportsOrientationChange = "onorientationchange" in window, + orientationEvent = supportsOrientationChange ? "orientationchange" : "resize"; + window.addEventListener(orientationEvent, front.doLayoutSize, false); + //$('#kiwi').bind("resize", front.doLayoutSize, false); + + front.doLayout(); + //front.windowAdd('server'); + front.tabviewAdd('server'); + + // Any pre-defined nick? + if(typeof init_data.nick == "string") $('#kiwi .formconnectwindow .nick').val(init_data.nick); + + //gateway.session_id = 'testses'; + + gateway.start(); + //front.sync(); + }, + + doLayoutSize: function() { + var kiwi = $('#kiwi'); + if(kiwi.width() < 330 && !kiwi.hasClass('small_kiwi')){ + console.log("switching to small kiwi"); + kiwi.removeClass('large_kiwi'); + kiwi.addClass('small_kiwi'); + } else if(kiwi.width() >= 330 && !kiwi.hasClass('large_kiwi')) { + console.log("switching to large kiwi"); + kiwi.removeClass('small_kiwi'); + kiwi.addClass('large_kiwi'); + } + + var ct = $('#kiwi .cur_topic'); + var ul = $('#kiwi .userlist'); + var top = ul.css('margin-top').replace('px', '') + ct.css('border-bottom-width').replace('px', ''); + //top = parseInt(ct.offset().top) + parseInt(ct.height()) + parseInt(top); + top = parseInt(ct.height()) + parseInt(top); + + $('#kiwi .messages').css('top', top+"px"); + $('#kiwi .userlist').css('top', top+"px"); + }, + + + doLayout: function(){ + $('#kiwi .msginput .nick a').text(gateway.nick); + $('#kiwi_msginput').val(' '); + $('#kiwi_msginput').focus(); + }, + + + joinChannel: function(chan_name){ + var chans = chan_name.split(','); + for(var i in chans){ + chan = chans[i]; + if(front.tabviews[chan.toLowerCase()] == undefined){ + gateway.join(chan); + front.tabviewAdd(chan); + } else { + front.tabviews[chan.toLowerCase()].show(); + } + } + }, + + + run: function(msg){ + if(msg.substring(0,1) == '/'){ + var parts = msg.split(' '); + switch(parts[0]){ + case '/j': + case '/join': + front.joinChannel(parts[1]); + break; + + case '/connect': + case '/server': + if(parts[1] == undefined){ + alert('Usage: /connect servername [port]'); + break; + } + + if(parts[2] == undefined) parts[2] = 6667; + front.cur_channel.addMsg(null, ' ', '=== Connecting to '+parts[1]+'...', 'status'); + gateway.connect(parts[1], parts[2], 0); + break; + + case '/part': + if(typeof parts[1] == "undefined"){ + gateway.raw(msg.substring(1)+' '+front.cur_channel.name); + } else { + gateway.raw(msg.substring(1)); + } + break; + + case '/names': + if(typeof parts[1] != "undefined"){ + gateway.raw(msg.substring(1)); + } + break; + + case '/debug': + gateway.debug(); + break; + + case '/q': + case '/query': + if(typeof parts[1] != "undefined"){ + front.tabviewAdd(parts[1]); + } + break; + + case '/quote': + gateway.raw(msg.replace(/^\/quote /i,'')); + break; + + case '/me': + gateway.action(front.cur_channel.name, msg.substring(4)); + //front.tabviews[destination.toLowerCase()].addMsg(null, ' ', '* '+data.nick+' '+data.msg, 'color:green;'); + front.cur_channel.addMsg(null, ' ', '* '+gateway.nick+' '+msg.substring(4), 'action', 'color:#555;'); + break; + + default: + //front.cur_channel.addMsg(null, ' ', '--> Invalid command: '+parts[0].substring(1)); + gateway.raw(msg.substring(1)); + } + + } else { + //alert('Sending message: '+msg); + if(msg.trim() == '') return; + gateway.msg(front.cur_channel.name, msg); + var d = new Date(); + var d = d.getHours() + ":" + d.getMinutes(); + //front.addMsg(d, gateway.nick, msg); + front.cur_channel.addMsg(null, gateway.nick, msg); + } + }, + + + onMsg: function(e, data){ + // Is this message from a user? + if(data.channel == gateway.nick){ + var destination = data.nick.toLowerCase(); + } else { + var destination = data.channel.toLowerCase(); + } + + if(!front.tabviewExists(destination)) front.tabviewAdd(destination); + front.tabviews[destination].addMsg(null, data.nick, data.msg); + }, + + onDebug: function(e, data){ + if(!front.tabviewExists('kiwi_debug')) front.tabviewAdd('kiwi_debug'); + front.tabviews['kiwi_debug'].addMsg(null, ' ', data.msg); + }, + + onAction: function(e, data){ + // Is this message from a user? + if(data.channel == gateway.nick){ + var destination = data.nick; + } else { + var destination = data.channel; + } + + if(!front.tabviewExists(destination)) front.tabviewAdd(destination); + front.tabviews[destination.toLowerCase()].addMsg(null, ' ', '* '+data.nick+' '+data.msg, 'action', 'color:#555;'); + }, + + onTopic: function(e, data){ + if(front.tabviewExists(data.channel)){ + front.tabviews[data.channel.toLowerCase()].changeTopic(data.topic); + } + }, + + onNotice: function(e, data){ + var nick = (data.nick=="") ? "" : '['+data.nick+']'; + if(data.channel != undefined){ + //alert('notice for '+data.channel); + if(front.tabviewExists(data.channel)){ + front.tabviews[data.channel.toLowerCase()].addMsg(null, nick, data.msg, 'notice'); + } + } else { + //alert('direct notice'); + front.tabviews['server'].addMsg(null, nick, data.msg, 'notice'); + } + }, + onConnect: function(e, data){ + if(data.connected){ + front.tabviews['server'].addMsg(null, ' ', '=== Connected OK :)', 'status'); + if(typeof init_data.channel == "string"){ + front.joinChannel(init_data.channel); + } + } else { + front.tabviews['server'].addMsg(null, ' ', '=== Failed to connect :(', 'status'); + } + }, + onOptions: function(e, data){ + if(typeof gateway.network_name == "string" && gateway.network_name != ""){ + front.tabviews['server'].tab.text(gateway.network_name); + } + }, + onMOTD: function(e, data){ + front.tabviews['server'].addMsg(null, data.server, data.msg, 'motd'); + }, + onWhois: function(e, data){ + front.cur_channel.addMsg(null, data.nick, data.msg, 'whois'); + }, + onUserList: function(e, data){ + if(front.tabviews[data.channel.toLowerCase()] == undefined) return; + + var ul = front.tabviews[data.channel.toLowerCase()].userlist; + + if(!document.userlist_updating){ + document.userlist_updating = true; + ul.empty(); + } + + $.each(data.users, function(i,item){ + var nick = i; //i.match(/^.+!/g); + var mode = item; + $('
            • '+mode+nick+'
            • ').appendTo(ul); + }); + + front.tabviews[data.channel.toLowerCase()].userlistSort(); + }, + onUserListEnd: function(e, data){ + document.userlist_updating = false; + }, + + onJoin: function(e, data){ + if(!front.tabviewExists(data.channel)){ + front.tabviewAdd(data.channel.toLowerCase()); + } + + if(data.nick == gateway.nick) return; // Not needed as it's already in nicklist + front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '--> '+data.nick+' has joined', 'action', 'color:#009900;'); + $('
            • '+data.nick+'
            • ').appendTo(front.tabviews[data.channel.toLowerCase()].userlist); + front.tabviews[data.channel.toLowerCase()].userlistSort(); + }, + onPart: function(e, data){ + if(front.tabviewExists(data.channel)){ + // If this is us, close the tabview + if(data.nick == gateway.nick){ + front.tabviews[data.channel.toLowerCase()].close(); + front.tabviews['server'].show(); + return; + } + + front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '<-- '+data.nick+' has left ('+data.message+')', 'action', 'color:#990000;'); + front.tabviews[data.channel.toLowerCase()].userlist.children().each(function(){ + if($(this).text() == data.nick){ + $(this).remove(); + } + }); + } + }, + onKick: function(e, data){ + if(front.tabviewExists(data.channel)){ + // If this is us, close the tabview + if(data.kicked == gateway.nick){ + front.tabviews[data.channel.toLowerCase()].close(); + return; + } + + front.tabviews[data.channel.toLowerCase()].addMsg(null, ' ', '<-- '+data.kicked+' kicked by '+data.nick+'('+data.message+')', 'action', 'color:#990000;'); + front.tabviews[data.channel.toLowerCase()].userlist.children().each(function(){ + if($(this).text() == data.nick){ + $(this).remove(); + } + }); + } + }, + onNick: function(e, data){ + if(data.nick == gateway.nick){ + gateway.nick = data.newnick; + front.doLayout(); + } + + $.each(front.tabviews, function(i,item){ + $.each(front.tabviews, function(i,item){ + item.changeNick(data.newnick, data.nick); + }); + }); + }, + onQuit: function(e, data){ + $.each(front.tabviews, function(i,item){ + $.each(front.tabviews, function(i,item){ + item.userlist.children().each(function(){ + if($(this).text() == data.nick){ + $(this).remove(); + item.addMsg(null, ' ', '<-- '+data.nick+' has quit ('+data.message+')', 'action', 'color:#990000;'); + } + }); + }); + }); + }, + onChannelRedirect: function(e, data){ + front.tabviews[data.from.toLowerCase()].close(); + front.tabviewAdd(data.to.toLowerCase()); + front.tabviews[data.to.toLowerCase()].addMsg(null, ' ', '=== Redirected from '+data.from, 'action'); + }, + + registerKeys: function(){ + $('#kiwi_msginput').bind('keydown', function(e){ + //$('input').keypress(function(e){ + switch(e.which){ + case 27: // escape + return false; + break; + case 13: // return + var msg = $('#kiwi_msginput').val(); + msg = msg.trim(); + + front.buffer.push(msg); + front.buffer_pos = front.buffer.length; + + front.run(msg); + $('#kiwi_msginput').val(''); + + break; + + case 38: // up + if(front.buffer_pos > 0){ + front.buffer_pos--; + $('#kiwi_msginput').val(front.buffer[front.buffer_pos]); + } + break; + case 40: // down + if(front.buffer_pos < front.buffer.length){ + front.buffer_pos++; + $('#kiwi_msginput').val(front.buffer[front.buffer_pos]); + } + break; + + case 9: // tab + // Get possible autocompletions + var data = []; + front.cur_channel.userlist.children().each(function(){ + nick = front.nickStripPrefix($('a.nick', this).text()); + data.push(nick); + }); + + // Do the autocomplete + if (this.value.length == this.selectionStart && this.value.length == this.selectionEnd) { + var candidates = []; + + var word_pos = this.value.lastIndexOf(' '); + var word = ""; + if(word_pos == -1){ + word = this.value; + } else { + word = this.value.substr(word_pos); + } + word = word.trim(); + + // filter data to find only strings that start with existing value + for (var i=0; i < data.length; i++) { + if (data[i].indexOf(word) == 0 && data[i].length > word.length) + candidates.push(data[i]); + } + + if (candidates.length > 0) { + // some candidates for autocompletion are found + this.value = this.value.substring(0, word_pos) + ' ' + candidates[0]+': '; + this.selectionStart = this.value.length; + } + } + return false; + + break; + } + }); + + + $('#kiwi .control .msginput .nick').click(function(){ + var html = '
              \ + Your new nick:
              \ +
              \ +
              \ + Cancel\ +
              \ +
              '; + $('#kiwi').append(html); + $('#kiwi .form_newnick').submit(function(){ + front.run('/NICK '+$('#kiwi .txtnewnick').val()); + $('#kiwi .newnick').remove(); + return false; + }); + $('#kiwi .cancelnewnick').click(function(){ + $('#kiwi .newnick').remove(); + }); + + $('#kiwi .txtnewnick').focus(); + + }); + + + + + + $('#kiwi .plugins .load_plugin_file').click(function(){ + if(typeof front.boxes.plugins != "undefined") return; + + var html = '
              \ +

              Kiwi plugins

              \ +

              \ + \ + \ +

              \ +
              \ +
              \ + Plugin file URL:
              \ +
              \ +
              \ + Cancel\ +
              \ +
              '; + front.boxes.plugins = new box("plugin_file"); + front.boxes.plugins.content.html(html); + front.boxes.plugins.box.css('top', -(front.boxes.plugins.height+40)); + + // Populate the plugin list.. + var lst = $('#plugin_list'); + lst.find('option').remove(); + for(var i in plugins.privmsg){ + var txt = plugins.privmsg[i].name; + lst.append(''); + } + + // Event bindings + $('#kiwi .plugin_file').submit(function(){ + $.getJSON($('.txtpluginfile').val(), function(data) { + var plg = {}; + plg.name = data.name; + eval("plg.onprivmsg = "+data.onprivmsg); + eval("plg.onload = "+data.onload); + eval("plg.onunload = "+data.onunload); + plugins.privmsg.push(plg); + + if(plg.onload instanceof Function) plg.onload(); + }); + return false; + }); + $('#kiwi .cancelpluginfile').click(function(){ + front.boxes.plugins.destroy(); + }); + + $('#kiwi #plugins_list_unload').click(function(){ + var selected_plugin = $('#plugin_list').val(); + console.log("removing plugin: "+selected_plugin); + for(var i in plugins.privmsg){ + if(plugins.privmsg[i].name == selected_plugin){ + if(plugins.privmsg[i].onunload instanceof Function) + plugins.privmsg[i].onunload(); + + delete plugins.privmsg[i]; + } + } + }); + + $('#kiwi .txtpluginfile').focus(); + + }); + + $('#kiwi .plugins .reload_css').click(function(){ + var links = document.getElementsByTagName("link"); + for (var i=0; i < links.length; i++){ + if(links[i].rel === "stylesheet"){ + if(links[i].href.indexOf("?")===-1){ + links[i].href += "?"; + } + links[i].href += "x"; + } + } + }); + + + $('#kiwi .about .about_close').click(function(){ + $('#kiwi .about').css('display', 'none'); + }); + + + $('#kiwi .poweredby').click(function(){ + $('#kiwi .about').css('display', 'block'); + }); + + }, + + + tabviewExists: function(name){ + return !(front.tabviews[name.toLowerCase()] == undefined); + }, + + tabviewAdd: function(v_name){ + if(v_name.charAt(0) == gateway.channel_prefix){ + var re = new RegExp(gateway.channel_prefix,"g"); + var htmlsafe_name = v_name.replace(re, 'pre'); + htmlsafe_name = "chan_"+htmlsafe_name; + } else { + var htmlsafe_name = 'query_'+v_name; + } + + var tmp_divname = 'kiwi_window_'+htmlsafe_name; + var tmp_userlistname = 'kiwi_userlist_'+htmlsafe_name; + var tmp_tabname = 'kiwi_tab_'+htmlsafe_name + + $('#kiwi').append('
              '); + $('#kiwi .userlist').append('
                '); + $('#kiwi .windowlist ul').append('
              • '+v_name+'
              • '); + //$('#kiwi .windowlist ul .window_'+v_name).click(function(){ front.windowShow(v_name); }); + //front.windowShow(v_name); + + front.tabviews[v_name.toLowerCase()] = new tabview(); + front.tabviews[v_name.toLowerCase()].name = v_name; + front.tabviews[v_name.toLowerCase()].div = $('#'+tmp_divname); + front.tabviews[v_name.toLowerCase()].userlist = $('#'+tmp_userlistname); + front.tabviews[v_name.toLowerCase()].tab = $('#'+tmp_tabname); + front.tabviews[v_name.toLowerCase()].show(); + + if(typeof registerTouches == "function"){ + //alert("Registering touch interface"); + //registerTouches($('#'+tmp_divname)); + registerTouches(document.getElementById(tmp_divname)); + } + /* + front.tabviews[v_name.toLowerCase()].userlist.click(function(){ + alert($(this).attr('id')); + }); + */ + + front.doLayoutSize(); + }, + + + userClick: function(item){ + // Remove any existing userboxes + $('#kiwi .userbox').remove(); + + var li = $(item).parent(); + var html = '
                \ + \ + Message\ + Info\ +
                '; + li.append(html); + + $('#kiwi .userbox .userbox_query').click(function(ev){ + var nick = $('#kiwi .userbox_nick').val(); + front.run('/query '+nick); + }); + + $('#kiwi .userbox .userbox_whois').click(function(ev){ + var nick = $('#kiwi .userbox_nick').val(); + front.run('/whois '+nick); + }); + }, + + + sync: function(){ + gateway.sync(); + }, + + onSync: function(e, data){ + // Set any settings + if(data.nick != undefined) gateway.nick = data.nick; + + // Add the tabviews + if(data.tabviews != undefined){ + $.each(data.tabviews, function(i,tab){ + if(!front.tabviewExists(tab.name)){ + front.tabviewAdd(gateway.channel_prefix+tab.name); + + if(tab.userlist != undefined) + front.onUserList({'channel':gateway.channel_prefix+tab.name, 'users':tab.userlist}); + } + }); + } + + front.doLayout(); + }, + + + setTopicText: function(new_topic){ + $('#kiwi .cur_topic').text(new_topic); + }, + + + + + + + + nickStripPrefix: function(nick){ + var tmp = nick; + + prefix = tmp.charAt(0); + if(typeof gateway.user_prefixes[prefix] != "undefined") tmp = tmp.substring(1); + + return tmp; + }, + + nickGetPrefix: function(nick){ + var tmp = nick; + + prefix = tmp.charAt(0); + if(typeof gateway.user_prefixes[prefix] == "undefined"){ + prefix = ""; + } + + return prefix; + }, + + isChannel: function(name){ + prefix = name.charAt(0); + if(gateway.channel_prefix.indexOf(prefix) > -1){ + is_chan = true; + } else { + is_chan = false; + } + + return is_chan; + }, + + tabviewsNext: function(){ + var wl = $('#kiwi .windowlist ul'); + var next_left = parseInt(wl.css('text-indent').replace('px', ''))+170; + wl.css('text-indent', next_left); + }, + + tabviewsPrevious: function(){ + var wl = $('#kiwi .windowlist ul'); + var next_left = parseInt(wl.css('text-indent').replace('px', ''))-170; + wl.css('text-indent', next_left); + } +} + + + + + + + + + + + + + + + + + +/* + * + * TABVIEWS + * + */ + + +var tabview = function(){} +tabview.prototype.name = null; +tabview.prototype.div = null; +tabview.prototype.userlist = null; +tabview.prototype.tab = null; +tabview.prototype.topic = ""; + +tabview.prototype.show = function(){ + $('#kiwi .messages').removeClass("active"); + $('#kiwi .userlist ul').removeClass("active"); + $('#kiwi .windowlist ul li').removeClass("active"); + + // Activate this tab! + this.div.addClass('active'); + this.userlist.addClass('active'); + this.tab.addClass('active'); + + document.tmp = this.div; + // Add the part image to the tab + this.addPartImage(); + + this.clearHighlight(); + front.setTopicText(this.topic); + front.cur_channel = this; + + this.scrollBottom(); + $('#kiwi_msginput').focus(); +} + +tabview.prototype.close = function(){ + this.div.remove(); + this.userlist.remove(); + this.tab.remove(); + + front.tabviews['server'].show(); + delete front.tabviews[this.name.toLowerCase()]; +} + +tabview.prototype.addPartImage = function(){ + this.clearPartImage(); + + var del_html = ''; + this.tab.append(del_html); + + $('.tab_part', this.tab).click(function(){ + if(front.isChannel($(this).parent().text())){ + front.run("/part"); + } else { + // Make sure we don't close the server tab + if(front.cur_channel.name != "server") front.cur_channel.close(); + } + }); +} + +tabview.prototype.clearPartImage = function(){ + $('#kiwi .windowlist .tab_part').remove(); +} + +tabview.prototype.addMsg = function(time, nick, msg, type, style){ + //if(nick.charAt(0) != "[" && nick != ""){ + // var html_nick = $('
                ').text('<'+nick+'>').html(); + //} else { + var html_nick = $('
                ').text(nick).html(); + //} + + var self = this; + + var tmp = msg; + var plugin_ret = ''; + for(var i in plugins.privmsg){ + if ((plugins.privmsg[i].onprivmsg instanceof Function)) { + plugin_ret = ''; + try { + plugin_ret = plugins.privmsg[i].onprivmsg(tmp, this.name); + + // If this plugin has returned false, do not add this message + if(plugin_ret === false) return; + } catch (e){} + + // If we actually have a string from the plugin, use it + if(typeof plugin_ret == "string") tmp = plugin_ret; + } + } + msg = tmp; + + //var html_msg = $('
                ').text(msg).html()+' '; // Add the space so the styling always has at least 1 character to go from + if(time == null){ + var d = new Date(); + time = d.getHours().toString().lpad(2, "0") + ":" + d.getMinutes().toString().lpad(2, "0") + ":" + d.getSeconds().toString().lpad(2, "0"); + } + + // The CSS class (action, topic, notice, etc) + if(typeof type != "string") type = ''; + + // Make sure we don't have NaN or something + if(typeof msg != "string") msg = ''; + + // Text formatting + // bold + if(msg.indexOf(String.fromCharCode(2))){ + next = ''; + while(msg.indexOf(String.fromCharCode(2)) != -1){ + msg = msg.replace(String.fromCharCode(2), next); + next = (next=='') ? '' : ''; + } + if(next == '') msg =+ ''; + } + + // Wierd thing noticed by Dux0r on the irc.esper.net server + if(typeof msg != "string") msg = ''; + + // underline + if(msg.indexOf(String.fromCharCode(31))){ + next = ''; + while(msg.indexOf(String.fromCharCode(31)) != -1){ + msg = msg.replace(String.fromCharCode(31), next); + next = (next=='') ? '' : ''; + } + if(next == '') msg =+ ''; + } + + + var line_msg = '
                '+time+'
                '+html_nick+'
                '+msg+'
                '; + this.div.append(line_msg); + this.scrollBottom(); +} + +tabview.prototype.scrollBottom = function(){ + this.div.attr({ scrollTop: this.div.attr("scrollHeight") }); +} + +tabview.prototype.changeNick = function(newNick, oldNick){ + this.userlist.children().each(function(){ + var item = $('a.nick', this); + if(front.nickStripPrefix(item.text()) == oldNick){ + item.text(front.nickGetPrefix(item.text())+newNick); + document.temp_chan = 1; + } + }); + + if(typeof document.temp_chan != "undefined"){ + this.addMsg(null, ' ', '=== '+oldNick+' is now known as '+newNick, 'action'); + delete document.temp_chan; + this.userlistSort(); + } +} + +tabview.prototype.userlistSort = function(){ + var ul = this.userlist; + var listitems = ul.children('li').get(); + listitems.sort(function(a, b) { + var compA = $(a).text().toUpperCase(); + var compB = $(b).text().toUpperCase(); + + // Sort by prefixes first + for (var prefix in gateway.user_prefixes) { + mode = gateway.user_prefixes[prefix]; + + if(compA.charAt(0) == prefix && compB.charAt(0) == prefix){ + // Both have the same prefix, string compare time + return 0; + } + + if(compA.charAt(0) == prefix && compB.charAt(0) != prefix){ + return -1; + } + + if(compA.charAt(0) != prefix && compB.charAt(0) == prefix){ + return 1; + } + } + + // No prefixes, compare by string + return (compA < compB) ? -1 : (compA > compB) ? 1 : 0; + }) + $.each(listitems, function(idx, itm) { ul.append(itm); }); +} + +tabview.prototype.highlight = function(){ + this.tab.addClass('highlight'); +} +tabview.prototype.activity = function(){ + this.tab.addClass('activity'); +} +tabview.prototype.clearHighlight = function(){ + this.tab.removeClass('highlight'); + this.tab.removeClass('activity'); +} +tabview.prototype.changeTopic = function(new_topic){ + this.topic = new_topic; + this.addMsg(null, ' ', '=== Topic for '+this.name+' is: '+new_topic, 'topic'); + if(front.cur_channel.name == this.name) front.setTopicText(new_topic); +} + + + + + +var box = function(classname){ + this.id = randomString(10); + var tmp = $('
                '); + $('#kiwi').append(tmp); + this.box = $('#'+this.id); + this.content = $('#'+this.id+' .boxarea'); + + this.box.draggable({ stack: ".box" }); + this.content.click(function(){}); + //this.box.click(function(){ $(this)..css }); +} +box.prototype.create = function(name, classname){ + +} +box.prototype.id = null; +box.prototype.box = null; +box.prototype.content = null; +box.prototype.destroy = function(){ + this.box.remove(); + for (var name in front.boxes) if(front.boxes[name].id = this.id) delete front.boxes[name]; +} +box.prototype.height = function(){ return this.box.height(); } \ No newline at end of file diff --git a/js/front_backup.js b/js/front_backup.js new file mode 100644 index 0000000..920359d --- /dev/null +++ b/js/front_backup.js @@ -0,0 +1,143 @@ +var front = { + cur_channel: '', + windows: {}, + + + init: function(){ + front.registerKeys(); + + gateway.nick = 'kiwiclone'; + gateway.session_id = null; + gateway.onMsg = front.onMsg; + gateway.onNotice = front.onNotice; + gateway.onMOTD = front.onMOTD; + gateway.onConnect = front.onConnect; + gateway.onUserList = front.onUserList; + + front.doLayout(); + front.windowAdd('server'); + gateway.poll(); + }, + + doLayout: function(){ + $('#kiwi .msginput .nick a').text(gateway.nick); + }, + + + onMsg: function(data){ + front.addMsg(null, data.nick,data.msg,data.channel); + }, + onNotice: function(data){ + front.addMsg(null, data.nick, '--> '+data.msg); + }, + onConnect: function(data){ + if(data.connected){ + front.addMsg(null, ' ', '--> Connected to '+data.host); + } else { + front.addMsg(null, ' ', '--> Failed to connect to '+data.host); + } + }, + onMOTD: function(data){ + front.addMsg(null, data.server, data.msg); + }, + onUserList: function(data){ + $.each(data.users, function(i,item){ + $('
              • '+i+'
              • ').appendTo('#kiwi .userlist ul'); + }); + }, + + registerKeys: function(){ + $('input').keypress(function(e){ + if(e.which == 13){ + var msg = $('#kiwi_msginput').val(); + if(msg.substring(0,1) == '/'){ + var parts = msg.split(' '); + switch(parts[0]){ + case '/join': + if(front.windows[parts[1]] == undefined){ + gateway.join(parts[1].replace('#', '')); + front.windowAdd(parts[1]); + } else { + front.windowShow(parts[1]); + } + break; + + case '/connect': + if(parts[1] == undefined){ + alert('Usage: /connect servername [port]'); + break; + } + + if(parts[2] == undefined) parts[2] = 6667; + front.addMsg(null, ' ', '--> Connecting to '+parts[1]+'...'); + gateway.connect(parts[1], parts[2], 0); + break; + + default: + front.addMsg(null, ' ', '--> Invalid command: '+parts[0].substring(1)); + } + + } else { + gateway.msg(front.cur_channel, msg); + var d = new Date(); + var d = d.getHours() + ":" + d.getMinutes(); + front.addMsg(d, gateway.nick, msg); + } + $('#kiwi_msginput').val(''); + } + }); + }, + + + + addMsg: function(time, nick, msg, channel){ + var html_nick = $('
                ').text(nick).html(); + var html_msg = $('
                ').text(msg).html()+' '; // Add the space so the styling always has at least 1 character to go from + if(time == null){ + var d = new Date(); + time = d.getHours() + ":" + d.getMinutes(); + } + + var msg = '
                '+time+'
                '+html_nick+'
                '+html_msg+'
                '; + if(channel == undefined){ + var messages = $("#kiwi_window_server"); + } else { + var messages = $("#kiwi_window_chan_"+channel.replace('#', '')); + } + messages.append(msg); + messages.attr({ scrollTop: messages.attr("scrollHeight") }); + }, + + + + windowExists: function(name){ + return !(front.windows[name] == undefined); + }, + windowAdd: function(v_name){ + var tmp_divname = 'kiwi_window_'+v_name.replace('#', 'chan_'); + front.windows[v_name] = { name: v_name, div_id: tmp_divname }; + $('#kiwi').append('
                '); + $('#kiwi .windowlist ul').append('
              • '+v_name+'
              • '); + //$('#kiwi .windowlist ul .window_'+v_name).click(function(){ front.windowShow(v_name); }); + front.windowShow(v_name); + + /* + var t = ""; + for (key in front.windows) + t += "Element value is " + front.windows[key].div_id + "\n"; + + alert(t); + */ + }, + windowDiv: function(name){ + if(!front.windowExists(name)) return false; + return $('#'+front.windows[name].div_id); + }, + windowShow: function(name){ + if(!front.windowExists(name)) return false; + $('#kiwi .messages').removeClass("active"); + var tmp = front.windowDiv(name); + tmp.addClass('active'); + front.cur_channel = name; + } +} \ No newline at end of file diff --git a/js/gateway.js b/js/gateway.js new file mode 100644 index 0000000..610d6e5 --- /dev/null +++ b/js/gateway.js @@ -0,0 +1,274 @@ +var gateway = { + + revision: 16, + + nick: 'kiwi', + session_id: null, + syncing: false, + channel_prefix: '#', + network_name: '', + user_prefixes: [], + + + connect: function(s_host, s_port, s_ssl, callback){ + var data = { + method: 'connect', + args: { + server: s_host, + port: s_port, + ssl: s_ssl, + nick: this.nick + } + }; + + //$.post('poll.php', data, callback, 'json'); + gateway.sendData(data, callback); + }, + + + + + start: function(){ + if(typeof WebSocket != "undefined"){ + //alert("Starting websocket support.."); + gateway.socket(); + } else { + //alert("Starting polling support.."); + gateway.poll(); + } + }, + + + poll: function(){ + if(this.session_id == null){ + gateway.sendData = function(data, callback){ + data = { + sid: this.session_id, + data: $.toJSON(data) + } + $.post('poll.php', data, callback, 'json'); + } + + $.post("poll.php", {}, + function(data, status){ + if(data.session_id != undefined){ + gateway.session_id = data.session_id; + gateway.poll(); + } else { + if(typeof data.error == "string"){ + alert(data.error); + } else { + alert('Woops, something went wrong! Unsure what it is though.. :('); + } + } + }, 'json' + ); + return; + } + $.post("poll.php", {sid: this.session_id}, + function(data, status){ + $.each(data, function(i,item){ + gateway.parse(item); + }); + gateway.poll(gateway.session_id); + }, 'json'); + }, + + + socket: function(){ + gateway.buffer = ""; + + gateway.conn = new WebSocket("ws://p1.kiwiirc.com:7777/client"); + + gateway.sendData = function(data, callback){ + gateway.conn.send($.toJSON(data)); + if(typeof callback == "function") callback(); + } + + //gateway.conn.onopen = function(evt) { alert("Conn opened"); } + gateway.conn.onmessage = function(evt) { + gateway.buffer += evt.data; + + if(gateway.buffer.indexOf("\n") > 0){ + var msgs = gateway.buffer.split("\n"); + for(var i=0; i 20){ + var n = obj.attr("scrollTop")+ydiff; + //alert("up (xdiff="+xdiff+" ydiff="+ydiff+" scrollTop="+obj.attr("scrollTop")+") "+n); + obj.attr("scrollTop", n); + } + } + obj.ontouchend = function(e) { + e.preventDefault(); + + var xdiff = document.touchx_start - document.touchx_cur + var ydiff = document.touchy_start - document.touchy_cur + //alert('x='+xdiff+' y='+ydiff); + //alert('Start: x='+document.touchx+' y='+document.touchy+"\n"+'End: x='+e.pageX+' y='+e.pageY); + + if(xdiff < -150 && (ydiff > -250 && ydiff < 250)){ + //alert("next window (xdiff="+xdiff+" ydiff="+ydiff+")"); + front.tabviewsNext(); + } else if(xdiff > 150 && (ydiff > -250 && ydiff < 250)){ + //alert("previous window (xdiff="+xdiff+" ydiff="+ydiff+")"); + front.tabviewsPrevious(); + } + } +//}); +} \ No newline at end of file diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..f36703f --- /dev/null +++ b/js/util.js @@ -0,0 +1,105 @@ +function randomString(string_length) { + var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz"; + var randomstring = ''; + for (var i=0; i').text(inp).html(); + } + }, + + { + name: "activity", + onprivmsg: function(inp, tabview){ + if(front.cur_channel.name.toLowerCase() != front.tabviews[tabview.toLowerCase()].name){ + front.tabviews[tabview].activity(); + } + } + }, + + { + name: "highlight", + onprivmsg: function(inp, tabview){ + if(inp.toLowerCase().indexOf(gateway.nick.toLowerCase()) > -1){ + if(front.cur_channel.name.toLowerCase() != front.tabviews[tabview.toLowerCase()].name){ + front.tabviews[tabview].highlight(); + } + if(front.isChannel(front.tabviews[tabview].name)) + inp = ''+inp+''; + } + + if( + !front.isChannel(front.tabviews[tabview].name) && front.tabviews[tabview].name != "server" + && front.cur_channel.name.toLowerCase() != front.tabviews[tabview.toLowerCase()].name + ){ + front.tabviews[tabview].highlight(); + } + return inp; + } + }, + + /* + { + name: "images", + onprivmsg: function(text){ + if( !text ) return text; + //alert("-"+text+"-"); + text = text.replace(/^((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?(\.jpg|\.jpeg|\.gif|\.bmp|\.png)$/gi,function(url){ + var img = ''; + return ''+ img +''; + }); + + return text; + } + }, + + */ + + + { + //Following method taken from: http://snipplr.com/view/13533/convert-text-urls-into-links/ + name: "linkify_plain", + onprivmsg: function(text){ + if( !text ) return text; + + text = text.replace(/((https?\:\/\/|ftp\:\/\/)|(www\.))(\S+)(\w{2,4})(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/gi,function(url){ + nice = url; + if(url.match('^https?:\/\/')){ + //nice = nice.replace(/^https?:\/\//i,'') + }else{ + url = 'http://'+url; + } + + return ''+ nice +''; + }); + + return text; + } + } +]; \ No newline at end of file diff --git a/layout.html b/layout.html new file mode 100644 index 0000000..33dee7e --- /dev/null +++ b/layout.html @@ -0,0 +1,107 @@ + + + + +Kiwi IRC + + + + + + + + +
                +

                Kiwi IRC

                +
                +
                +
                +
                  +
                • +
                • +
                + Connect » +
                + +
                + More +
                +
                  +
                • +
                • +
                • +
                • +
                + Connect » +
                +
                +
                +
                +
                + + + diff --git a/plugins.js b/plugins.js new file mode 100644 index 0000000..8d50ee0 --- /dev/null +++ b/plugins.js @@ -0,0 +1,3 @@ +plugins.privmsg.lol = function(message, tab_name){ + return message+' .. lol'; +} \ No newline at end of file diff --git a/plugins/console.php b/plugins/console.php new file mode 100644 index 0000000..90c71fd --- /dev/null +++ b/plugins/console.php @@ -0,0 +1,13 @@ +name = "Console Logger"; + + $p->onprivmsg = << "+tabview+": "+inp); +} +JS; + + echo $p->export(); \ No newline at end of file diff --git a/plugins/dev.php b/plugins/dev.php new file mode 100644 index 0000000..f8352d1 --- /dev/null +++ b/plugins/dev.php @@ -0,0 +1,18 @@ +name = "Dev Tools"; + + $p->onload = <<Dev Tools'); + $('#dev_tools_open').click(function(){ alert("Opening Dev Tools window"); }); +} +JS; + + $p->onunload = <<export(); \ No newline at end of file diff --git a/plugins/plugin.php b/plugins/plugin.php new file mode 100644 index 0000000..0e8062c --- /dev/null +++ b/plugins/plugin.php @@ -0,0 +1,18 @@ +$this->name, + 'onprivmsg'=>$this->onprivmsg, + 'onload'=>$this->onload, + 'onunload'=>$this->onunload + )); + } + } \ No newline at end of file diff --git a/poll.php b/poll.php new file mode 100644 index 0000000..91a6a5f --- /dev/null +++ b/poll.php @@ -0,0 +1,110 @@ +'session_error'))); + } + + // Session create OK, yay + die(gen_response(array('session_id'=>$new_session))); + } + + + + ############################################ + ## Existing sessions + ############################################ + + // Quit here if a session hasn't been specified + if(!isset($_POST['sid']) || empty($_POST['sid'])){ + die(gen_response(array('error'=>'session_not_set'))); + } + + $session_id = $_POST['sid']; + + // Make sure the session exists + if(!SESSIONS::exists($session_id)){ + die(gen_response(array('error'=>'no_session'))); + } + + // Connect to the IRC session + $ses = SESSIONS::open($session_id); + if(!$ses){ + die(gen_response(array('error'=>'session_error'))); + } + + if(!isset($_POST['data'])){ + // Read any commands to be sent to the web client + $data = array(); + + /* This was used for blocking the first call which caused a wait of timeout seconds if the user quits + // Make this read block + $tmp = json_decode(trim(SESSIONS::read($ses, true)),1); + if($tmp != null) $data[] = $tmp; + */ + + // Unblocked reads just incase only 1 message is actually available + $start_time = time(); + while(time() - $start_time < $config['max_time_per_poll'] && count($data) == 0 && !connection_aborted()){ + for($i=0; $i<$config['messages_per_poll']; $i++){ + if(connection_aborted()){ + deb("Connection aborted"); + break; + } + deb("Polling.."); + $tmp = json_decode(trim(SESSIONS::read($ses, false)),1); + if($tmp != null){ + $data[] = $tmp; + } else { + break; + } + + echo " "; + flush(); + } + + if(count($data) == 0) sleep(1); + } + deb("Polled"); + + if(!empty($data)){ + echo gen_response($data); + } else { + echo gen_response(array()); + } + } else { + fwrite($ses, $_POST['data']); + } + + // We're done here, close the session connection + SESSIONS::close($ses); + + + + + + + + + + ############################################ + ## Functions + ############################################ + + function gen_response($data){ + return json_encode($data); + } diff --git a/test.html b/test.html new file mode 100644 index 0000000..9a55f7a --- /dev/null +++ b/test.html @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/ws.html b/ws.html new file mode 100644 index 0000000..a4a38ad --- /dev/null +++ b/ws.html @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/ws.php b/ws.php new file mode 100644 index 0000000..315c137 --- /dev/null +++ b/ws.php @@ -0,0 +1,324 @@ +ircsocket + $pairs = array(); + + // Stores all sockets + $soks = array(); + + // Handshaken sockets + $handshakes = array(); + + + + require(dirname(__FILE__).'/config.php'); + require(dirname(__FILE__).'/common.php'); + require(dirname(__FILE__).'/class_session.php'); + + + error_reporting(E_ALL); + set_time_limit(0); + ob_implicit_flush(); + + $server = WebSocket($config['websocket']['bind_addr'], $config['websocket']['bind_port']); + $soks[(int)$server] = $server; + $last_dump = 0; + while(1){ + echo time()." - $last_dump (".(time() - $last_dump).")\n"; + if(time() - $last_dump >= 3){ + $last_dump = time(); + echo "\nPairs: "; + var_dump($pairs); + + echo "\nSoks: "; + var_dump($soks); + } + + $changed = $soks; + stream_select($changed, $write=NULL, $except=NULL, NULL); + + foreach($changed as $socket){ + // New connection? + if($socket == $server){ + $client = stream_socket_accept($server); + if($client<0){ + console("socket_accept() failed"); continue; + } else { + connect($client); + } + } else { + + $buffer = fread($socket, 2048); + if($buffer === false || $buffer == ''){ + // Disconnected + disconnect($socket); + } else { + //$buffer = substr($buffer, 0, strlen($buffer)); + //console("INCOMING:\n".$buffer."########\n"); + if(isset($handshakes[(int)$socket])){ + // websocket upgrade + dohandshake($socket, $buffer); + } else { + // Data transfering.. + transfer($socket, $buffer); + } + } + + } + } + } + + + + + function WebSocket($address,$port){ + //$master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed"); + $master = stream_socket_server("tcp://$address:$port") or die("socket_create() failed"); + //socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed"); + //socket_bind($master, $address, $port) or die("socket_bind() failed"); + //socket_listen($master,20) or die("socket_listen() failed"); + echo "Server Started : ".date('Y-m-d H:i:s')."\n"; + echo "Master socket : ".$master."\n"; + echo "Listening on : ".$address." port ".$port."\n\n"; + return $master; + } + + + + + + function connect($socket){ + global $soks, $pairs, $handshakes; + + //$session_id = SESSIONS::create(); + //if(!$session_id) return false; + + //$pairs[$socket]= SESSION::open($session_id); + + $soks[(int)$socket] = $socket; + $handshakes[(int)$socket] = false; + //array_push($soks, $pairs[$socket]); + + console($socket." connection.."); + } + + function disconnect($sok){ + global $soks, $pairs; + console("disconnected?\n"); + + $pair = findPair($sok); + if($pair === false) return false; + foreach($pair as $websocket => $local_con){ + @fclose($soks[$websocket]); + unset($soks[$websocket]); + + @fclose($soks[$local_con]); + unset($soks[$local_con]); + + unset($pairs[$websocket]); + } + + console($sok." DISCONNECTED!"); + } + + function transfer($sok, $buffer){ + global $soks, $pairs; + console("Transfering data?\n"); + + $pair = findPair($sok); + if($pair === false) return false; + + console("Transfering ".strlen($buffer)." bytes.. '".$buffer."'"); + //$buffer = wrap($buffer); + foreach($pair as $websocket => $local_con){ + if($sok == $soks[$websocket]){ + // From websocket.. + fwrite($soks[$local_con], unwrap($buffer)); + break; + } elseif($sok == $soks[$local_con]){ + // From irc client + fwrite($soks[$websocket], chr(0).$buffer.chr(255)); + break; + } + } + } + + function findPair($socket){ + global $soks, $pairs; + console("Finding pair: ".(int)$socket."\n"); + + // If it's a websocket, then this will find it.. + if(isset($pairs[(int)$socket])) + return array((int)$socket=>$pairs[(int)$socket]); + + // If it's an irc client socket, then we will find it when flipped.. + $flipped = array_flip($pairs); + if(isset($flipped[(int)$socket])) + return array($flipped[(int)$socket] => (int)$socket); + + return false; + } + + function dohandshake($sok, $buffer){ + global $handshakes, $soks, $pairs; + console("\nRequesting handshake..."); + + console("Handshaking..."); + /* + list($resource, $host, $origin) = getheaders($buffer); + $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . + "Upgrade: WebSocket\r\n" . + "Connection: Upgrade\r\n" . + "WebSocket-Origin: " . $origin . "\r\n" . + "WebSocket-Location: ws://" . $host . $resource . "\r\n" . + "\r\n"; + */ + if(!strpos($buffer, 'WebSocket-Key1:')){ + $upgrade = (string)new WebSocket75($buffer); + } else { + $upgrade = (string)new WebSocket76($buffer); + } + + fwrite($sok, $upgrade.chr(0)); + + // Done the handshake so remove it from the handshaking array + unset($handshakes[(int)$sok]); + + console("Done handshaking..."); + + //socket_getsockname($sok, $sok_name); + $sok_name = stream_socket_get_name($sok, true); + $session_id = SESSIONS::create($sok_name); + if(!$session_id) return false; + $irc_client_sok = SESSIONS::open($session_id); + + $soks[(int)$irc_client_sok] = $irc_client_sok; + $pairs[(int)$sok] = (int)$irc_client_sok; + + fwrite($irc_client_sok, json_encode(array('method'=>'read'))); + + console($sok." CONNECTED!"); + return true; + } + + + + + + class WebSocket75 { + private $__value__; + + public function __toString() { + return $this->__value__; + } + + public function __construct($buffer){ + list($resource, $host, $origin) = $this->getheaders($buffer); + $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" . + "Upgrade: WebSocket\r\n" . + "Connection: Upgrade\r\n" . + "WebSocket-Origin: " . $origin . "\r\n" . + "WebSocket-Location: ws://" . $host . $resource . "\r\n" . + "\r\n"; + + $this->__value__ = $upgrade; + } + + private function getheaders($req){ + $r=$h=$o=null; + if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; } + if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; } + if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; } + return array($r,$h,$o); + } + } + + + class WebSocket76 { + + /*! Easy way to handshake a WebSocket via draft-ietf-hybi-thewebsocketprotocol-00 + * @link http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt + * @author Andrea Giammarchi + * @blog webreflection.blogspot.com + * @date 4th June 2010 + * @example + * // via function call ... + * $handshake = WebSocketHandshake($buffer); + * // ... or via class + * $handshake = (string)new WebSocketHandshake($buffer); + * + * socket_write($socket, $handshake, strlen($handshake)); + */ + + private $__value__; + + public function __construct($buffer) { + $resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null; + preg_match('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1]; + preg_match("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1]; + preg_match("#Sec-WebSocket-Key1: (.*?)\r\n#", $buffer, $match) && $key1 = $match[1]; + preg_match("#Sec-WebSocket-Key2: (.*?)\r\n#", $buffer, $match) && $key2 = $match[1]; + preg_match("#Sec-WebSocket-Protocol: (.*?)\r\n#", $buffer, $match) && $protocol = $match[1]; + preg_match("#Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1]; + preg_match("#\r\n(.*?)\$#", $buffer, $match) && $code = $match[1]; + $this->__value__ = + "HTTP/1.1 101 WebSocket Protocol Handshake\r\n". + "Upgrade: WebSocket\r\n". + "Connection: Upgrade\r\n". + "Sec-WebSocket-Origin: {$origin}\r\n". + "Sec-WebSocket-Location: ws://{$host}{$resource}\r\n". + ($protocol ? "Sec-WebSocket-Protocol: {$protocol}\r\n" : ""). + "\r\n". + $this->_createHandshakeThingy($key1, $key2, $code) + ; + } + + public function __toString() { + return $this->__value__; + } + + private function _doStuffToObtainAnInt32($key) { + return preg_match_all('#[0-9]#', $key, $number) && preg_match_all('# #', $key, $space) ? + implode('', $number[0]) / count($space[0]) : + '' + ; + } + + private function _createHandshakeThingy($key1, $key2, $code) { + return md5( + pack('N', $this->_doStuffToObtainAnInt32($key1)). + pack('N', $this->_doStuffToObtainAnInt32($key2)). + $code, + true + ); + } + } + + +function say($msg=""){ echo $msg."\n"; } +function wrap($msg=""){ return chr(0).$msg.chr(255); } +function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); } +function console($msg=""){ global $debug; if($debug){ echo time().' '.trim($msg)."\n"; } }