--- /dev/null
+<?php\r
+\r
+class SocketStream {\r
+ var $stream;\r
+ function __construct($host = null, $port = null, $blocking = true) {\r
+ if(!$host && !$port)\r
+ return;\r
+ $scheme = 'tcp';\r
+ if($host{0}=='!')\r
+ {\r
+ $scheme = 'ssl';\r
+ $port = 6697;\r
+ $host = substr($host, 1);\r
+ } \r
+ $this->stream = @stream_socket_client("{$scheme}://$host:$port", $errno, $errstr, 4);\r
+ if(!$this->stream)\r
+ {\r
+ debug("Error creating socket: $host $errstr\n");\r
+ return;\r
+ }\r
+// socket_set_timeout($this->stream, 30);\r
+ stream_set_blocking($this->stream, $blocking);\r
+ }\r
+ function __destruct()\r
+ {\r
+ if($this->stream)\r
+ {\r
+ fclose($this->stream);\r
+ $this->stream = null;\r
+ }\r
+ }\r
+ function SetBlocking($blocking = true)\r
+ {\r
+ if(!$this->stream)\r
+ return false;\r
+ stream_set_blocking($this->stream, $blocking);\r
+ }\r
+ function SetTimeout($timeout, $ms=0)\r
+ {\r
+ if(!$this->stream)\r
+ return false;\r
+ stream_set_timeout($this->stream, $timeout, $ms);\r
+ }\r
+ function Eof()\r
+ {\r
+ if(!$this->stream)\r
+ return true;\r
+ $x = stream_get_meta_data($this->stream);\r
+ if($x['eof'] && $x['unread_bytes']==0 && feof($this->stream))\r
+ return true;\r
+ return false;\r
+ }\r
+ function Read($length = 512, $type = PHP_BINARY_READ)\r
+ {\r
+ if(!$this->stream) return false;\r
+ if($type == PHP_NORMAL_READ){\r
+ $ret = fgets($this->stream, $length);\r
+ } elseif($type == PHP_BINARY_READ) {\r
+ $ret = fread($this->stream, $length);\r
+ }\r
+ //file_put_contents('/home/wok/public_html/kiwi/rawlog', '> '.$ret, FILE_APPEND);\r
+ return $ret;\r
+ }\r
+ function Write($data)\r
+ {\r
+ global $totalup;\r
+ if(!$this->stream) return false;\r
+ \r
+ //file_put_contents('/home/wok/public_html/kiwi/rawlog', '< '.$data, FILE_APPEND);\r
+ $x = @fwrite($this->stream, $data);\r
+ return $x;\r
+ }\r
+ function GetInfo()\r
+ {\r
+ if(!$this->stream)\r
+ return false;\r
+ return array('local'=>stream_socket_get_name($this->stream,false),'remote'=>stream_socket_get_name($this->stream, true));\r
+ }\r
+}\r
+class SocketStreamSSL extends SocketStream\r
+{\r
+ function __construct($host = null, $port = null, $blocking = true)\r
+ {\r
+ if(!$host && !$port)\r
+ return;\r
+ $this->stream = @stream_socket_client("ssl://$host:$port", $errno, $errstr, 4);\r
+ if(!$this->stream)\r
+ {\r
+ debug("Error creating socket: $host $errstr\n");\r
+ return;\r
+ }\r
+// socket_set_timeout($this->stream, 30);\r
+ stream_set_blocking($this->stream, $blocking);\r
+ }\r
+}\r
+class IRC\r
+{\r
+ var $stream;\r
+ var $url, $urlinfo;\r
+ var $nick;\r
+ protected $msgqueue = array();\r
+ protected $dccs = array();\r
+ private $reconnect=0;\r
+ private $nextping=0;\r
+ public $modes;\r
+ public $chanlist;\r
+ public $connected;\r
+ function __construct($url, $kiwi=null, $opts=array()){ \r
+ $this->url = $url;\r
+ $urlinfo = parse_url($url);\r
+ $this->urlinfo = $urlinfo;\r
+ if(!ereg("^ircs?$", $urlinfo['scheme']))\r
+ return false;\r
+ $ssl = ($urlinfo['scheme']=='ircs');\r
+ $host = $urlinfo['host'];\r
+ $port = isset($urlinfo['port'])?$urlinfo['port']:6667;\r
+ $this->nick = $nick = isset($urlinfo['user'])?$urlinfo['user']:'kiwi_user|'.(string)rand(0,9999);\r
+ //$ident = isset($urlinfo['pass'])?$urlinfo['pass']:$nick;\r
+ $ident = (isset($opts['ident'])) ? "{$opts['ident']}_kiwi" : "{$nick}_kiwi";\r
+ $chans = false;\r
+ if(isset($urlinfo['path'])){\r
+ $path = trim($urlinfo['path'], "/");\r
+ $chans = explode(",", $path); //"#".str_replace(array(":", ","), array(" ", ",#"), $path);\r
+ }\r
+ $this->connected = false;\r
+ if($ssl)\r
+ $this->stream = new SocketStreamSSL($host, $port, false);\r
+ else\r
+ $this->stream = new SocketStream($host, $port, false);\r
+ if(!$this->stream || !$this->stream->stream){\r
+ return false;\r
+ }\r
+ \r
+ // Send the login data\r
+ if($kiwi != null) $this->stream->Write($kiwi."\r\n");\r
+ if(isset($urlinfo['fragment'])) $this->stream->Write("PASS {$urlinfo['fragment']}\r\n");\r
+ \r
+ $this->stream->Write("NICK $nick\r\n");\r
+ $this->stream->Write("USER $ident 0 0 :$nick\r\n");\r
+ //if($chans){\r
+ // foreach($chans as $chan)\r
+ // $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n");\r
+ //} else {\r
+ $chans = array();\r
+ //}\r
+ $this->chans = $chans;\r
+ $this->connected = true;\r
+ return true;\r
+ }\r
+ function __destruct(){\r
+ if($this->stream){\r
+ $this->stream->Write("QUIT :kiwi\r\n"); \r
+ }\r
+ }\r
+ function Process(){\r
+ if((!$this->stream || !$this->stream->stream)){\r
+ if(time() > $this->reconnect) {\r
+ $this->__construct($this->url);\r
+ $this->reconnect = time() + 10;\r
+ }\r
+ usleep(50000);\r
+ return;\r
+ }\r
+ if($this->stream->Eof()){\r
+ $this->stream = null;\r
+ return;\r
+ }\r
+ $r=array($this->stream->stream);\r
+ $w = $e = array();\r
+ if(($num=@stream_select($r, $w,$e,0,50000))===false){\r
+ \r
+ } elseif($num>0){\r
+ $data=$this->stream->Read(512, PHP_NORMAL_READ);\r
+ //deb($data);\r
+ if(preg_match("/^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@([a-z0-9\.\-:]+)) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i", $data, $regs))\r
+ {\r
+ unset($prefix, $nick, $ident, $hostname, $command, $params, $trailing, $flarp);\r
+ $prefix = $regs[1];\r
+ $nick = $regs[2];\r
+ $ident = $regs[3];\r
+ $hostname = $regs[4];\r
+ $command = $regs[5];\r
+ $params = isset($regs[6]) ? $regs[6] : '';\r
+ $trailing = trim(isset($regs[7]) ? $regs[7] : '');\r
+ $flarp = compact('prefix', 'nick', 'ident', 'hostname', 'command', 'params', 'trailing');\r
+ $this->ProcessMessage($flarp);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ if(sizeof($this->msgqueue) && (microtime(true)>($this->lastqueuewrite+0.1)) && ($msg=array_shift($this->msgqueue)))\r
+ { $this->lastqueuewrite = microtime(true); $this->stream->Write($msg); }\r
+ foreach($this->dccs as $k=>&$dcc)\r
+ {\r
+ if(!$dcc->Process())\r
+ {\r
+ if($dcc->errorinfo)\r
+ $this->SendNotice($dcc->errorinfo['nick'], $dcc->errorinfo['text']);\r
+ unset($this->dccs[$k]);\r
+ }\r
+ }\r
+ if(($now=microtime(1))>$this->nextping)\r
+ {\r
+ $this->stream->Write("PONG x\r\n");\r
+ $this->nextping = $now + 150;\r
+ }\r
+ }\r
+ }\r
+ function ProcessMessage($message)\r
+ {\r
+ //echo $message['command']."\n";\r
+ switch($message['command'])\r
+ {\r
+ case "PING":\r
+ $this->stream->Write("PONG {$message['trailing']}\r\n");\r
+ break;\r
+ case "PRIVMSG":\r
+ $this->ProcessPrivMsg($message);\r
+ break;\r
+ case "001":\r
+ if($this->chans)\r
+ foreach($this->chans as $chan)\r
+ $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n");\r
+ break;\r
+ case "443":\r
+ $newnick = 'kiwi_user|'.rand(0,99999);\r
+ $this->SendCmd("NICK ".$newnick);\r
+ $this->nick = $newnick;\r
+ break;\r
+ case "MODE":\r
+ $this->OnMODE($message);\r
+ break;\r
+ case "353":\r
+ $this->On353($message);\r
+ break;\r
+ case "QUIT":\r
+ $chan = $message['params'];\r
+ foreach($this->chanlist as &$chan){\r
+ if(isset($chan['userlist'][$message['nick']]))\r
+ unset($chan['userlist'][$message['nick']]);\r
+ }\r
+ break;\r
+ case "PART":\r
+ $chan = $message['params'];\r
+ //debug("Parting {$message['nick']} from $chan");\r
+ if(isset($this->chanlist[ltrim($chan, "#")])){\r
+ unset($this->chanlist[ltrim($chan, "#")]['userlist'][$message['nick']]);\r
+ }\r
+ break;\r
+ case "JOIN":\r
+ $chan = $message['trailing'];\r
+ $nick = array($message['nick'] => '');\r
+ $nicklist = is_array($this->chanlist[ltrim($chan, "#")]['userlist']) ? $this->chanlist[ltrim($chan, "#")]['userlist'] :\r
+ array();\r
+ \r
+ $this->chanlist[ltrim($chan, "#")]['userlist'] = array_merge($nicklist, $nick);\r
+ break;\r
+ case "NICK":\r
+ if($message['nick'] == $this->nick){\r
+ $this->nick = $message['trailing'];\r
+ }\r
+ foreach($this->chanlist as $chan_name => $chan){\r
+ foreach($chan['userlist'] as $nick => $status){\r
+ if($nick == $message['nick']){\r
+ $this->chanlist[$chan_name]['userlist'][$message['trailing']] = $this->chanlist[$chan_name]['userlist'][$message['nick']];\r
+ unset($this->chanlist[$chan_name]['userlist'][$message['nick']]);\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ break;\r
+ }\r
+ }\r
+ function OnMODE($message){\r
+ if($message['params'] == $this->nick) {\r
+ $modes = $message['trailing'];\r
+ $size = strlen($modes);\r
+ $add = 1;\r
+ for($i=0;$i<$size;$i++){\r
+ if($modes{$i} == '+'){\r
+ $add = 1;\r
+ continue;\r
+ } elseif($modes{$i} == '-'){\r
+ $add = 0;\r
+ continue;\r
+ }\r
+ if($add && strpos($this->modes, $modes{$i}) === false)\r
+ $this->modes.=$modes{$i};\r
+ if(!$add && strpos($this->modes, $modes{$i}))\r
+ $this->modes = str_replace($modes{$i}, "", $this->modes);\r
+ }\r
+ }\r
+ $params = $message['params'];\r
+ $chan = trim(strtok($params, ' '));\r
+ if(in_array(trim($chan,'#'), $this->chans)){\r
+ $modes = strtok(' ');\r
+ $therest = explode(" ", trim(strtok('')));\r
+ $size = strlen($modes);\r
+ $add = 1;\r
+ $offset = 0;\r
+ for($i=0;$i<$size;$i++){\r
+ if($modes{$i} == '+'){\r
+ $add = 1;\r
+ $offset--;\r
+ continue;\r
+ } elseif($modes{$i} == '-'){\r
+ $add = 0;\r
+ $offset--;\r
+ continue;\r
+ }\r
+ if($modes[$i]=='v')\r
+ {\r
+ $user = $therest[$i+$offset];\r
+ if($add)\r
+ {\r
+ if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '+')===false)\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '+';\r
+ }\r
+ else\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('+','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);\r
+ continue;\r
+ }\r
+ if($modes[$i]=='o')\r
+ {\r
+ $user = $therest[$i+$offset];\r
+ if($add)\r
+ {\r
+ if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '@')===false)\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '@';\r
+ }\r
+ else\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('@','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);\r
+ continue;\r
+ }\r
+ if($modes[$i]=='h')\r
+ {\r
+ $user = $therest[$i+$offset];\r
+ if($add)\r
+ {\r
+ if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '%')===false)\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '%';\r
+ }\r
+ else\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('%','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);\r
+ continue;\r
+ }\r
+ if($modes[$i]=='q')\r
+ {\r
+ $user = $therest[$i+$offset];\r
+ if($add)\r
+ {\r
+ if(stripos($this->chanlist[trim($chan,'#')]['userlist'][$user], '~')===false)\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] .= '~';\r
+ }\r
+ else\r
+ $this->chanlist[trim($chan,'#')]['userlist'][$user] = str_replace('~','',$this->chanlist[trim($chan,'#')]['userlist'][$user]);\r
+ continue;\r
+ }\r
+ }\r
+ }\r
+ \r
+ }\r
+ function On353($message){\r
+ // Response to NAMES command\r
+ list($nick,,$chan) = explode(" ", $message['params']);\r
+ $nicks = explode(" ", $message['trailing']);\r
+ $prefixes = array('~', '&', '@','%','+');\r
+ $nicklist = array();\r
+ foreach($nicks as $nick){\r
+ if(in_array($nick{0}, $prefixes)){\r
+ $prefix = $nick{0};\r
+ $nick = substr($nick,1);\r
+ }\r
+ else\r
+ $prefix = '';\r
+ $nicklist[$nick] = $prefix;\r
+ }\r
+ if(sizeof($nicklist)){\r
+ // If we havn't got a list of nicks for this channel yet, create it\r
+ if(!isset($this->chanlist[ltrim($chan, "#")]['userlist'])){\r
+ $this->chanlist[ltrim($chan, "#")]['userlist'] = array();\r
+ }\r
+ $this->chanlist[ltrim($chan, "#")]['userlist'] = array_merge($this->chanlist[ltrim($chan, "#")]['userlist'], $nicklist);\r
+ }\r
+ }\r
+ function ProcessPrivMsg($message){\r
+ //$cmd = strtok($message['trailing'], ' ');\r
+ //switch($cmd){\r
+ //}\r
+ }\r
+ function SendMessage($dest, $text){\r
+ if(!$this->stream)\r
+ return false;\r
+ $this->stream->Write("PRIVMSG $dest :$text\r\n");\r
+ }\r
+ function Join($chan){\r
+ $this->stream->Write("JOIN ".str_replace(":", " ", $chan)."\r\n");\r
+ }\r
+ function SendNotice($dest, $text)\r
+ {\r
+ $this->stream->Write("NOTICE $dest :$text\r\n");\r
+ }\r
+ function QueueMessage($dest, $text)\r
+ {\r
+ $this->msgqueue[] = "PRIVMSG $dest :$text\r\n";\r
+ }\r
+ function QueueNotice($dest, $text)\r
+ {\r
+ $this->msgqueue[] = "NOTICE $dest :$text\r\n"; \r
+ }\r
+ function GetMainChan()\r
+ {\r
+ return '#'.$this->chans[0]; \r
+ }\r
+}
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ class IRCConnection extends IRC {\r
+ \r
+ var $scrollback = array();\r
+ var $server_options = array();\r
+ \r
+ function ProcessMessage($message){\r
+ global $config;\r
+ global $buffer;\r
+ global $send_debug;\r
+ \r
+ //debug("Message: ".print_r($message, 1));\r
+ deb(print_r($message, 1));\r
+ \r
+ if(strlen($message['trailing']) && ord($message['trailing'][0]) == 1){\r
+ $message['trailing'] = trim($message['trailing'], chr(1));\r
+ if(strtoupper(substr($message['trailing'], 1, 6)) == 'ACTION'){\r
+ $message['command'] = 'ACTION';\r
+ } else {\r
+ $message['command'] = 'CTCP';\r
+ }\r
+ }\r
+ \r
+ switch(strtoupper($message['command'])){\r
+ case '001':\r
+ $buffer[] = json_encode(array(\r
+ 'event'=>'connect',\r
+ 'connected'=>true,\r
+ 'host'=>$args['server']\r
+ ));\r
+ break;\r
+ \r
+ case '005':\r
+ $opts = explode(' ', $message['params']);\r
+ $to_client = array();\r
+ foreach($opts as $pair){\r
+ $opt = explode('=', $pair, 2);\r
+ $name = strtoupper($opt[0]);\r
+ $val = isset($opt[1]) ? $opt[1] : true;\r
+ $this->server_options[$name] = $val;\r
+ \r
+ if(in_array($name, array('NETWORK', 'PREFIX', 'CHANTYPES'))){\r
+ // Put the user prefixes (~&@ etc etc) into a more usable format\r
+ if($name == 'PREFIX'){\r
+ $matches = null;\r
+ preg_match('/\(([^)]*)\)(.*)/', $val, $matches);\r
+ \r
+ $val = array();\r
+ if(count($matches) == 3){\r
+ for($i=0; $i<strlen($matches[1]); $i++){\r
+ $val[$matches[2][$i]] = $matches[1][$i];\r
+ }\r
+ }\r
+ }\r
+ $to_client[$name] = $val;\r
+ }\r
+ }\r
+ \r
+ $data = array(\r
+ 'event' => 'options',\r
+ 'server' => '',\r
+ 'options' => $to_client\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case RPL_WHOISUSER:\r
+ case RPL_WHOISSERVER:\r
+ case RPL_WHOISOPERATOR:\r
+ case RPL_WHOISIDLE:\r
+ case RPL_ENDOFWHOIS:\r
+ case RPL_WHOISCHANNELS:\r
+ case RPL_WHOISMODES:\r
+ $tmp = explode(' ', $message['params']);\r
+ $tmp = $tmp[1];\r
+ $data = array(\r
+ 'event' => 'whois',\r
+ 'server' => '',\r
+ 'nick' => $tmp,\r
+ 'msg' => $message['trailing']\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case RPL_MOTD:\r
+ $data = array(\r
+ 'event' => 'motd',\r
+ 'server' => '',\r
+ 'msg' => $message['trailing']\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case "353":\r
+ // NAMES command reply\r
+ list($nick,,$chan) = explode(" ", $message['params']);\r
+ $nicks = explode(" ", $message['trailing']);\r
+ \r
+ $data = array(\r
+ 'event' => 'userlist',\r
+ 'server' => '',\r
+ 'users' => array(),\r
+ 'channel' => $chan\r
+ );\r
+ \r
+ $prefixes = array('~', '&', '@','%','+');\r
+ $nicklist = array();\r
+ $i = 0;\r
+ foreach($nicks as $nick){\r
+ if(in_array($nick{0}, $prefixes)){\r
+ $prefix = $nick{0};\r
+ $nick = substr($nick,1);\r
+ } else {\r
+ $prefix = '';\r
+ }\r
+ $nicklist[$nick] = $prefix;\r
+ \r
+ if($i==50){\r
+ $tmp = $data;\r
+ $tmp['users'] = $nicklist;\r
+ $buffer[] = json_encode($tmp);\r
+ unset($tmp);\r
+ $nicklist = array();\r
+ }\r
+ \r
+ $i++;\r
+ }\r
+ \r
+ if(count($nicklist)){\r
+ $tmp = $data;\r
+ $tmp['users'] = $nicklist;\r
+ $buffer[] = json_encode($tmp);\r
+ }\r
+ //deb(print_r($data, 1));\r
+ break;\r
+ \r
+ case '366':\r
+ list(,$chan) = explode(' ', $message['params']);\r
+ $data = array(\r
+ 'event' => 'userlist_end',\r
+ 'server' => '',\r
+ 'channel' => $chan\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case ERR_LINKCHANNEL:\r
+ list($nick, $source_chan, $dest_chan) = explode(' ', $message['params']);\r
+ $data = array(\r
+ 'event' => 'channel_redirect',\r
+ 'from' => $source_chan,\r
+ 'to' => $dest_chan\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case ERR_NOSUCHNICK:\r
+ //TODO: shit\r
+ break;\r
+ \r
+ case "JOIN":\r
+ $data = array(\r
+ 'event' => 'join',\r
+ 'nick' => $message['nick'],\r
+ 'ident' => $message['ident'],\r
+ 'hostname' => $message['hostname'],\r
+ 'channel' => $message['trailing'],\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ \r
+ deb("JOIN: {$message['nick']} / {$this->nick}");\r
+ if($message['nick'] == $this->nick){\r
+ $this->stream->Write("NAMES {$message['trailing']}\r\n");\r
+ }\r
+ \r
+ break;\r
+ \r
+ case "PART":\r
+ $data = array(\r
+ 'event' => 'part',\r
+ 'nick' => $message['nick'],\r
+ 'ident' => $message['ident'],\r
+ 'hostname' => $message['hostname'],\r
+ 'channel' => trim($message['params']),\r
+ 'message' => $message['trailing'],\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case "KICK":\r
+ $tmp = explode(' ', $message['params']);\r
+ \r
+ $data = array(\r
+ 'event' => 'kick',\r
+ 'kicked' => $tmp[1],\r
+ 'nick' => $message['nick'],\r
+ 'ident' => $message['ident'],\r
+ 'hostname' => $message['hostname'],\r
+ 'channel' => trim($tmp[0]),\r
+ 'message' => $message['trailing'],\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case "QUIT":\r
+ $data = array(\r
+ 'event' => 'quit',\r
+ 'nick' => $message['nick'],\r
+ 'ident' => $message['ident'],\r
+ 'hostname' => $message['hostname'],\r
+ 'message' => $message['trailing'],\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case "NOTICE":\r
+ $data = array(\r
+ 'event' => 'notice',\r
+ 'nick' => $message['nick'],\r
+ 'ident' => $message['ident'],\r
+ 'hostname' => $message['hostname'],\r
+ 'msg' => $message['trailing'],\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case "NICK":\r
+ $data = array(\r
+ 'event' => 'nick',\r
+ 'nick' => $message['nick'],\r
+ 'ident' => $message['ident'],\r
+ 'hostname' => $message['hostname'],\r
+ 'newnick' => $message['trailing'],\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case 'TOPIC':\r
+ $data = array(\r
+ 'event' => 'topic',\r
+ 'nick' => $message['nick'],\r
+ 'channel' => $message['params'],\r
+ 'topic' => $message['trailing']\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case '332':\r
+ $tmp = explode(' ', $message['params']);\r
+ $data = array(\r
+ 'event' => 'topic',\r
+ 'nick' => '',\r
+ 'channel' => $tmp[1],\r
+ 'topic' => $message['trailing']\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case 'ACTION':\r
+ $data = array(\r
+ 'event' => 'action',\r
+ 'nick' => $message['nick'],\r
+ 'msg' => trim(substr($message['trailing'], 8), chr(1)),\r
+ 'channel' => $message['params'],\r
+ 'time' => time()\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case 'CTCP':\r
+ $data = array(\r
+ 'event' => 'ctcp',\r
+ 'nick' => $message['nick'],\r
+ 'msg' => trim(substr($message['trailing'], 8), chr(1)),\r
+ 'channel' => $message['params'],\r
+ 'time' => time()\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ case 'MODE':\r
+ $opts = explode(' ', $message['params']);\r
+ if(count($opts) == 1){\r
+ $effected_nick = $opts[0];\r
+ $mode = $message['trailing'];\r
+ } elseif(count($opts) == 2){\r
+ $channel = $opts[0];\r
+ $mode = $opts[1];\r
+ } else {\r
+ $channel = $opts[0];\r
+ $mode = $opts[1];\r
+ $effected_nick = $opts[2];\r
+ }\r
+ \r
+ $data = array(\r
+ 'event' => 'mode',\r
+ 'nick' => $message['nick'],\r
+ 'mode' => $mode\r
+ );\r
+ \r
+ if(isset($effected_nick)) $data['effected_nick'] = $effected_nick;\r
+ if(isset($channel)) $data['channel'] = $channel;\r
+ $buffer[] = json_encode($data);\r
+ \r
+ break;\r
+ }\r
+ \r
+ if($send_debug){\r
+ $ret = str_replace('\n', ' ', print_r($message, 1));\r
+ $data = array(\r
+ 'event' => 'debug',\r
+ 'msg' => $ret,\r
+ 'time' => time()\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ }\r
+ \r
+ parent::ProcessMessage($message);\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ function ProcessPrivMsg($message){\r
+ global $buffer;\r
+ global $timeout;\r
+ global $config;\r
+ \r
+ $msg = $message['trailing'];\r
+ $cmd = strtok($msg, ' ');\r
+ //debug(print_r($message));\r
+ //deb(print_r($message, 1));\r
+ $data = array(\r
+ 'event' => 'msg',\r
+ 'nick' => $message['nick'],\r
+ 'msg' => $message['trailing'],\r
+ 'channel' => $message['params'],\r
+ 'time' => time()\r
+ );\r
+ \r
+ // Save it into the scrollback\r
+ if($config['scrollback_size']){\r
+ deb('Size: '.count($this->scrollback));\r
+ if(count($this->scrollback) >= $config['scrollback_size']){\r
+ \r
+ for($i=1; $i<count($this->scrollback); $i++){\r
+ $this->scrollback[$i-1] = $this->scrollback[$i];\r
+ }\r
+ /*\r
+ for($j=$i; $j<count($this->scrollback); $j++){\r
+ unset($this->scrollback[$i]);\r
+ }\r
+ //unset($this->scrollback[$i]);\r
+ */\r
+ $pos = count($this->scrollback)-1;\r
+ deb('Popped');\r
+ } else {\r
+ $pos = count($this->scrollback);\r
+ }\r
+ \r
+ $this->scrollback[$pos] = $data;\r
+ }\r
+ \r
+ $buffer[] = json_encode($data);\r
+ parent::ProcessPrivMsg($message);\r
+ \r
+ return;\r
+ }\r
+ \r
+ \r
+ private function isChannel($name){\r
+ return ($name[0] == "#");\r
+ }\r
+ \r
+ \r
+ private function formatMsgToHtml($inp){\r
+ $tmp = $inp;\r
+ \r
+ // Bold\r
+ if(strpos($tmp, chr(2))){\r
+ $next = '<b>';\r
+ while(strpos($tmp, chr(2)) !== false){\r
+ $pos = strpos($tmp, chr(2));\r
+ $tmp = str_replace($tmp, chr(2), $next);\r
+ $next = ($next=='<b>') ? '</b>' : '<b>';\r
+ }\r
+ if($next == '</b>') $tmp = $tmp . '</b>';\r
+ }\r
+ \r
+ return $tmp;\r
+ }\r
+ \r
+ }
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ class SESSIONS {\r
+ static function exists($session_id){\r
+ global $config;\r
+ \r
+ if($config['memcache_use']){\r
+ $ret = GLOB::$mc->get('sid_'.$session_id) ? true : false;\r
+ return $ret;\r
+ } else {\r
+ $session_sok = $config['sok_dir'].$config['sok_prefix'].$session_id;\r
+ return file_exists($session_sok);\r
+ }\r
+ }\r
+ \r
+ \r
+ \r
+ static function create($client_key){\r
+ global $config;\r
+ \r
+ $temp_id = md5(microtime().rand());\r
+ \r
+ if($config['memcache_use']){\r
+ $host_key = 'hostcount_'.$client_key;\r
+ $count = (int)GLOB::$mc->get($host_key);\r
+ if($count > $config['connections_per_host']) return false;\r
+ \r
+ // Save the sid into memcached for the ircbot to pick up\r
+ GLOB::$mc->add('sid_'.$temp_id, $temp_id);\r
+ }\r
+ \r
+ $session_cmd = "{$config['php_path']} {$config['session_script_path']} $temp_id $host_key -h$client_key";\r
+ //die($session_cmd);\r
+ $session_status = `$session_cmd`;\r
+ \r
+ if($session_status != 'ok'){\r
+ debug("Failed creating session socket with: $session_status");\r
+ GLOB::$mc->delete('sid_'.$temp_id);\r
+ return false;\r
+ }\r
+ \r
+ if($config['memcache_use']){\r
+ GLOB::$mc->add($host_key, 0);\r
+ GLOB::$mc->increment($host_key);\r
+ }\r
+ \r
+ return $temp_id;\r
+ }\r
+ \r
+ \r
+ \r
+ static function open($session_id){\r
+ global $config;\r
+ \r
+ if($config['memcache_use']){\r
+ $session_sok = GLOB::$mc->get('sid_'.$session_id);\r
+ if(!$session_sok) return false;\r
+ } else {\r
+ $session_sok = $session_id;\r
+ }\r
+ $session_sok = $config['sok_dir'].$config['sok_prefix'].$session_sok;\r
+ \r
+ $sok = @stream_socket_client('unix://'.$session_sok, $errno, $errstr);\r
+ \r
+ if(!$sok) return false;\r
+ return $sok;\r
+ }\r
+ \r
+ \r
+ \r
+ static function close($session){\r
+ fclose($session);\r
+ }\r
+ \r
+ \r
+ static function read($session, $block=true){\r
+ fwrite($session, json_encode(array('method'=>'read')));\r
+ \r
+ if(!$block){\r
+ stream_set_timeout($session, 0, 100000);\r
+ } else {\r
+ stream_set_timeout($session, 120, 0);\r
+ }\r
+ $data = fgets($session);\r
+ \r
+ return $data;\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ \r
+ static function clStart($session_id){\r
+ global $config;\r
+ \r
+ if($config['memcache_use']){\r
+ $session_sok = GLOB::$mc->get('sid_'.$session_id);\r
+ if(!$session_sok) return false;\r
+ } else {\r
+ $session_sok = $session_id;\r
+ }\r
+ $session_sok = $config['sok_dir'].$config['sok_prefix'].$session_sok;\r
+ \r
+ $sok = stream_socket_server('unix://'.$session_sok, $errno, $errstr);\r
+ if(!$sok) return false;\r
+ \r
+ return $sok;\r
+ \r
+ }\r
+ \r
+ \r
+ static function clStop($session){\r
+ fclose($session);\r
+ }\r
+ }\r
--- /dev/null
+<?php\r
+\r
+ // Class for storing global vars\r
+ class GLOB {\r
+ // Memcache connection\r
+ static $mc;\r
+ }\r
+ \r
+ \r
+ if($config['memcache_use']){\r
+ GLOB::$mc = new Memcache();\r
+ GLOB::$mc->addServer('localhost', 11211, true, 1);\r
+ }\r
+ \r
+ \r
+ \r
+ function deb($what){\r
+ //if(DEBUG){\r
+ // echo "$what\n";\r
+ //} else {\r
+ file_put_contents('/tmp/kiwi_err.log', "$what\n", FILE_APPEND);\r
+ //}\r
+ }\r
+ function debug($what){ deb($what); }
\ No newline at end of file
--- /dev/null
+<?php\r
+ \r
+ $config = array(\r
+ 'sok_dir' => '/tmp/',\r
+ 'sok_prefix' => 'kiwi_',\r
+ \r
+ 'memcache_use' => true,\r
+ 'memcache_addr' => 'localhost',\r
+ 'memcache_port' => 11211,\r
+ \r
+ 'php_path' => '/usr/bin/php5',\r
+ 'session_script_path' => dirname(__FILE__).'/irc_session.php',\r
+ //Unused 'cons_per_host' => 5,\r
+ \r
+ 'messages_per_poll' => 30,\r
+ 'max_time_per_poll' => 30, //60*2,\r
+ 'timeout' => 5, // minimum time in seconds in which the webclient needs to poll the server\r
+ 'scrollback_size' => 15,\r
+ 'connections_per_host' => 20,\r
+ \r
+ 'websocket' => array(\r
+ 'bind_addr'=>'0.0.0.0',\r
+ 'bind_port'=>7777\r
+ )\r
+ );
\ No newline at end of file
--- /dev/null
+--Do not remove this if you are using--\r
+Original Author: Remiz Rahnas\r
+Original Author URL: http://www.htmlremix.com\r
+Published date: 2008/09/24\r
+\r
+Changes by Nick Fetchak:\r
+- IE8 standards mode compatibility\r
+- VML elements now positioned behind original box rather than inside of it - should be less prone to breakage\r
+Published date : 2009/11/18\r
+\r
+\r
+<public:attach event="oncontentready" onevent="oncontentready('v08vnSVo78t4JfjH')" />\r
+<script type="text/javascript">\r
+\r
+// findPos() borrowed from http://www.quirksmode.org/js/findpos.html\r
+function findPos(obj) {\r
+ var curleft = curtop = 0;\r
+\r
+ if (obj.offsetParent) {\r
+ do {\r
+ curleft += obj.offsetLeft;\r
+ curtop += obj.offsetTop;\r
+ } while (obj = obj.offsetParent);\r
+ }\r
+\r
+ return({\r
+ 'x': curleft,\r
+ 'y': curtop\r
+ });\r
+}\r
+\r
+function oncontentready(classID) {\r
+ if (this.className.match(classID)) { return(false); }\r
+\r
+ if (!document.namespaces.v) { document.namespaces.add("v", "urn:schemas-microsoft-com:vml"); }\r
+\r
+ this.className = this.className.concat(' ', classID);\r
+ var arcSize = Math.min(parseInt(this.currentStyle['-moz-border-radius'] ||\r
+ this.currentStyle['-webkit-border-radius'] ||\r
+ this.currentStyle['border-radius'] ||\r
+ this.currentStyle['-khtml-border-radius']) /\r
+ Math.min(this.offsetWidth, this.offsetHeight), 1);\r
+ var fillColor = this.currentStyle.backgroundColor;\r
+ var fillSrc = this.currentStyle.backgroundImage.replace(/^url\("(.+)"\)$/, '$1');\r
+ var strokeColor = this.currentStyle.borderColor;\r
+ var strokeWeight = parseInt(this.currentStyle.borderWidth);\r
+ var stroked = 'true';\r
+ if (isNaN(strokeWeight)) {\r
+ strokeWeight = 0;\r
+ strokeColor = fillColor;\r
+ stroked = 'false';\r
+ }\r
+\r
+ this.style.background = 'transparent';\r
+ this.style.borderColor = 'transparent';\r
+\r
+ // Find which element provides position:relative for the target element (default to BODY)\r
+ var el = this;\r
+ var limit = 100, i = 0;\r
+ while ((typeof(el) != 'unknown') && (el.currentStyle.position != 'relative') && (el.tagName != 'BODY')) {\r
+ el = el.parentElement;\r
+ i++;\r
+ if (i >= limit) { return(false); }\r
+ }\r
+ var el_zindex = parseInt(el.currentStyle.zIndex);\r
+ if (isNaN(el_zindex)) { el_zindex = 0; }\r
+ //alert('got tag '+ el.tagName +' with pos '+ el.currentStyle.position);\r
+\r
+ var rect_size = {\r
+ 'width': this.offsetWidth - strokeWeight,\r
+ 'height': this.offsetHeight - strokeWeight\r
+ };\r
+ var el_pos = findPos(el);\r
+ var this_pos = findPos(this);\r
+ this_pos.y = this_pos.y + (0.5 * strokeWeight) - el_pos.y;\r
+ this_pos.x = this_pos.x + (0.5 * strokeWeight) - el_pos.x;\r
+\r
+ var rect = document.createElement('v:roundrect');\r
+ rect.arcsize = arcSize +'px';\r
+ rect.strokecolor = strokeColor;\r
+ rect.strokeWeight = strokeWeight +'px';\r
+ rect.stroked = stroked;\r
+ rect.style.display = 'block';\r
+ rect.style.position = 'absolute';\r
+ rect.style.top = this_pos.y +'px';\r
+ rect.style.left = this_pos.x +'px';\r
+ rect.style.width = rect_size.width +'px';\r
+ rect.style.height = rect_size.height +'px';\r
+ rect.style.antialias = true;\r
+ rect.style.zIndex = el_zindex - 1;\r
+\r
+ var fill = document.createElement('v:fill');\r
+ fill.color = fillColor;\r
+ fill.src = fillSrc;\r
+ fill.type = 'tile';\r
+\r
+ rect.appendChild(fill);\r
+ el.appendChild(rect);\r
+\r
+ var css = el.document.createStyleSheet();\r
+ css.addRule("v\\:roundrect", "behavior: url(#default#VML)");\r
+ css.addRule("v\\:fill", "behavior: url(#default#VML)");\r
+\r
+ isIE6 = /msie|MSIE 6/.test(navigator.userAgent);\r
+ // IE6 doesn't support transparent borders, use padding to offset original element\r
+ if (isIE6 && (strokeWeight > 0)) {\r
+ this.style.borderStyle = 'none';\r
+ this.style.paddingTop = parseInt(this.currentStyle.paddingTop || 0) + strokeWeight;\r
+ this.style.paddingBottom = parseInt(this.currentStyle.paddingBottom || 0) + strokeWeight;\r
+ }\r
+\r
+ if (typeof(window.rounded_elements) == 'undefined') {\r
+ window.rounded_elements = new Array();\r
+\r
+ if (typeof(window.onresize) == 'function') { window.previous_onresize = window.onresize; }\r
+ window.onresize = window_resize;\r
+ }\r
+ this.element.vml = rect;\r
+ window.rounded_elements.push(this.element);\r
+}\r
+\r
+function window_resize() {\r
+ if (typeof(window.rounded_elements) == 'undefined') { return(false); }\r
+\r
+ for (var i in window.rounded_elements) {\r
+ var el = window.rounded_elements[i];\r
+\r
+ var strokeWeight = parseInt(el.currentStyle.borderWidth);\r
+ if (isNaN(strokeWeight)) { strokeWeight = 0; }\r
+\r
+ var parent_pos = findPos(el.vml.parentNode);\r
+ var pos = findPos(el);\r
+ pos.y = pos.y + (0.5 * strokeWeight) - parent_pos.y;\r
+ pos.x = pos.x + (0.5 * strokeWeight) - parent_pos.x;\r
+\r
+ el.vml.style.top = pos.y +'px';\r
+ el.vml.style.left = pos.x +'px';\r
+ }\r
+\r
+ if (typeof(window.previous_onresize) == 'function') { window.previous_onresize(); }\r
+}\r
+</script>\r
+\r
--- /dev/null
+body, html {\r
+ height: 100%; margin:0px;\r
+ font-size:14px;\r
+}\r
+\r
+\r
+\r
+\r
+\r
+/* Style resets */\r
+#kiwi * {\r
+ padding:0px; margin:0px;\r
+}\r
+#kiwi ul { list-style:none; }\r
+\r
+#kiwi #login a {\r
+ color: #8D9713;\r
+ text-decoration:none;\r
+}\r
+\r
+\r
+/* The main app container */\r
+#kiwi {\r
+ overflow:hidden;\r
+ position:relative; \r
+ height:100%;\r
+ font-family:monospace; font-size:1em;\r
+ background:#EBEBEB;\r
+}\r
+#kiwi.small_kiwi .userlist { right:-100px; }\r
+#kiwi.small_kiwi .messages { right:10px; }\r
+\r
+\r
+#kiwi .windowlist {\r
+ /*height: 65px;*/\r
+ background-color:#1B1B1B;\r
+ font-size:0.9em;\r
+}\r
+#kiwi .windowlist ul {\r
+ overflow: hidden;\r
+ margin-right: 200px;\r
+ white-space: nowrap;\r
+ display:block;\r
+ /*height: 35px;*/\r
+}\r
+#kiwi .windowlist ul li {\r
+ float: left;\r
+ list-style: inline;\r
+ padding:5px;\r
+ margin:3px;\r
+ border: 1px solid #333;\r
+ background-color: #eee;\r
+ display: inline;\r
+ line-height: 1.4em;\r
+ vertical-align: middle;\r
+ \r
+ position:relative;\r
+ \r
+ border-radius:5px;\r
+ -moz-border-radius:5px;\r
+ -webkit-border-radius:5px;\r
+ -khtml-border-radius:5px;\r
+ behavior: url(border-radius.htc);\r
+}\r
+#kiwi .windowlist .active { padding-right:23px; }\r
+#kiwi .windowlist .highlight {\r
+ background-color: #990000;\r
+ font-weight: bold;\r
+}\r
+#kiwi .windowlist .activity { font-weight: bold; background-color: #009900; }\r
+\r
+#kiwi .windowlist ul li img { width:1em; height:1em; top:7px; right:5px; position:absolute; }\r
+/*#kiwi .windowlist ul li a {\r
+ margin:3px;\r
+ border: 1px solid #333;\r
+ background-color: #eee;\r
+ display: inline;\r
+}*/\r
+\r
+#kiwi .poweredby { float:right; margin:8px 15px; cursor:pointer; color:#EBEBEB; font-family: arial;}\r
+\r
+\r
+#kiwi .userlist {\r
+ position: absolute;\r
+ width: 100px;\r
+ top:92px; right:0px;\r
+ bottom: 55px;\r
+ margin: 5px;\r
+ overflow-y:auto;\r
+}\r
+#kiwi .userlist ul { display: none; }\r
+#kiwi .userlist ul.active { display:block; }\r
+#kiwi .userlist ul li { padding:1px 2px; cursor:pointer; }\r
+#kiwi .userlist ul li:hover { background-color:#FAF7D3; }\r
+#kiwi .userlist ul li a.nick { display:block; }\r
+\r
+#kiwi .userlist ul li .userbox { margin:0px 5px 5px 5px; font-size:.9em; }\r
+#kiwi .userlist ul li .userbox a { display:block; text-decoration:none; border-bottom: 1px dashed #aaa; }\r
+\r
+#kiwi .cur_topic {\r
+ font-family:Verdana, Geneva, sans-serif;\r
+ font-size:.85em;\r
+ line-height:18px; vertical-align:middle;\r
+ height:18px;\r
+ text-align:center;\r
+ white-space: nowrap; overflow: hidden;\r
+ padding: 3px 5px;\r
+ border-bottom:3px solid #1B1B1B;\r
+ \r
+}\r
+\r
+#kiwi .messages {\r
+ position: absolute;\r
+ top:92px; left:0px;\r
+ right: 110px; bottom:30px;\r
+ overflow-y:scroll;\r
+ overflow-x:wrap;\r
+ border:none;\r
+ display: none;\r
+}\r
+#kiwi .messages a {\r
+ text-decoration:none;\r
+}\r
+#kiwi .messages.active { display:block; }\r
+\r
+#kiwi .messages .msg { border-bottom: 1px solid #CCC; padding:1px; font-size:0.9em; }\r
+#kiwi .messages .msg .time { width:6em; float:left; color:#777; }\r
+#kiwi .messages .msg .nick { width:7em; text-align:right; float:left; font-family:arial; font-size:12px; }\r
+#kiwi .messages .msg .text { margin-left:15em; white-space:pre-wrap; word-wrap:break-word; }\r
+\r
+#kiwi .messages .msg.action .nick { display:none; }\r
+#kiwi .messages .msg.action .text { margin-left:9em; }\r
+#kiwi .messages .msg.status .nick { display:none; }\r
+#kiwi .messages .msg.status .text { color:#990000; margin-left:9em; font-weight:bold; }\r
+#kiwi .messages .msg.topic .nick { display:none; }\r
+#kiwi .messages .msg.topic .text { color:#009900; margin-left:9em; font-style: italic; }\r
+/*#kiwi .messages .msg.motd .nick { display:none; }*/\r
+#kiwi .messages .msg.motd { border:none; }\r
+#kiwi .messages .msg.motd .text { color:#666; }\r
+#kiwi .messages .msg.whois .nick { font-weight:normal; }\r
+#kiwi .messages .msg.whois .text { margin-left:18em; padding-left:1em; border-left:1px dashed #999; }\r
+\r
+\r
+\r
+#kiwi .control {\r
+ position: absolute;\r
+ bottom:0px; left: 0px;\r
+ /*height:55px;*/\r
+ /*padding: 3px;*/\r
+ width: 100%;\r
+ background-color:#1B1B1B;\r
+}\r
+#kiwi .control .msginput {\r
+ background-color:#fff; margin:3px;\r
+ position:relative;\r
+ \r
+ border-radius:5px;\r
+ -moz-border-radius:5px;\r
+ -webkit-border-radius:5px;\r
+ -khtml-border-radius:5px;\r
+ behavior: url(border-radius.htc);\r
+}\r
+#kiwi .control .nick { text-align: right; width:11em; float:left; padding:2px; }\r
+#kiwi .control .nick a { text-decoration:none; }\r
+#kiwi .control input {\r
+ border:none;\r
+ width:70%; display:block;\r
+ margin-left:12em;\r
+ padding: 2px 0px;\r
+}\r
+#kiwi .control .plugins { margin:3px; margin-top:7px; }\r
+#kiwi .control .plugins ul li {\r
+ display:inline; font-size:0.8em; margin-left:1em;\r
+}
\ No newline at end of file
--- /dev/null
+body, html { height: 100%; margin:0px; }\r
+\r
+\r
+\r
+#kiwi { overflow:hidden; font-family:monospace; font-size:1em; height:100%; }\r
+#kiwi * {\r
+ padding:0px; margin:0px;\r
+}\r
+#kiwi ul { list-style:none; }\r
+\r
+#kiwi .userlist {\r
+ float:right;\r
+ width: 100px;\r
+ margin: 5px;\r
+ overflow-y:auto;\r
+}\r
+\r
+#kiwi .messages { overflow-y:scroll; height:90%; border:1px solid #999; }\r
+\r
+#kiwi .messages .msg { border-bottom: 1px solid #CCC; }\r
+#kiwi .messages .msg .time { width:6em; float:left; }\r
+#kiwi .messages .msg .nick { width:7em; text-align:right; float:left; }\r
+#kiwi .messages .msg .text { margin-left:15em; }\r
+\r
+#kiwi .control { margin: 3px;}\r
+#kiwi .control .msginput { padding:3px; padding-left: 7em; background-color:#F2F9FD; }\r
+#kiwi .control .msginput .nick { float:left; width:6em; text-align:right; }\r
+#kiwi .control .msginput .nick a { text-decoration:none; }\r
+#kiwi .control .msginput input { margin-left: 1em; border:none; background-color:inherit; padding:3px; display:block;}\r
+#kiwi .control .plugins ul li { display:inline; font-size:0.9em; margin-left:1em; }
\ No newline at end of file
--- /dev/null
+#kiwi.touchscreen.portrait .userlist { right:-100px; }\r
+\r
+#kiwi.touchscreen.portrait .messages { right:10px; }\r
+\r
+#kiwi.touchscreen .windowlist ul li { padding:10px; }\r
+\r
+#kiwi .windowlist ul {\r
+ /* border: 1px solid blue;*/\r
+ margin-right: 0px; /* i* safaris seem to behave differently here :/ */\r
+ height: 50px;\r
+}
\ No newline at end of file
--- /dev/null
+/*\r
+ ####################\r
+ Common\r
+ ####################\r
+*/\r
+#kiwi .link { cursor:pointer; color:blue; }\r
+#kiwi .newnick {\r
+ bottom:40px;\r
+ left:20px;\r
+}\r
+\r
+\r
+#kiwi .plugins a { cursor:pointer; color:#EBEBEB; font-family:verdana; }\r
+#kiwi .plugin_file { left:20px; }\r
+\r
+#kiwi .plugin_file .list select { width:150px; height:200px; }\r
+#kiwi .plugin_file .list button { float:right; }\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+#kiwi .connectwindow {\r
+ background:#1b1b1b;\r
+ color: #D4D4D4;\r
+ z-index:50;\r
+ position:absolute;\r
+ top:0px; bottom:0px;\r
+ width:100%; height:100%;\r
+}\r
+#kiwi h1.logo { display:block; width:264px; height:90px; text-indent:-1000px; margin:70px auto 100px; background:url(../img/logo.png) no-repeat; }\r
+#kiwi #login { width:665px; margin:0px auto; }\r
+#kiwi #login ul { list-style:none; }\r
+#kiwi #login ul li { overflow:hidden; margin-bottom:10px; }\r
+#kiwi #login ul li label { float:left; text-align:right; padding-right:.5em; }\r
+#kiwi #login input { border:none; }\r
+#kiwi #login .more { border-top: 1px dotted #888888; }\r
+#kiwi #login .more_link {\r
+ font-size:12px;\r
+ position:relative;\r
+ top:-16px;\r
+ background: url(../img/more.png) no-repeat right 5px;\r
+ padding-right: 12px;\r
+ color:#888;\r
+}\r
+\r
+#kiwi #login .content.top {\r
+ width:470px;\r
+ margin:30px auto 60px;\r
+}\r
+#kiwi #login .content.top ul { font-size:20px; }\r
+#kiwi #login .content.top label { width:40%; }\r
+#kiwi #login .content.top input {\r
+ width:240px; padding:.2em .5em;\r
+ font-size:.8em; text-align:center;\r
+ background:#D4D4D4;\r
+ border-radius:8px; -moz-border-radius:8px;\r
+}\r
+#kiwi #login .content.top a { display:block; text-align:right; font-size:22px; margin-top:30px; }\r
+\r
+#kiwi #login .content.bottom { width:350px; margin:30px auto; display:none; }\r
+#kiwi #login .content.bottom ul { font-size:18px; }\r
+#kiwi #login .content.bottom label { width:25%; }\r
+#kiwi #login .content.bottom input { width:216px; font-size:.8em; no-repeat; padding:.2em .5em; background:#D4D4D4; border-radius:8px; -moz-border-radius:8px; }\r
+#kiwi #login .content.bottom a { display:block; text-align:right; font-size:16px; margin-top:30px; }\r
+\r
+\r
+\r
+\r
+#kiwi .about {\r
+ top: 100px;\r
+ left: 200px;\r
+ width: 400px;\r
+ z-index: 1;\r
+ display: none;\r
+}\r
+#kiwi .about button { float:right; }\r
+\r
+#kiwi .box {\r
+ min-width:150px;\r
+ background: url(../img/trans_back.png) repeat;\r
+ border-radius:5px;\r
+ -moz-border-radius:5px;\r
+ -webkit-border-radius:5px;\r
+ -khtml-border-radius:5px;\r
+ padding:7px;\r
+ \r
+ /*background-color: #819463;\r
+ padding: 10px;*/\r
+ position:absolute;\r
+ \r
+ /*\r
+ box-shadow: 0px 0px 20px #555;\r
+ -webkit-box-shadow: 0px 0px 20px #555;\r
+ -moz-box-shadow: 0px 0px 20px #555;\r
+ filter: progid:DXImageTransform.Microsoft.dropShadow(color=#555, offX=0, offY=0, positive=true);\r
+ \r
+ border-radius: 4px;\r
+ -moz-border-radius: 4px;\r
+ -webkit-border-radius: 4px;\r
+ */\r
+}\r
+\r
+.box .boxarea {\r
+ padding:15px !important;\r
+ background-color:#D4D4D4;\r
+ border-radius:3px;\r
+ -moz-border-radius:3px;\r
+ -webkit-border-radius:3px;\r
+ -khtml-border-radius:3px;\r
+ overflow:hidden;\r
+}
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+\r
+ //deb("FROM IPC CLIENT:\n$data\n-----");\r
+ $d = json_decode($data, 1);\r
+ //deb('Processed data: '.print_r($d, 1));\r
+ switch($d['method']){\r
+ case 'addbuffer':\r
+ $buffer[] = json_encode($d['args']);\r
+ break;\r
+ \r
+ case 'read':\r
+ // The 'read' command adds this web client to the read buffer queue\r
+ if(!in_array($cl, $ipc_read)) $ipc_read[] = $cl;\r
+ break;\r
+ \r
+ case 'quit':\r
+ deb('Quitting');\r
+ $bot = null;\r
+ break;\r
+ \r
+ case 'connect':\r
+ $args = $d['args'];\r
+ deb('Connecting with: '.print_r($args, 1));\r
+ if(!isset($args['server'], $args['port'], $args['ssl'], $args['nick'])){\r
+ $buffer[] = json_encode(array('error'=>'invalid_args'));\r
+ break;\r
+ } else {\r
+ $kiwi = null;\r
+ /*if($config['memcache_use']){\r
+ $conf_key = 'kiwi_conf_'.$args['server'];\r
+ $conf = (int)GLOB::$mc->get($conf_key);\r
+ if($conf){\r
+ $c = @unserialize($conf);\r
+ if($c){\r
+ //$hostname = GET HOSTNAME HERE\r
+ //$ip = GET IP HERE\r
+ $kiwi = "WEBIRC {$c['password']} {$c['user']} $hostname $ip";\r
+ }\r
+ }\r
+ }*/\r
+ deb("ARGSLOL: ".print_r($app_args, 1));\r
+ $opts = array();\r
+ if(isset($app_args['remote_host'])) $opts['ident'] = md5($app_args['remote_host']);\r
+ \r
+ $bot = new IRCConnection("irc://{$args['nick']}@{$args['server']}:{$args['port']}", $kiwi, $opts);\r
+ if(isset($args['channels'])) $bot->chans = explode(',', $args['channels']);\r
+ if($bot->connected){\r
+ // We're.. connected!\r
+ } else {\r
+ $buffer[] = json_encode(array('event'=>'server_connect', 'connected'=>false, 'host'=>$args['server']));\r
+ unset($bot);\r
+ }\r
+ }\r
+ break;\r
+ \r
+ case 'join':\r
+ $args = $d['args'];\r
+ if(!isset($args['channel'])){\r
+ $buffer[] = json_encode(array('error'=>'invalid_args'));\r
+ break;\r
+ } else {\r
+ $chans = explode(',', $args['channel']);\r
+ foreach($chans as $c)\r
+ $bot->Join($c);\r
+ }\r
+ break;\r
+ \r
+ case 'msg':\r
+ $args = $d['args'];\r
+ deb('msg with: '.print_r($args, 1));\r
+ if(!isset($args['target'],$args['msg'])){\r
+ $buffer[] = json_encode(array('error'=>'invalid_args'));\r
+ break;\r
+ } else {\r
+ $bot->SendMessage($args['target'], $args['msg']);\r
+ $buffer[] = json_encode($data);\r
+ }\r
+ break;\r
+ \r
+ case 'action':\r
+ $args = $d['args'];\r
+ deb('action with: '.print_r($args, 1));\r
+ if(!isset($args['target'],$args['msg'])){\r
+ $buffer[] = json_encode(array('error'=>'invalid_args'));\r
+ break;\r
+ } else {\r
+ $bot->SendMessage($args['target'], chr(1)."ACTION {$args['msg']}".chr(1));\r
+ $buffer[] = json_encode($data);\r
+ }\r
+ break;\r
+ \r
+ case 'raw':\r
+ $args = $d['args'];\r
+ if(!isset($args['data'])){\r
+ $buffer[] = json_encode(array('error'=>'invalid_args'));\r
+ break;\r
+ } else {\r
+ $bot->stream->Write("{$args['data']}\r\n");\r
+ //$bot->SendMessage($args['target'], $args['msg']);\r
+ }\r
+ break;\r
+ \r
+ \r
+ case 'debug':\r
+ $send_debug = !$send_debug;\r
+ $data = array(\r
+ 'event' => 'debug',\r
+ 'msg' => 'Debugging '.($send_debug)?'on':'off',\r
+ 'time' => time()\r
+ );\r
+ $buffer[] = json_encode($data);\r
+ break;\r
+ \r
+ \r
+ case 'sync':\r
+ // Clear the current buffer as we're gonna send only scrollback\r
+ $buffer = array();\r
+ \r
+ // Send the settings and channels over\r
+ if($bot){\r
+ $data = array('event'=>'sync');\r
+ $data['nick'] = $bot->nick;\r
+ $data['tabviews'] = array();\r
+ if($bot->chanlist){\r
+ foreach($bot->chanlist as $chan_name => $chan){\r
+ $data['tabviews'][] = array('name'=>$chan_name, 'userlist'=>$chan['userlist']);\r
+ }\r
+ $buffer[] = json_encode($data);\r
+ }\r
+ \r
+ // Send the message scrollback\r
+ foreach($bot->scrollback as $line) $buffer[] = json_encode($line);\r
+ //$bot->scrollback = array();\r
+ } else {\r
+ $data = array('error'=>'no_data');\r
+ }\r
+ \r
+ $buffer[] = json_encode($data);\r
+ \r
+ break;\r
+ }
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+<title>kiwi</title>\r
+<link rel="stylesheet" type="text/css" href="css/default.css">\r
+<link rel="stylesheet" type="text/css" href="css/ui.css">\r
+\r
+<?php\r
+\r
+ switch(true){\r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'android') > 0:\r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'iphone') > 0:\r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'ipod') > 0:\r
+?>\r
+<meta name="viewport" content="width=device-width,user-scalable=no" />\r
+\r
+<?php\r
+ break;\r
+ \r
+ }\r
+?>\r
+\r
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>\r
+<script type="text/javascript" src="js/jquery.json-2.2.min.js"></script>\r
+<script type="text/javascript" src="js/util.js"></script>\r
+<script type="text/javascript" src="js/gateway.js"></script>\r
+<script type="text/javascript" src="js/front.js"></script>\r
+<script type="text/javascript">\r
+\r
+ var init_data = {};\r
+\r
+ $(document).ready(function(){\r
+ \r
+ //#nick@irc.anonnet.org:6679+/#channel,##channel,&channel\r
+ var chans = document.location.hash.match(/[#&]+[^ ,\007]+/g);\r
+ if(chans != null && chans.length > 0) { init_data.channel = chans.join(','); }\r
+ \r
+ front.init();\r
+ });\r
+</script>\r
+\r
+</head>\r
+\r
+<body>\r
+\r
+<div id="kiwi">\r
+ <div class="connectwindow">\r
+ <form class="formconnectwindow">\r
+ <label for="nick">Nick</label>\r
+ <input type="text" class="nick" value="<?php if(isset($_GET['nick'])) echo htmlentities($_GET['nick']); ?>" /><br />\r
+ \r
+ <span class="networkselection">\r
+<?php if(isset($_GET['server'])){ ?>\r
+ <input type="hidden" class="network" value="<?php if(isset($_GET['server'])) echo htmlentities($_GET['server']); ?>" />\r
+<?php } else { ?>\r
+ <label for="network">Network</label>\r
+ <select class="network">\r
+ <option value="irc.anonnet.org">AnonNet</option>\r
+ <option value="irc.freenode.net">Freenode</option>\r
+ <option value="irc.dal.net">DALnet</option>\r
+ \r
+ </select>\r
+<?php } ?>\r
+ </span>\r
+ <button type="submit">Connect</button>\r
+ </form>\r
+ </div>\r
+ \r
+ \r
+ \r
+ \r
+ <div class="windowlist">\r
+ <ul></ul>\r
+ <div class="poweredby">Powered by kiwi</div>\r
+ </div>\r
+ \r
+ <div class="userlist">\r
+ <ul></ul>\r
+ </div>\r
+ \r
+ \r
+ <div class="control">\r
+ <div class="msginput">\r
+ <div class="nick"><a href="#"></a>:</div>\r
+ <input type="text" name="kiwi_msginput" id="kiwi_msginput" />\r
+ </div>\r
+ <div class="plugins">\r
+ <ul>\r
+ <li><a class="load_plugin_file">Load plugin file</a></li>\r
+ </ul>\r
+ </div>\r
+ </div>\r
+</div>\r
+\r
+</body>\r
+</html>\r
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />\r
+<title>Welcome page</title>\r
+<style type="text/css" media="screen">\r
+body { background: #e7e7e7; font-family: Verdana, sans-serif; font-size: 11pt; }\r
+#page { background: #ffffff; margin: 50px; border: 2px solid #c0c0c0; padding: 10px; }\r
+#header { background: #4b6983; border: 2px solid #7590ae; text-align: center; padding: 10px; color: #ffffff; }\r
+#header h1 { color: #ffffff; }\r
+#body { padding: 10px; }\r
+span.tt { font-family: monospace; }\r
+span.bold { font-weight: bold; }\r
+a:link { text-decoration: none; font-weight: bold; color: #C00; background: #ffc; }\r
+a:visited { text-decoration: none; font-weight: bold; color: #999; background: #ffc; }\r
+a:active { text-decoration: none; font-weight: bold; color: #F00; background: #FC0; }\r
+a:hover { text-decoration: none; color: #C00; background: #FC0; }\r
+</style>\r
+</head>\r
+<body>\r
+<div id="page">\r
+ <div id="header">\r
+ <h1> Placeholder page </h1>\r
+ The owner of this web site has not put up any web pages yet. Please come back later.\r
+ </div>\r
+ <div id="body">\r
+ <h2>You should replace this page with your own web pages as soon as possible.</h2>\r
+ Unless you changed its configuration, your new server is configured as follows:\r
+ <ul>\r
+ <li>Configuration files can be found in <span class="tt">/etc/lighttpd</span>. Please read <span class="tt">/etc/lighttpd/conf-available/README</span> file.</li>\r
+ <li>The DocumentRoot, which is the directory under which all your HTML files should exist, is set to <span class="tt">/var/www</span>.</li>\r
+ <li>CGI scripts are looked for in <span class="tt">/usr/lib/cgi-bin</span>, which is where Ubuntu packages will place their scripts. You can enable cgi module by using command <span class="bold tt">"lighty-enable-mod cgi"</span>.</li>\r
+ <li>Log files are placed in <span class="tt">/var/log/lighttpd</span>, and will be rotated weekly. The frequency of rotation can be easily changed by editing <span class="tt">/etc/logrotate.d/lighttpd</span>.</li>\r
+ <li>The default directory index is <span class="tt">index.html</span>, meaning that requests for a directory <span class="tt">/foo/bar/</span> will give the contents of the file /var/www/foo/bar/index.html if it exists (assuming that <span class="tt">/var/www</span> is your DocumentRoot).</li>\r
+ <li>You can enable user directories by using command <span class="bold tt">"lighty-enable-mod userdir"</span></li>\r
+ </ul>\r
+ <h2>About this page</h2>\r
+ <p>\r
+ This is a placeholder page installed by the Ubuntu release of the <a href="http://packages.ubuntu.com/lighttpd">Lighttpd server package.</a>\r
+ </p>\r
+ <p>\r
+ 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.\r
+ </p>\r
+ <p>\r
+ 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 \r
+ <a href="https://bugs.launchpad.net/ubuntu/+source/lighttpd/">Ubuntu Bug Tracking System.</a>\r
+ </p>\r
+ <p>\r
+ <a href="http://validator.w3.org/check?uri=referer"><img\r
+ src="http://www.w3.org/Icons/valid-xhtml10"\r
+ alt="Valid XHTML 1.0 Transitional" height="31" width="88" border="0" /></a>\r
+ </p> \r
+ </div>\r
+</div>\r
+<!-- s:853e9a42efca88ae0dd1a83aeb215047 -->\r
+</body>\r
+</html>\r
--- /dev/null
+<?php\r
+\r
+ switch(true){\r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'android') > 0:\r
+ $agent = "android"; $touchscreen = true;\r
+ break;\r
+ \r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'iphone') > 0:\r
+ $agent = "iphone"; $touchscreen = true;\r
+ break;\r
+ \r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'ipod') > 0:\r
+ $agent = "ipod"; $touchscreen = true;\r
+ break;\r
+ \r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'ipad') > 0:\r
+ $agent = "ipad"; $touchscreen = true;\r
+ break;\r
+ \r
+ default:\r
+ $agent = "normal";\r
+ $touchscreen = false;\r
+ }\r
+ \r
+ define("SERVER_SET", isset($_GET['server']));\r
+ $server = isset($_GET['server']) ? $_GET['server'] : "irc.anonnet.org";\r
+ $nick = isset($_GET['nick']) ? $_GET['nick'] : "";\r
+ // Channel is set via javascript using location.hash\r
+ \r
+?>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+\r
+<?php if(in_array($agent, array("android", "ipad", "iphone", "ipad"))){ ?>\r
+<meta name="viewport" content="width=device-width,user-scalable=no" />\r
+<?php } ?>\r
+\r
+<title>Kiwi IRC</title>\r
+<link rel="stylesheet" type="text/css" href="css/default.css">\r
+<link rel="stylesheet" type="text/css" href="css/ui.css">\r
+\r
+<?php if($touchscreen){ ?>\r
+<link rel="stylesheet" type="text/css" href="css/touchscreen_tweaks.css">\r
+<?php } ?>\r
+\r
+\r
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>\r
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>\r
+<script type="text/javascript" src="js/jquery.json-2.2.min.js"></script>\r
+<script type="text/javascript" src="js/util.js"></script>\r
+<script type="text/javascript" src="js/gateway.js"></script>\r
+<script type="text/javascript" src="js/front.js"></script>\r
+\r
+<?php if(in_array($agent, array("android", "ipad", "iphone", "ipad"))){ ?>\r
+<script type="text/javascript" src="js/touchscreen_tweaks.js"></script>\r
+<?php } ?>\r
+\r
+<script type="text/javascript">\r
+ var agent = '<?= $agent ?>';\r
+ var touchscreen = <?= ($touchscreen) ? 'true' : 'false' ?>;\r
+ var init_data = {};\r
+ \r
+ $(document).ready(function(){\r
+ if(touchscreen) $('#kiwi').addClass('touchscreen');\r
+ \r
+ //#nick@irc.anonnet.org:6679+/#channel,##channel,&channel\r
+ var chans = document.location.hash.match(/[#&]+[^ ,\007]+/g);\r
+ if(chans != null && chans.length > 0) {\r
+ init_data.channel = chans.join(',');\r
+ $('#channel').val(init_data.channel);\r
+ }\r
+ \r
+ front.init();\r
+ \r
+ addEvents();\r
+ $('.nick').focus();\r
+ });\r
+ \r
+ \r
+ function addEvents(){\r
+ $('.more_link').click(function(){ $('.content.bottom').slideDown('fast'); $('.network').focus(); return false; });\r
+ $('.formconnectwindow').submit(function(){\r
+ init_data.channel = $('#channel').val();\r
+ return false;\r
+ });\r
+ $('a.connect').click(function(){ $('.formconnectwindow').submit(); return false; });\r
+ }\r
+</script>\r
+\r
+</head>\r
+\r
+<body>\r
+\r
+<div id="kiwi">\r
+ <div class="connectwindow">\r
+ <h1 class="logo">Kiwi IRC</h1>\r
+ <div id="login">\r
+ <form class="formconnectwindow">\r
+ <div class="content top">\r
+ <ul>\r
+ <li><label for="nick">Your nickname:</label>\r
+ <input type="text" id="nick" name="nick" class="nick" value="<?php echo htmlentities($nick); ?>" /></li>\r
+ </ul>\r
+ <a class="connect" href="">Connect..</a>\r
+ </div>\r
+ \r
+ <div class="more" style="<?php if(SERVER_SET) echo "display:none;"; ?>">\r
+ <a href="" class="more_link">More</a>\r
+ <div class="content bottom">\r
+ <ul>\r
+ <li><label for="network">Server:</label>\r
+ <input type="text" id="network" name="network" class="network" value="<?php echo htmlentities($server); ?>" /></li>\r
+ <li><label for="channel">Channel:</label>\r
+ <input type="text" id="channel" name="channel" class="channel" value="#kiwiirc" /></li>\r
+ </ul>\r
+ <a class="connect" href="">Connect..</a>\r
+ </div>\r
+ </div>\r
+ </form>\r
+ </div>\r
+ </div>\r
+ \r
+ \r
+ \r
+ <div class="windowlist">\r
+ <div class="poweredby">Powered by Kiwi IRC</div>\r
+ <ul></ul>\r
+ </div>\r
+ \r
+ <div class="cur_topic"></div>\r
+ \r
+ <div class="userlist">\r
+ <ul></ul>\r
+ </div>\r
+ \r
+ <div class="control">\r
+ <div class="msginput">\r
+ <div class="nick"><a href="#"></a>:</div>\r
+ <input type="text" name="kiwi_msginput" id="kiwi_msginput" />\r
+ </div>\r
+ <div class="plugins">\r
+ <ul>\r
+ <!-- <li><a class="load_plugin_file">Plugins</a></li> -->\r
+ <?php if(isset($_GET['debug'])){ ?>\r
+ <li><a class="reload_css">Reload CSS</a></li>\r
+ <?php } ?>\r
+ </ul>\r
+ </div>\r
+ </div>\r
+</div>\r
+\r
+</body>\r
+</html>
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ switch(true){\r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'android') > 0:\r
+ $agent = "android"; $touchscreen = true;\r
+ break;\r
+ \r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'iphone') > 0:\r
+ $agent = "iphone"; $touchscreen = true;\r
+ break;\r
+ \r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'ipod') > 0:\r
+ $agent = "ipod"; $touchscreen = true;\r
+ break;\r
+ \r
+ case stripos($_SERVER['HTTP_USER_AGENT'], 'ipad') > 0:\r
+ $agent = "ipad"; $touchscreen = true;\r
+ break;\r
+ \r
+ default:\r
+ $agent = "normal";\r
+ $touchscreen = false;\r
+ }\r
+?>\r
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+\r
+<?php if(in_array($agent, array("android", "ipad", "iphone", "ipad"))){ ?>\r
+<meta name="viewport" content="width=device-width,user-scalable=no" />\r
+<?php } ?>\r
+\r
+<title>kiwi</title>\r
+<link rel="stylesheet" type="text/css" href="css/default.css">\r
+<link rel="stylesheet" type="text/css" href="css/ui.css">\r
+\r
+<?php if($touchscreen){ ?>\r
+<link rel="stylesheet" type="text/css" href="css/touchscreen_tweaks.css">\r
+<?php } ?>\r
+\r
+\r
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>\r
+<script type="text/javascript" src="js/jquery.json-2.2.min.js"></script>\r
+<script type="text/javascript" src="js/util.js"></script>\r
+<script type="text/javascript" src="js/gateway.js"></script>\r
+<script type="text/javascript" src="js/front.js"></script>\r
+\r
+<?php if(in_array($agent, array("android", "ipad", "iphone", "ipad"))){ ?>\r
+<script type="text/javascript" src="js/touchscreen_tweaks.js"></script>\r
+<?php } ?>\r
+\r
+<script type="text/javascript">\r
+ var agent = '<?= $agent ?>';\r
+ var touchscreen = <?= ($touchscreen) ? 'true' : 'false' ?>;\r
+ var init_data = {};\r
+ \r
+ $(document).ready(function(){\r
+ if(touchscreen) $('#kiwi').addClass('touchscreen');\r
+ \r
+ //#nick@irc.anonnet.org:6679+/#channel,##channel,&channel\r
+ var chans = document.location.hash.match(/[#&]+[^ ,\007]+/g);\r
+ if(chans != null && chans.length > 0) { init_data.channel = chans.join(','); }\r
+ \r
+ front.init();\r
+ });\r
+</script>\r
+\r
+</head>\r
+\r
+<body>\r
+\r
+<div id="kiwi">\r
+ <div class="box about">\r
+ <h2>kiwi</h2>\r
+ <p>An alternative to downloading an irc client. This web app is the best thing you'll use in the next couple years.</p>\r
+ <button class="about_close">Close</button>\r
+ </div>\r
+ \r
+ <div class="connectwindow">\r
+ <form class="formconnectwindow">\r
+ <label for="nick">Nick</label>\r
+ <input type="text" class="nick" value="<?php if(isset($_GET['nick'])) echo htmlentities($_GET['nick']); ?>" /><br />\r
+ \r
+ <span class="networkselection">\r
+<?php if(isset($_GET['server'])){ ?>\r
+ <input type="hidden" class="network" value="<?php if(isset($_GET['server'])) echo htmlentities($_GET['server']); ?>" />\r
+<?php } else { ?>\r
+ <label for="network">Network</label>\r
+ <select class="network">\r
+ <?php if(1==1){ echo '<option value="irc.anonnet.org">AnonNet</option>'; } ?>\r
+ <option value="irc.freenode.net">Freenode</option>\r
+ <option value="irc.dal.net">DALnet</option>\r
+ \r
+ </select>\r
+<?php } ?>\r
+ </span>\r
+ <button type="submit">Connect</button>\r
+ </form>\r
+ </div>\r
+ \r
+ \r
+ \r
+ <div class="windowlist">\r
+ <div class="poweredby">Powered by kiwi</div>\r
+ <ul></ul>\r
+ </div>\r
+ \r
+ <div class="userlist">\r
+ <ul></ul>\r
+ </div>\r
+ \r
+ <div class="cur_topic"></div>\r
+ \r
+ <div class="control">\r
+ <div class="msginput">\r
+ <div class="nick"><a href="#"></a>:</div>\r
+ <input type="text" name="kiwi_msginput" id="kiwi_msginput" />\r
+ </div>\r
+ <div class="plugins">\r
+ <ul>\r
+ <li><a class="load_plugin_file">Load plugin file</a></li>\r
+ <li><a class="reload_css">Reload CSS</a></li>\r
+ </ul>\r
+ </div>\r
+ </div>\r
+</div>\r
+\r
+</body>\r
+</html>
\ No newline at end of file
--- /dev/null
+<?php\r
+ print_r($_SERVER);\r
+ exit;\r
+ echo `whoami`;\r
+phpinfo();\r
--- /dev/null
+<?php\r
+\r
+ /*\r
+ * CASE: Page requested to start a session.\r
+ * 1. Attempt connect to the server\r
+ * Fail?\r
+ * End session and send error to the client\r
+ * Connected?\r
+ * Create a listening UNIX socket and send OK to the client\r
+ *\r
+ * 2. Run the IRC client class as normal, while polling for connections\r
+ * on the listening socket. Save any messages for the web client into\r
+ * a buffer.\r
+ * This is the normal running state.\r
+ *\r
+ * 3. Upon a connection on the UNIX socket, send the oldest message from\r
+ * the buffer to the web client, FIFO style and close the connection.\r
+ */\r
+ \r
+ // If -d has been sent to start this session, start in debug mode\r
+ if(in_array('-d', $argv)){\r
+ define('DEBUG', true);\r
+ } else {\r
+ define('DEBUG', false);\r
+ }\r
+ \r
+ \r
+ require(dirname(__FILE__).'/config.php');\r
+ require(dirname(__FILE__).'/common.php');\r
+ require(dirname(__FILE__).'/class_session.php');\r
+ require(dirname(__FILE__).'/class_irc.php');\r
+ require(dirname(__FILE__).'/class_ircconnection.php');\r
+ require(dirname(__FILE__).'/ircd_profiles/hybrid.php');\r
+ \r
+ \r
+ // Make sure a session id has been specified\r
+ if(!isset($argv[1]) || empty($argv[1]))\r
+ die('no_session');\r
+ \r
+ $session_id = $argv[1];\r
+ $host_key = $argv[2]; // The remote host key in memcached\r
+ \r
+ // Process any arguments\r
+ $app_args = array();\r
+ foreach($argv as $a){\r
+ $value = substr($a, 2);\r
+ switch(substr($a, 0, 2)){\r
+ case '-h':\r
+ // Client host\r
+ $app_args['remote_host'] = $value;\r
+ break;\r
+ }\r
+ }\r
+ \r
+ \r
+ // Start the unix session, and if it fails quit here\r
+ $ipc_srv = SESSIONS::clStart($session_id);\r
+ if($ipc_srv === false) die("fail '$session_id' '$host_key'");\r
+ \r
+ // Debug mode runs in terminal to see output\r
+ if(!DEBUG){\r
+ // See if we can fork our process..\r
+ $pid = pcntl_fork();\r
+ if($pid === -1){\r
+ die('Forking error');\r
+ }elseif($pid){\r
+ // Parent process\r
+ die('ok');\r
+ }else{\r
+ // Child process\r
+ fclose(STDOUT);\r
+ }\r
+ }\r
+ \r
+ //posix_setsid();\r
+\r
+ \r
+ \r
+ ############################################\r
+ ## Main process\r
+ ############################################\r
+ \r
+ $old_error_handler = set_error_handler("errorHandler");\r
+ \r
+ $bot = null; // The IRC connection\r
+ $ipc = array(); // The IPC connections to the web clients\r
+ $ipc_read = array(); // The IPC connections wanting to read the buffer\r
+ $buffer = array(); // Messages to be sent to the web client\r
+ $send_debug = false;\r
+ \r
+ deb("Starting $session_id");\r
+ \r
+ $timeout = time();\r
+ $timeout_timer = 0;\r
+ while(true){\r
+ // If there are no webclients connected, start the timer to disconnect\r
+ if(!count($ipc)){\r
+ if($timeout == 0) $timeout = time();\r
+ $timeout_timer = time() - $timeout;\r
+ if($timeout_timer >= $config['timeout']) break;\r
+ } else {\r
+ $timeout = 0;\r
+ }\r
+ \r
+ processClients();\r
+ \r
+ if($bot != null){\r
+ // $bot handles the sleep time here\r
+ $bot->Process();\r
+ } else {\r
+ usleep(5000);\r
+ }\r
+ }\r
+ \r
+ \r
+ // We've quit, lower the counter for this host\r
+ if($config['memcache_use']){\r
+ @GLOB::$mc->decrement($host_key);\r
+ }\r
+ \r
+ deb('Exiting client');\r
+ \r
+ \r
+ ############################################\r
+ ## Functions / Classes\r
+ ############################################\r
+ \r
+ \r
+ \r
+ function processClients(){\r
+ global $ipc_srv;\r
+ global $ipc;\r
+ global $ipc_read;\r
+ global $buffer;\r
+ global $timeout;\r
+ \r
+ // Check for any new web client connections...\r
+ $read = array($ipc_srv);\r
+ $write = $excep = array();\r
+ $read_changed = stream_select($read, $write, $excep, 0);\r
+ for($i=0; $i<$read_changed; $i++) {\r
+ if($read[$i] === $ipc_srv){\r
+ $ipc[] = stream_socket_accept($ipc_srv);\r
+ deb("Connection...");\r
+ }\r
+ }\r
+ \r
+ \r
+ // Check for any changed web clients..\r
+ $read = $ipc;\r
+ $read_changed = (count($read)) ? stream_select($read, $write, $excep, 0) : array();\r
+ if($read_changed){\r
+ foreach($read as $cl){\r
+ $data = fread($cl, 1024);\r
+ if(!$data){\r
+ // Web client has disconnected\r
+ deb("Removing closed socket..");\r
+ $key = array_search($cl, $ipc);\r
+ unset($ipc[$key]);\r
+ \r
+ $key = array_search($cl, $ipc_read);\r
+ if($key !== false) unset($ipc_read[$key]);\r
+ } else {\r
+ //deb('Got data: '.$data);\r
+ processClientMessage($data, $cl);\r
+ }\r
+ }\r
+ }\r
+ \r
+ \r
+ \r
+ // Send the buffer messages to any connected web clients..\r
+ // 1 message at a time...\r
+ if(count($buffer) && count($ipc_read)){\r
+ //deb(print_r($ipc_read, 1));\r
+ $msg = array_shift($buffer);\r
+ //deb("Sending '$msg' to ".count($ipc_read)." clients..");\r
+ foreach($ipc_read as $cl){\r
+ if($cl) fwrite($cl, $msg."\n");\r
+ }\r
+ }\r
+ \r
+ // The whole buffer at a time...\r
+ /*\r
+ while(count($buffer)){\r
+ $msg = array_shift($buffer);\r
+ foreach($ipc as $cl) frwite($cl, $msg);\r
+ }\r
+ */\r
+ \r
+ }\r
+ \r
+ \r
+ function processClientMessage($data, $cl){\r
+ global $config;\r
+ global $buffer;\r
+ global $bot, $ipc_read;\r
+ global $timeout;\r
+ global $send_debug;\r
+ global $app_args;\r
+ \r
+ require('dev_processClientMessage.php');\r
+ return true;\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ function errorHandler($errno, $errstr, $errfile, $errline){\r
+ $ret = '';\r
+ switch ($errno) {\r
+ case E_USER_ERROR:\r
+ $ret .= "USER [$errno] $errstr\n";\r
+ $ret .= " Fatal error on line $errline in file $errfile";\r
+ $ret .= ", PHP " . PHP_VERSION . " (" . PHP_OS . ")\n";\r
+ $ret .= "Aborting...<br />\n";\r
+ exit(1);\r
+ break;\r
+\r
+ case E_USER_WARNING:\r
+ $ret .= "WARNING on line $errline in file $errfile: [$errno] $errstr\n";\r
+ break;\r
+\r
+ case E_USER_NOTICE:\r
+ $ret .= "NOTICE on line $errline in file $errfile: [$errno] $errstr\n";\r
+ break;\r
+\r
+ default:\r
+ $ret .= "UNKOWN ERR on line $errline in file $errfile: [$errno] $errstr\n";\r
+ break;\r
+ }\r
+ \r
+ if(!empty($ret)) deb($ret); \r
+ \r
+ \r
+ /* Don't execute PHP internal error handler */\r
+ return true;\r
+ }
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ /*\r
+ * Best of all worlds\r
+ * A collection of response commands from all RFCs.\r
+ * These selected appears to work best for the most part.\r
+ */\r
+ \r
+ define('RPL_WHOISUSER', 311);\r
+ define('RPL_WHOISSERVER', 312);\r
+ define('RPL_WHOISOPERATOR', 313);\r
+ define('RPL_WHOISIDLE', 317);\r
+ define('RPL_ENDOFWHOIS', 318);\r
+ define('RPL_WHOISCHANNELS', 319);\r
+ \r
+ define('RPL_MOTD', 372);\r
+ define('RPL_WHOISMODES', 379); // Unreal. Conflicts.\r
+ \r
+ define('ERR_NOSUCHNICK', 401);\r
+ define('ERR_NOSUCHSERVER', 402);\r
+ define('ERR_NOSUCHCHANNEL', 403);\r
+ define('ERR_CANNOTSENDTOCHAN', 404);\r
+ define('ERR_TOOMANYCHANNELS', 405);\r
+ define('ERR_WASNOSUCHNICK', 406);\r
+ define('ERR_TOOMANYTARGETS', 407);\r
+ \r
+ define('ERR_LINKCHANNEL', 470);
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ $resp_code = array(\r
+ 'ERR_NOSUCHNICK' => '401',\r
+ 'ERR_NOSUCHSERVER' => '402',\r
+ 'ERR_NOSUCHCHANNEL' => '403',\r
+ 'ERR_CANNOTSENDTOCHAN' => '404',\r
+ 'ERR_TOOMANYCHANNELS' => '405',\r
+ 'ERR_WASNOSUCHNICK' => '406',\r
+ 'ERR_TOOMANYTARGETS' => '407',\r
+ );
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ $resp_code = array(\r
+ 'RPL_WELCOME' => '001',\r
+ 'RPL_YOURHOST' => '002',\r
+ );
\ No newline at end of file
--- /dev/null
+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('<h2>Kiwi IRC</h2>');
+ front.boxes.about.content.append("<p>An alternative to downloading an irc client. Kiwi IRC is the best web app you'll use for the next couple years.</p>");
+ front.boxes.about.content.append('<button class="about_close">Close</button>');
+
+ var about_info = 'UI adapted for '+agent;
+ if(touchscreen) about_info += ' touchscreen ';
+ about_info += 'usage';
+
+ front.boxes.about.content.append('<p class="info">'+about_info+'</p>');
+ front.boxes.about.content.append('<p class="revisions">Front: '+front.revision+'<br />Gateway: '+gateway.revision+'</p>');
+
+ //$(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;
+ $('<li><a class="nick" onclick="front.userClick(this);">'+mode+nick+'</a></li>').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;');
+ $('<li><a class="nick" onclick="front.userClick(this);">'+data.nick+'</a></li>').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 = '<div class="newnick box">\
+ Your new nick:<br />\
+ <form class="form_newnick">\
+ <input type="text" class="txtnewnick" /><br />\
+ <button class="butnewnick" type="submit">Change</button> <a class="link cancelnewnick">Cancel</a>\
+ </form>\
+</div>';
+ $('#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 = '<div class="list">\
+ <h2>Kiwi plugins</h2>\
+ <p>\
+ <select multiple="multiple" id="plugin_list">\
+ </select>\
+ <button id="plugins_list_unload">Unload</button>\
+ </p>\
+ </div>\
+ <div class="load">\
+ Plugin file URL:<br />\
+ <form>\
+ <input type="text" class="txtpluginfile" /><br />\
+ <button class="butnewnick" type="submit">Load..</button> <a class="link cancelpluginfile">Cancel</a>\
+ </form>\
+ </div>';
+ 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('<option value="'+txt+'">'+txt+'</option>');
+ }
+
+ // 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('<div id="'+tmp_divname+'" class="messages"></div>');
+ $('#kiwi .userlist').append('<ul id="'+tmp_userlistname+'"></ul>');
+ $('#kiwi .windowlist ul').append('<li id="'+tmp_tabname+'" onclick="front.tabviews[\''+v_name.toLowerCase()+'\'].show();">'+v_name+'</li>');
+ //$('#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 = '<div class="userbox">\
+ <input type="hidden" class="userbox_nick" value="'+front.nickStripPrefix($(item).text())+'" />\
+ <a href="#" class="userbox_query">Message</a>\
+ <a href="#" class="userbox_whois">Info</a>\
+</div>';
+ 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 = '<img src="img/redcross.png" class="tab_part" />';
+ 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 = $('<div/>').text('<'+nick+'>').html();
+ //} else {
+ var html_nick = $('<div/>').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 = $('<div/>').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 = '<b>';
+ while(msg.indexOf(String.fromCharCode(2)) != -1){
+ msg = msg.replace(String.fromCharCode(2), next);
+ next = (next=='<b>') ? '</b>' : '<b>';
+ }
+ if(next == '</b>') msg =+ '</b>';
+ }
+
+ // Wierd thing noticed by Dux0r on the irc.esper.net server
+ if(typeof msg != "string") msg = '';
+
+ // underline
+ if(msg.indexOf(String.fromCharCode(31))){
+ next = '<u>';
+ while(msg.indexOf(String.fromCharCode(31)) != -1){
+ msg = msg.replace(String.fromCharCode(31), next);
+ next = (next=='<u>') ? '</u>' : '<u>';
+ }
+ if(next == '</u>') msg =+ '</u>';
+ }
+
+
+ var line_msg = '<div class="msg '+type+'"><div class="time">'+time+'</div><div class="nick">'+html_nick+'</div><div class="text" style="'+style+'">'+msg+' </div></div>';
+ 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 = $('<div id="'+this.id+'" class="box '+classname+'"><div class="boxarea"></div></div>');
+ $('#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
--- /dev/null
+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){
+ $('<li>'+i+'</li>').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 = $('<div/>').text(nick).html();
+ var html_msg = $('<div/>').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 = '<div class="msg"><div class="time">'+time+'</div><div class="nick">'+html_nick+'</div><div class="text">'+html_msg+'</div></div>';
+ 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('<div id="'+tmp_divname+'"class="messages"></div>');
+ $('#kiwi .windowlist ul').append('<li class="window_'+v_name+'"><a onclick="front.windowShow(\''+v_name+'\');">'+v_name+'</a></li>');
+ //$('#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
--- /dev/null
+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<msgs.length; i++){
+ if(msgs[i].substring(msgs[i].length-1) != "}"){
+ gateway.buffer = msgs[i];
+ continue;
+ }
+
+ cmd = (msgs[i].charCodeAt(0) == 0) ? msgs[i].substring(1) : msgs[i];
+ console.log(cmd.charCodeAt(0)+"-"+cmd+"-"+cmd.charCodeAt(cmd.length-1));
+ obj = eval("("+cmd+")");
+ gateway.parse(obj);
+ }
+ }
+ }
+ //gateway.conn.onclose = function(evt) { alert("Conn closed"); }
+ },
+
+
+
+
+
+
+
+
+
+ /*
+ Events:
+ msg
+ action
+ server_connect
+ options
+ motd
+ notice
+ userlist
+ nick
+ join
+ topic
+ part
+ kick
+ quit
+ whois
+ syncchannel_redirect
+ debug
+ */
+ parse: function(item){
+ if(item.event != undefined){
+ $(gateway).trigger("on"+item.event, item);
+
+ switch(item.event){
+ case 'options':
+ $.each(item.options, function(name,value){
+ switch(name){
+ case 'CHANTYPES':
+ gateway.channel_prefix = value.charAt(0);
+ break;
+ case 'NETWORK':
+ gateway.network_name = value;
+ break;
+ case 'PREFIX':
+ gateway.user_prefixes = value;
+ break;
+ }
+ });
+ break;
+
+ case 'sync':
+ if(gateway.onSync && gateway.syncing){
+ gateway.syncing = false;
+ gateway.onSync(item);
+ }
+ break;
+
+ }
+ }
+ },
+
+ sendData: function(){},
+
+
+
+
+
+
+
+
+
+
+
+ sync: function(callback){
+ if(this.session_id == null) return;
+
+ var data = {
+ method: 'sync',
+ args: {}
+ };
+
+ gateway.syncing = true;
+ //$.post('poll.php', data, callback, 'json');
+ gateway.sendData(data, callback);
+ },
+
+ debug: function(){
+ var data = {
+ method: 'debug',
+ args: {}
+ };
+
+ //$.post('poll.php', data, function(){}, 'json');
+ gateway.sendData(data, callback);
+ },
+
+
+ msg: function(s_target, s_msg, callback){
+ var data = {
+ method: 'msg',
+ args: {
+ target: s_target,
+ msg: s_msg
+ }
+ };
+
+ //$.post('poll.php', data, callback, 'json');
+ gateway.sendData(data, callback);
+ },
+
+ action: function(s_target, s_msg, callback){
+ var data = {
+ method: 'action',
+ args: {
+ target: s_target,
+ msg: s_msg
+ }
+ };
+
+ //$.post('poll.php', data, callback, 'json');
+ gateway.sendData(data, callback);
+ },
+
+
+ join: function(s_channel, callback){
+ var data = {
+ method: 'join',
+ args: {
+ channel: s_channel,
+ }
+ };
+
+ //$.post('poll.php', data, callback, 'json');
+ gateway.sendData(data, callback);
+ },
+
+
+ raw: function(v_data, callback){
+ var data = {
+ method: 'raw',
+ args: {
+ data: v_data,
+ }
+ };
+
+ //$.post('poll.php', data, callback, 'json');
+ gateway.sendData(data, callback);
+ },
+
+
+ quit: function(msg){
+ alert("closing");
+ msg = "";
+ var data = {
+ method: 'quit',
+ args: {
+ message: msg,
+ }
+ };
+
+ gateway.sendData(data, callback);
+ }
+
+
+
+
+};
--- /dev/null
+
+(function($){$.toJSON=function(o)
+{if(typeof(JSON)=='object'&&JSON.stringify)
+return JSON.stringify(o);var type=typeof(o);if(o===null)
+return"null";if(type=="undefined")
+return undefined;if(type=="number"||type=="boolean")
+return o+"";if(type=="string")
+return $.quoteString(o);if(type=='object')
+{if(typeof o.toJSON=="function")
+return $.toJSON(o.toJSON());if(o.constructor===Date)
+{var month=o.getUTCMonth()+1;if(month<10)month='0'+month;var day=o.getUTCDate();if(day<10)day='0'+day;var year=o.getUTCFullYear();var hours=o.getUTCHours();if(hours<10)hours='0'+hours;var minutes=o.getUTCMinutes();if(minutes<10)minutes='0'+minutes;var seconds=o.getUTCSeconds();if(seconds<10)seconds='0'+seconds;var milli=o.getUTCMilliseconds();if(milli<100)milli='0'+milli;if(milli<10)milli='0'+milli;return'"'+year+'-'+month+'-'+day+'T'+
+hours+':'+minutes+':'+seconds+'.'+milli+'Z"';}
+if(o.constructor===Array)
+{var ret=[];for(var i=0;i<o.length;i++)
+ret.push($.toJSON(o[i])||"null");return"["+ret.join(",")+"]";}
+var pairs=[];for(var k in o){var name;var type=typeof k;if(type=="number")
+name='"'+k+'"';else if(type=="string")
+name=$.quoteString(k);else
+continue;if(typeof o[k]=="function")
+continue;var val=$.toJSON(o[k]);pairs.push(name+":"+val);}
+return"{"+pairs.join(", ")+"}";}};$.evalJSON=function(src)
+{if(typeof(JSON)=='object'&&JSON.parse)
+return JSON.parse(src);return eval("("+src+")");};$.secureEvalJSON=function(src)
+{if(typeof(JSON)=='object'&&JSON.parse)
+return JSON.parse(src);var filtered=src;filtered=filtered.replace(/\\["\\\/bfnrtu]/g,'@');filtered=filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']');filtered=filtered.replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered))
+return eval("("+src+")");else
+throw new SyntaxError("Error parsing JSON, source is not valid.");};$.quoteString=function(string)
+{if(string.match(_escapeable))
+{return'"'+string.replace(_escapeable,function(a)
+{var c=_meta[a];if(typeof c==='string')return c;c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
+return'"'+string+'"';};var _escapeable=/["\\\x00-\x1f\x7f-\x9f]/g;var _meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};})(jQuery);
\ No newline at end of file
--- /dev/null
+/*
+ Orientation stuff
+*/
+/*
+var supportsOrientationChange = "onorientationchange" in window,
+ orientationEvent = supportsOrientationChange ? "orientationchange" : "resize";
+
+window.addEventListener(orientationEvent, function() {
+ //alert('rotated' + window.orientation + " " + screen.width);
+ var or = window.orientation;
+ if(or == 90 || or == -90){
+ // We have a landscape screen
+ or = "landscape";
+ } else {
+ // We have a portrait screen
+ or = "portrait";
+ }
+
+ if(agent == "ipad" || agent == "iphone" || agent == "ipod"){
+ if(or == "landscape"){
+ width = window.height;
+ height = window.width;
+ } else {
+ width = window.width;
+ height = window.height;
+ }
+ } else {
+ width = window.width;
+ height = window.height;
+ }
+
+ //alert('adding class: '+or);
+ if(or=="landscape"){
+ $('#kiwi').removeClass("portrait");
+ } else {
+ $('#kiwi').removeClass("landscape");
+ }
+ $('#kiwi').addClass(or);
+}, false);
+*/
+
+
+
+
+
+
+
+
+/*
+ Scroll stuff
+*/
+
+function registerTouches(obj){
+ return;
+//$(document).ready(function(){
+ obj.ontouchstart = function(e) {
+ e.preventDefault();
+ document.touchx_start = e.touches[0].pageX;
+ document.touchy_start = e.touches[0].pageY;
+ }
+ obj.ontouchmove = function(e) {
+ e.preventDefault();
+ document.touchx_cur = e.touches[0].pageX;
+ document.touchy_cur = e.touches[0].pageY;
+
+ var xdiff = document.touchx_start - document.touchx_cur
+ var ydiff = document.touchy_start - document.touchy_cur
+
+ var obj = $(this);
+ if(ydiff < -20){
+ var n = obj.attr("scrollTop")+ydiff;
+ //alert("down (xdiff="+xdiff+" ydiff="+ydiff+" scrollTop="+obj.attr("scrollTop")+") "+n );
+ obj.attr("scrollTop", n);
+ } else if(ydiff > 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
--- /dev/null
+function randomString(string_length) {
+ var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
+ var randomstring = '';
+ for (var i=0; i<string_length; i++) {
+ var rnum = Math.floor(Math.random() * chars.length);
+ randomstring += chars.substring(rnum,rnum+1);
+ }
+ return randomstring;
+}
+
+String.prototype.trim = function() {
+ return this.replace(/^\s+|\s+$/g, "");
+};
+
+String.prototype.lpad = function(length, character){
+ var padding = "";
+ for(var i=0; i<length; i++) padding += character;
+ return (padding + this).slice (-length)
+};
+
+
+
+/*
+ PLUGINS
+ Each function in each object is looped through and ran. The resulting text
+ is expected to be returned.
+*/
+var plugins={};
+plugins.privmsg = [
+ {
+ name: "html_safe",
+ onprivmsg: function(inp, tabview){
+ return $('<div/>').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 = '<span style="color:red;">'+inp+'</span>';
+ }
+
+ 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 = '<img src="'+url+'" height="50" width="50" />';
+ return '<a target="_blank" rel="nofollow" href="'+ url +'">'+ img +'</a>';
+ });
+
+ 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 '<a target="_blank" rel="nofollow" href="'+ url +'">'+ nice +'</a>';
+ });
+
+ return text;
+ }
+ }
+];
\ No newline at end of file
--- /dev/null
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\r
+<html xmlns="http://www.w3.org/1999/xhtml">\r
+<head>\r
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\r
+<title>Kiwi IRC</title>\r
+<style type="text/css">\r
+* { margin:0px; padding:0px; }\r
+body {\r
+ background:#1b1b1b;\r
+ font: 14px Tahoma, Geneva, sans-serif;\r
+ color: #D4D4D4;\r
+ height:100%;\r
+}\r
+\r
+a {\r
+ color: #8D9713;\r
+ text-decoration:none;\r
+}\r
+\r
+\r
+\r
+.logo { display:block; width:264px; height:90px; text-indent:-1000px; margin:70px auto 100px; background:url(img/logo.png) no-repeat; }\r
+#login { width:665px; margin:0px auto; }\r
+#login ul { list-style:none; }\r
+#login ul li { overflow:hidden; margin-bottom:10px; }\r
+#login ul li label { float:left; text-align:right; padding-right:.5em; }\r
+#login input { border:none; }\r
+#login .more { border-top: 1px dotted #888888; }\r
+#login .more_link {\r
+ font-size:12px;\r
+ position:relative;\r
+ top:-16px;\r
+ background: url(img/more.png) no-repeat right 5px;\r
+ padding-right: 12px;\r
+ color:#888;\r
+}\r
+\r
+#login .content.top {\r
+ width:470px;\r
+ margin:30px auto 60px;\r
+}\r
+#login .content.top ul { font-size:27px; }\r
+#login .content.top label { width:40%; }\r
+#login .content.top input {\r
+ width:240px; padding:.2em .5em;\r
+ font-size:.8em; text-align:center;\r
+ background:#D4D4D4;\r
+ border-radius:8px; -moz-border-radius:8px;\r
+}\r
+#login .content.top a { display:block; text-align:right; font-size:22px; margin-top:30px; }\r
+\r
+#login .content.bottom { width:350px; margin:30px auto; display:none; }\r
+#login .content.bottom ul { font-size:20px; }\r
+#login .content.bottom label { width:25%; }\r
+#login .content.bottom input { width:216px; font-size:.8em; no-repeat; padding:.2em .5em; background:#D4D4D4; border-radius:8px; -moz-border-radius:8px; }\r
+#login .content.bottom a { display:block; text-align:right; font-size:16px; margin-top:30px; }\r
+</style>\r
+\r
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>\r
+<script type="text/javascript"> \r
+ \r
+ $(document).ready(function(){\r
+ addEvents();\r
+ $('.nick').focus();\r
+ });\r
+ \r
+ \r
+ function addEvents(){\r
+ $('.more_link').click(function(){ $('.content.bottom').slideDown('fast'); $('.network').focus(); return false; });\r
+ $('.formconnectwindow').submit(function(){ alert("logging in.."); return false; });\r
+ $('a.connect').click(function(){ $('.formconnectwindow').submit(); return false; });\r
+ }\r
+</script>\r
+</head>\r
+\r
+<body>\r
+\r
+<div class="connectwindow">\r
+ <h1 class="logo">Kiwi IRC</h1>\r
+ <div id="login">\r
+ <form class="formconnectwindow">\r
+ <div class="content top">\r
+ <ul>\r
+ <li><label for="nick">Your nickname:</label>\r
+ <input type="text" id="nick" name="nick" class="nick" /></li>\r
+ </ul>\r
+ <a class="connect" href="">Connect »</a>\r
+ </div>\r
+ \r
+ <div class="more">\r
+ <a href="" class="more_link">More</a>\r
+ <div class="content bottom">\r
+ <ul>\r
+ <li><label for="network">Server:</label>\r
+ <input type="text" id="network" name="network" class="network" value="irc.anonnet.org" /></li>\r
+ <li><label for="channel">Channel:</label>\r
+ <input type="text" id="channel" name="channel" class="channel" value="#kiwi" /></li>\r
+ </ul>\r
+ <a class="connect" href="">Connect »</a>\r
+ </div>\r
+ </div>\r
+ </form>\r
+ </div>\r
+</div>\r
+\r
+</body>\r
+</html>\r
--- /dev/null
+plugins.privmsg.lol = function(message, tab_name){
+ return message+' .. lol';
+}
\ No newline at end of file
--- /dev/null
+<?php\r
+ \r
+ require("plugin.php");\r
+ $p = new Plugin();\r
+ $p->name = "Console Logger";\r
+\r
+ $p->onprivmsg = <<<JS\r
+function(inp, tabview){\r
+ console.log("> "+tabview+": "+inp);\r
+}\r
+JS;\r
+\r
+ echo $p->export();
\ No newline at end of file
--- /dev/null
+<?php\r
+\r
+ require("plugin.php");\r
+ $p = new Plugin();\r
+ $p->name = "Dev Tools";\r
+ \r
+ $p->onload = <<<JS\r
+function(){\r
+ $('#kiwi .plugins ul').append('<li><a id="dev_tools_open">Dev Tools</a></li>');\r
+ $('#dev_tools_open').click(function(){ alert("Opening Dev Tools window"); });\r
+}\r
+JS;\r
+\r
+ $p->onunload = <<<JS\r
+function(){ alert('unloading dev tools'); $('#dev_tools_open').remove(); }\r
+JS;\r
+\r
+ echo $p->export();
\ No newline at end of file
--- /dev/null
+<?php\r
+ \r
+ class Plugin {\r
+ var $name = "Kiwi Plugin";\r
+ var $onload = "function(){}";\r
+ var $onunload = "function(){}";\r
+ var $onprivmsg = "function(inp, tabview){}";\r
+ \r
+ public function export(){\r
+ //header("Content-Type: application/json");\r
+ return json_encode(array(\r
+ 'name'=>$this->name,\r
+ 'onprivmsg'=>$this->onprivmsg,\r
+ 'onload'=>$this->onload,\r
+ 'onunload'=>$this->onunload\r
+ ));\r
+ }\r
+ }
\ No newline at end of file
--- /dev/null
+<?php\r
+ \r
+ require(dirname(__FILE__).'/config.php');\r
+ require(dirname(__FILE__).'/common.php');\r
+ require(dirname(__FILE__).'/class_session.php');\r
+ \r
+ $starttime = time();\r
+ \r
+ ############################################\r
+ ## New sessions\r
+ ############################################\r
+ \r
+ // If no session has been specified, start a new one\r
+ if(empty($_POST)){\r
+ $new_session = SESSIONS::create($_SERVER['REMOTE_ADDR']);\r
+ \r
+ // Session creation fail?\r
+ if($new_session === false){\r
+ die(gen_response(array('error'=>'session_error')));\r
+ }\r
+ \r
+ // Session create OK, yay\r
+ die(gen_response(array('session_id'=>$new_session)));\r
+ }\r
+ \r
+ \r
+ \r
+ ############################################\r
+ ## Existing sessions\r
+ ############################################\r
+ \r
+ // Quit here if a session hasn't been specified\r
+ if(!isset($_POST['sid']) || empty($_POST['sid'])){\r
+ die(gen_response(array('error'=>'session_not_set')));\r
+ }\r
+ \r
+ $session_id = $_POST['sid'];\r
+ \r
+ // Make sure the session exists\r
+ if(!SESSIONS::exists($session_id)){\r
+ die(gen_response(array('error'=>'no_session')));\r
+ }\r
+ \r
+ // Connect to the IRC session\r
+ $ses = SESSIONS::open($session_id);\r
+ if(!$ses){\r
+ die(gen_response(array('error'=>'session_error')));\r
+ }\r
+ \r
+ if(!isset($_POST['data'])){\r
+ // Read any commands to be sent to the web client\r
+ $data = array();\r
+ \r
+ /* This was used for blocking the first call which caused a wait of timeout seconds if the user quits\r
+ // Make this read block\r
+ $tmp = json_decode(trim(SESSIONS::read($ses, true)),1);\r
+ if($tmp != null) $data[] = $tmp;\r
+ */\r
+ \r
+ // Unblocked reads just incase only 1 message is actually available\r
+ $start_time = time();\r
+ while(time() - $start_time < $config['max_time_per_poll'] && count($data) == 0 && !connection_aborted()){\r
+ for($i=0; $i<$config['messages_per_poll']; $i++){\r
+ if(connection_aborted()){\r
+ deb("Connection aborted");\r
+ break;\r
+ }\r
+ deb("Polling..");\r
+ $tmp = json_decode(trim(SESSIONS::read($ses, false)),1);\r
+ if($tmp != null){\r
+ $data[] = $tmp;\r
+ } else {\r
+ break;\r
+ }\r
+ \r
+ echo " ";\r
+ flush();\r
+ }\r
+ \r
+ if(count($data) == 0) sleep(1);\r
+ }\r
+ deb("Polled");\r
+ \r
+ if(!empty($data)){\r
+ echo gen_response($data);\r
+ } else {\r
+ echo gen_response(array());\r
+ }\r
+ } else {\r
+ fwrite($ses, $_POST['data']);\r
+ }\r
+ \r
+ // We're done here, close the session connection\r
+ SESSIONS::close($ses);\r
+ \r
+ \r
+ \r
+ \r
+ \r
+ \r
+ \r
+ \r
+ \r
+ ############################################\r
+ ## Functions\r
+ ############################################\r
+ \r
+ function gen_response($data){\r
+ return json_encode($data);\r
+ }\r
--- /dev/null
+<html>\r
+\r
+<head>\r
+<script type="text/javascript">\r
+\r
+ var init_data = {};\r
+\r
+\r
+ //#nick@irc.anonnet.org:6679+/#channel,##channel,&channel\r
+ var con_string = 'nick@irc.anonnet.org:6679+/#channel,##channel,&channel';\r
+ var chans = con_string.match(/[#&]+[^ ,\007]+/g);\r
+ var addr = con_string.match(/[A-Za-z0-9.-]+(:\d+\+?)?/);\r
+\r
+ var parts = con_string.match(/^(([A-Za-z0-9-_\[\]<>]+)@)?([A-Za-z0-9.-]+(:\d+\+?)?)([#&]+[^ ,\007]+)/);\r
+ //alert(parts);\r
+ \r
+ alert('nick="'+parts[2]+'"');\r
+ alert('server="'+parts[3]+'"');\r
+ alert('channels="'+parts[5]+'"');\r
+</script>\r
+\r
+</head>\r
+\r
+<body></body>\r
+</html>
\ No newline at end of file
--- /dev/null
+<script>\r
+alert(typeof WebSocket);\r
+/*\r
+var conn = new WebSocket("ws://wuthost.net:7777/~wok/miblol/sessionname");\r
+conn.onopen = function(evt) { alert("Conn opened"); }\r
+conn.onread = function(evt) { alert("Read: " + evt.data); }\r
+conn.onclose = function(evt) { alert("Conn closed"); }\r
+\r
+conn.send("Hello World")\r
+*/\r
+</script>
\ No newline at end of file
--- /dev/null
+<?php\r
+ \r
+ /*\r
+ * This script takes a WebSocket connection from a web client, creates a\r
+ * new session and simply proxies the data between the 2 connections. No\r
+ * data processing is done here at all.\r
+ * \r
+ */\r
+ \r
+ \r
+ $msg = <<<MSG\r
+ \r
+ \r
+######################################################\r
+ Lol, Kiwi websocket support is starting.\r
+\r
+ Support: support@kiwiirc.com\r
+######################################################\r
+\r
+MSG;\r
+ echo $msg;\r
+ \r
+ \r
+ $debug = true;\r
+ \r
+ // Stores the sockets in pairs, websocket=>ircsocket\r
+ $pairs = array();\r
+ \r
+ // Stores all sockets\r
+ $soks = array();\r
+ \r
+ // Handshaken sockets\r
+ $handshakes = array();\r
+\r
+ \r
+ \r
+ require(dirname(__FILE__).'/config.php');\r
+ require(dirname(__FILE__).'/common.php');\r
+ require(dirname(__FILE__).'/class_session.php');\r
+ \r
+ \r
+ error_reporting(E_ALL);\r
+ set_time_limit(0);\r
+ ob_implicit_flush();\r
+ \r
+ $server = WebSocket($config['websocket']['bind_addr'], $config['websocket']['bind_port']);\r
+ $soks[(int)$server] = $server;\r
+ $last_dump = 0;\r
+ while(1){\r
+ echo time()." - $last_dump (".(time() - $last_dump).")\n";\r
+ if(time() - $last_dump >= 3){\r
+ $last_dump = time();\r
+ echo "\nPairs: ";\r
+ var_dump($pairs);\r
+ \r
+ echo "\nSoks: ";\r
+ var_dump($soks);\r
+ }\r
+ \r
+ $changed = $soks;\r
+ stream_select($changed, $write=NULL, $except=NULL, NULL);\r
+ \r
+ foreach($changed as $socket){\r
+ // New connection?\r
+ if($socket == $server){\r
+ $client = stream_socket_accept($server);\r
+ if($client<0){\r
+ console("socket_accept() failed"); continue;\r
+ } else {\r
+ connect($client);\r
+ }\r
+ } else {\r
+ \r
+ $buffer = fread($socket, 2048);\r
+ if($buffer === false || $buffer == ''){\r
+ // Disconnected\r
+ disconnect($socket);\r
+ } else {\r
+ //$buffer = substr($buffer, 0, strlen($buffer));\r
+ //console("INCOMING:\n".$buffer."########\n");\r
+ if(isset($handshakes[(int)$socket])){\r
+ // websocket upgrade\r
+ dohandshake($socket, $buffer);\r
+ } else {\r
+ // Data transfering..\r
+ transfer($socket, $buffer);\r
+ }\r
+ }\r
+ \r
+ }\r
+ }\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ function WebSocket($address,$port){\r
+ //$master=socket_create(AF_INET, SOCK_STREAM, SOL_TCP) or die("socket_create() failed");\r
+ $master = stream_socket_server("tcp://$address:$port") or die("socket_create() failed");\r
+ //socket_set_option($master, SOL_SOCKET, SO_REUSEADDR, 1) or die("socket_option() failed");\r
+ //socket_bind($master, $address, $port) or die("socket_bind() failed");\r
+ //socket_listen($master,20) or die("socket_listen() failed");\r
+ echo "Server Started : ".date('Y-m-d H:i:s')."\n";\r
+ echo "Master socket : ".$master."\n";\r
+ echo "Listening on : ".$address." port ".$port."\n\n";\r
+ return $master;\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ \r
+ function connect($socket){\r
+ global $soks, $pairs, $handshakes;\r
+\r
+ //$session_id = SESSIONS::create();\r
+ //if(!$session_id) return false;\r
+ \r
+ //$pairs[$socket]= SESSION::open($session_id);\r
+\r
+ $soks[(int)$socket] = $socket;\r
+ $handshakes[(int)$socket] = false;\r
+ //array_push($soks, $pairs[$socket]);\r
+ \r
+ console($socket." connection..");\r
+ }\r
+\r
+ function disconnect($sok){\r
+ global $soks, $pairs;\r
+ console("disconnected?\n");\r
+ \r
+ $pair = findPair($sok);\r
+ if($pair === false) return false;\r
+ foreach($pair as $websocket => $local_con){ \r
+ @fclose($soks[$websocket]);\r
+ unset($soks[$websocket]);\r
+ \r
+ @fclose($soks[$local_con]);\r
+ unset($soks[$local_con]);\r
+ \r
+ unset($pairs[$websocket]);\r
+ }\r
+ \r
+ console($sok." DISCONNECTED!");\r
+ }\r
+ \r
+ function transfer($sok, $buffer){\r
+ global $soks, $pairs;\r
+ console("Transfering data?\n");\r
+ \r
+ $pair = findPair($sok);\r
+ if($pair === false) return false;\r
+ \r
+ console("Transfering ".strlen($buffer)." bytes.. '".$buffer."'");\r
+ //$buffer = wrap($buffer);\r
+ foreach($pair as $websocket => $local_con){\r
+ if($sok == $soks[$websocket]){\r
+ // From websocket..\r
+ fwrite($soks[$local_con], unwrap($buffer));\r
+ break;\r
+ } elseif($sok == $soks[$local_con]){\r
+ // From irc client\r
+ fwrite($soks[$websocket], chr(0).$buffer.chr(255));\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ \r
+ function findPair($socket){\r
+ global $soks, $pairs;\r
+ console("Finding pair: ".(int)$socket."\n");\r
+ \r
+ // If it's a websocket, then this will find it..\r
+ if(isset($pairs[(int)$socket]))\r
+ return array((int)$socket=>$pairs[(int)$socket]);\r
+ \r
+ // If it's an irc client socket, then we will find it when flipped..\r
+ $flipped = array_flip($pairs);\r
+ if(isset($flipped[(int)$socket]))\r
+ return array($flipped[(int)$socket] => (int)$socket);\r
+ \r
+ return false;\r
+ }\r
+\r
+ function dohandshake($sok, $buffer){\r
+ global $handshakes, $soks, $pairs;\r
+ console("\nRequesting handshake...");\r
+ \r
+ console("Handshaking...");\r
+ /*\r
+ list($resource, $host, $origin) = getheaders($buffer);\r
+ $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .\r
+ "Upgrade: WebSocket\r\n" .\r
+ "Connection: Upgrade\r\n" .\r
+ "WebSocket-Origin: " . $origin . "\r\n" .\r
+ "WebSocket-Location: ws://" . $host . $resource . "\r\n" .\r
+ "\r\n";\r
+ */\r
+ if(!strpos($buffer, 'WebSocket-Key1:')){\r
+ $upgrade = (string)new WebSocket75($buffer);\r
+ } else {\r
+ $upgrade = (string)new WebSocket76($buffer);\r
+ }\r
+ \r
+ fwrite($sok, $upgrade.chr(0));\r
+ \r
+ // Done the handshake so remove it from the handshaking array\r
+ unset($handshakes[(int)$sok]);\r
+ \r
+ console("Done handshaking...");\r
+ \r
+ //socket_getsockname($sok, $sok_name);\r
+ $sok_name = stream_socket_get_name($sok, true);\r
+ $session_id = SESSIONS::create($sok_name);\r
+ if(!$session_id) return false;\r
+ $irc_client_sok = SESSIONS::open($session_id);\r
+ \r
+ $soks[(int)$irc_client_sok] = $irc_client_sok;\r
+ $pairs[(int)$sok] = (int)$irc_client_sok;\r
+ \r
+ fwrite($irc_client_sok, json_encode(array('method'=>'read')));\r
+ \r
+ console($sok." CONNECTED!");\r
+ return true;\r
+ }\r
+ \r
+ \r
+ \r
+ \r
+ \r
+ class WebSocket75 {\r
+ private $__value__;\r
+ \r
+ public function __toString() {\r
+ return $this->__value__;\r
+ }\r
+ \r
+ public function __construct($buffer){\r
+ list($resource, $host, $origin) = $this->getheaders($buffer);\r
+ $upgrade = "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" .\r
+ "Upgrade: WebSocket\r\n" .\r
+ "Connection: Upgrade\r\n" .\r
+ "WebSocket-Origin: " . $origin . "\r\n" .\r
+ "WebSocket-Location: ws://" . $host . $resource . "\r\n" .\r
+ "\r\n";\r
+ \r
+ $this->__value__ = $upgrade;\r
+ }\r
+ \r
+ private function getheaders($req){\r
+ $r=$h=$o=null;\r
+ if(preg_match("/GET (.*) HTTP/" ,$req,$match)){ $r=$match[1]; }\r
+ if(preg_match("/Host: (.*)\r\n/" ,$req,$match)){ $h=$match[1]; }\r
+ if(preg_match("/Origin: (.*)\r\n/",$req,$match)){ $o=$match[1]; }\r
+ return array($r,$h,$o);\r
+ }\r
+ }\r
+ \r
+ \r
+ class WebSocket76 {\r
+\r
+ /*! Easy way to handshake a WebSocket via draft-ietf-hybi-thewebsocketprotocol-00\r
+ * @link http://www.ietf.org/id/draft-ietf-hybi-thewebsocketprotocol-00.txt\r
+ * @author Andrea Giammarchi\r
+ * @blog webreflection.blogspot.com\r
+ * @date 4th June 2010\r
+ * @example\r
+ * // via function call ...\r
+ * $handshake = WebSocketHandshake($buffer);\r
+ * // ... or via class\r
+ * $handshake = (string)new WebSocketHandshake($buffer);\r
+ *\r
+ * socket_write($socket, $handshake, strlen($handshake));\r
+ */\r
+\r
+ private $__value__;\r
+\r
+ public function __construct($buffer) {\r
+ $resource = $host = $origin = $key1 = $key2 = $protocol = $code = $handshake = null;\r
+ preg_match('#GET (.*?) HTTP#', $buffer, $match) && $resource = $match[1];\r
+ preg_match("#Host: (.*?)\r\n#", $buffer, $match) && $host = $match[1];\r
+ preg_match("#Sec-WebSocket-Key1: (.*?)\r\n#", $buffer, $match) && $key1 = $match[1];\r
+ preg_match("#Sec-WebSocket-Key2: (.*?)\r\n#", $buffer, $match) && $key2 = $match[1];\r
+ preg_match("#Sec-WebSocket-Protocol: (.*?)\r\n#", $buffer, $match) && $protocol = $match[1];\r
+ preg_match("#Origin: (.*?)\r\n#", $buffer, $match) && $origin = $match[1];\r
+ preg_match("#\r\n(.*?)\$#", $buffer, $match) && $code = $match[1];\r
+ $this->__value__ =\r
+ "HTTP/1.1 101 WebSocket Protocol Handshake\r\n".\r
+ "Upgrade: WebSocket\r\n".\r
+ "Connection: Upgrade\r\n".\r
+ "Sec-WebSocket-Origin: {$origin}\r\n".\r
+ "Sec-WebSocket-Location: ws://{$host}{$resource}\r\n".\r
+ ($protocol ? "Sec-WebSocket-Protocol: {$protocol}\r\n" : "").\r
+ "\r\n".\r
+ $this->_createHandshakeThingy($key1, $key2, $code)\r
+ ;\r
+ }\r
+\r
+ public function __toString() {\r
+ return $this->__value__;\r
+ }\r
+ \r
+ private function _doStuffToObtainAnInt32($key) {\r
+ return preg_match_all('#[0-9]#', $key, $number) && preg_match_all('# #', $key, $space) ?\r
+ implode('', $number[0]) / count($space[0]) :\r
+ ''\r
+ ;\r
+ }\r
+\r
+ private function _createHandshakeThingy($key1, $key2, $code) {\r
+ return md5(\r
+ pack('N', $this->_doStuffToObtainAnInt32($key1)).\r
+ pack('N', $this->_doStuffToObtainAnInt32($key2)).\r
+ $code,\r
+ true\r
+ );\r
+ }\r
+ }\r
+ \r
+ \r
+function say($msg=""){ echo $msg."\n"; }\r
+function wrap($msg=""){ return chr(0).$msg.chr(255); }\r
+function unwrap($msg=""){ return substr($msg,1,strlen($msg)-2); }\r
+function console($msg=""){ global $debug; if($debug){ echo time().' '.trim($msg)."\n"; } }\r