webqq机器人,实现支持虚拟空间,异步后台执行,使用了fsockopen 函数模拟浏览器请求,并把相关cookie保存,在下一次请求时再发送到原始的header头里,目前代码还未很完善,也需要web登陆窗口的支持。
webQQ登陆流程:QQ登陆窗口 输入QQ号后验证QQ相关信息,查看是否需要返回验证码,如果需要则返回验证码,输入密码后提交登陆时生成相关的加密串,并提交到腾讯的网站用户登陆验证接口,此时仅仅是登陆到了腾讯网站,再提交一次到webqq登陆网关实现QQ的登录,返回登录成功后就获取相关的cookie。
异步挂起实现:使用fsockopen访问启动qq在线的web地址,无需等待直接返回,qq在线的web地址就使用while来获取qq返回的聊天信息(qq.class.php main 方法)
1.qq.class.php
<?php set_time_limit(0); ignore_user_abort(true); require_once('./common.php'); require_once('./db.class.php'); define('IN_QQ',true); class qqclient{ private $setopt = array( 'port'=>80, 'host'=>'d.web2.qq.com', 'userAgent'=>'Mozilla/5.0 (Windows NT 6.1; rv:10.0) Gecko/20100101 Firefox/10.0', 'timeOut'=>16, 'useCookie'=>true, 'getmsg'=>'/channel/poll2', 'putmsg'=>'', 'uid'=>'', 'pwd'=>'', 'checkhost'=>'ptlogin2.qq.com', 'runTime'=>300, ); public function __set($property,$value){ $this->$property = $value; if($property == 'u')$this->setopt['uid'] = $this->u; } public function __get($property){ return $this->$property; } function __construct($setopt=array()){ $this->setopt = array_merge($this->setopt,$setopt); if(@$setopt['uid'])$this->u = $setopt['uid']; } public function check($host = null , $url = null , $port = 80){ $host = isset($host) ? $host : $this->setopt['checkhost']; $url = isset($url) ? $url : '/check?uin=' . $this->setopt['uid'] . '&appid=1003903&r='.rand(); $port = $port ? $port : $this->setopt['port']; $this->check = $this->get( $host , $url , $port ); return $this; } public function verify(){ $out = $this->_parsmResult($this->body); $this->verify = array(); $this->verify['state'] = $out[1][0]; $this->verify['code'] = $out[1][1]; return $this; } public function verifycode(){ $t = $this->get('captcha.qq.com','/getimage?aid=1003903&r='. rand() .'&uin='. $this->setopt['uid'] .'&vc_type='.$this->setopt['code']); if($t){ header('Content-Type: image/jpeg'); echo $this->body; } } public function login(){ $this->info = array(); if(getCache('live'.$this->u,time()+86400)){ $this->info['statusCode'] = '300'; $this->info['message'] = 'QQ已经登录了!请勿反复登录'; }else{ $url = "/login?u={$this->u}&p={$this->p}&verifycode={$this->v}&remember_uin=1&aid=1003903&u1=http%3A%2F%2Fweb2.qq.com%2"; $url .= "Floginproxy.html%3Fstrong%3Dtrue&h=1&ptredirect=0&ptlang=2052&from_ui=1&pttype=1&dumy=&fp=loginerroralert"; $this->setclientid()->get('ptlogin2.qq.com',$url); $msg = $this->_parsmResult($this->body); if($msg[1][0]==0){ $this->loginqq(); }else{ $this->info['statusCode'] = '300'; $this->info['message'] = $msg[1][count($msg[1])-1]; } } return $this->info; } public function progress($host,$url,$port = 80){ $this->get($host,$url,$port,'',true); } public function main(){ $clientid = $this->getclientid(); $psessionid = $this->getResult('psessionid'); $data = array( 'r'=>'{"clientid":"'.$clientid.'","psessionid":"'.$psessionid.'","key":0,"ids":[]}', 'clientid'=>$clientid, 'psessionid'=>$psessionid, ); writeCache('live'.$this->u,time()); $this->msgid = $this->generateCode(7); $this->rc = $this->generateCode(5); $time = time(); while(true){ $this->flushmsg(); try{ if(!getCache('live'.$this->u,time()+86400))exit; $msg = $this->post('d.web2.qq.com','/channel/poll2',80,$data); if($msg){ $header = split("\r\n\r\n",$msg);$this->header = $header[0];unset($header[0]);$msg = join('',$header); $msg = json_decode($msg); if(@$msg->retcode == 0){ $this->putmsg($msg); }elseif(@$msg->retcode == 103){ purgeCache('live'.$this->u); exit; } } }catch(Exception $e){ } if( (time() - $time ) > $this->setopt['runTime'] ){ $this->progress($this->phost,$this->pmain,$this->pport);exit;} } } private function putmsg($msg){ $clientid = $this->getclientid(); $psessionid = $this->getResult('psessionid'); foreach($msg->result as $value){ if($value->poll_type == 'message'){ $this->msgid += 1; $from_uin = $value->value->from_uin; unset($value->value->content[0]); $sendmsg = $this->getKeyword(preg_replace("/\[.*?\]/si","",join('',$value->value->content))); $tmp["to"] = $from_uin; $tmp["face"] = 732; $tmp["content"] = '["'.addslashes($sendmsg).'",["font",{"name":"微软雅黑","size":"10","style":[0,0,0],"color":"000000"}]]'; $tmp["msg_id"] = $this->msgid; $tmp["clientid"] = $clientid; $tmp["psessionid"] = $psessionid; $data['r'] = json_encode($tmp);; $data['clientid'] = $clientid; $data['psessionid'] = $psessionid; $e = $this->post('d.web2.qq.com','/channel/send_buddy_msg2',80,$data); }elseif($value->poll_type == 'kick_message'){ purgeCache('live'.$this->u); exit; } } } private function getKeyword($key){ $key = trim($key); $db = new mysql(); $preg = array(); if($key){ if(preg_match_all("/^((\w+):?)([\w\W]*)/i",$key,$preg)){ $query = $db->findOne('qqrobot_key','files,stype,cotents',"qq='{$this->u}' and keyword='".$preg[2][0]."'"); }else{ $query = $db->findOne('qqrobot_key','files,stype,cotents',"qq='{$this->u}' and keyword='".addslashes($key)."'"); } } if(@$query){ if(@$query['stype'] == 'normal') return $query['cotents']; else{ if(is_file(dirname(__FILE__).'/plugin/'.$query['files'])){ try{ if(@$preg[1][0] == @$preg[2][0]){ return $query['cotents']; } require_once('./plugin/'.$query['files']); }catch(Exception $e){ } } if($query['cotents']) return $query['cotents']; } } $query = $db->findOne('qqrobot_key','files,stype,cotents',"qq='{$this->u}' and keyword='defalut'"); return @$query['cotents'] ? $query['cotents']:"完了完了...我什么都不知道,怎么办呀!! ~~~~(>_<)~~~~ "; } private function flushmsg(){ $this->rc += 1; $this->get('web.qq.com','/get_msg_tip?uin=&tp=1&id=0&retype=1&rc='.$this->rc.'&lv=3&t='.time()); } private function loginqq(){ $cookie = $this->_getCookies(); list($t,$ptwebqq) = split('=',$cookie['ptwebqq']); $clientid = $this->getclientid(); $data= array( 'r' => '{"status":"callme","ptwebqq":"'.$ptwebqq.'","passwd_sig":"","clientid":"'.$clientid.'","psessionid":""}', 'clientid'=> $clientid, 'psessionid' => null, ); $data2 = ""; $this->post('d.web2.qq.com','/channel/login2',80,$data); $body = json_decode($this->body); if($body->retcode == 0){ writeCache('result'.$this->u,$body); $this->progress($this->phost,$this->pmain,$this->pport); $this->info['statusCode'] = '200'; $this->info['message'] = '登录成功!'; $this->info['callbackType'] = 'sucessCurrent'; }else{ $this->info['statusCode'] = '300'; $this->info['message'] = '未知错误!'; } return $this->info; } private function setclientid($uid = null){ $uid = $uid ? $uid : $this->setopt['uid']; writeCache('clientid'.$uid,$this->generateCode(8)); return $this; } private function getclientid($uid = null){ $uid = $uid ? $uid : $this->setopt['uid']; return getCache('clientid'.$uid,time()+86400); } private function getResult($key){ $data = getCache('result'.$this->u,time()+86400); if($data && $data->result->$key){ return $data->result->$key; } return null; } private function _parsmResult($text){ if(!$text)return ''; preg_match_all("/\'(.*?)\'/",$text,$out); return $out; } private function get($host,$url,$port = 80,$content=array(),$quick=false){ return $this->_request('get',$host,$url,$port,$content,$quick); } private function post($host,$url,$port = 80,$content=array(),$quick=false){ return $this->_request('post',$host,$url,$port,$content,$quick); } private function _request($type,$host,$url,$port = 80,$content=array(),$quick=false){ $type = $type == 'get' ? 'GET' : 'POST'; $port = $port ? $port : $this->setopt['port']; $content = $this->_parsmEncode($content); $i = $this->generateCode('20000'); $fp[$i] = fsockopen($host, $port, $errno, $errstr); if(!$fp[$i]){ $this->errno = $errno ; $this->errstr = $errstr ; return false; } fputs($fp[$i], "$type $url HTTP/1.0\r\n"); fputs($fp[$i], "Host: $host\r\n"); if($type == 'POST'){ fputs($fp[$i], "Content-Type: application/x-www-form-urlencoded; charset=UTF-8\r\n"); fputs($fp[$i], "Content-length: " . strlen($content) . "\r\n"); } fputs($fp[$i], "Referer: http://d.web2.qq.com/proxy.html?v=20110331002&callback=2\r\n"); fputs($fp[$i], "Accept-Language: zh-cn,zh;q=0.5\r\n"); fputs($fp[$i], "Cookie: ".$this->getCookies()."\r\n"); fputs($fp[$i], "User-Agent: " . $this->setopt['userAgent'] . "\r\n"); fputs($fp[$i], "Connection: keep-alive\r\n\r\n"); if($type == 'POST')fputs($fp[$i], "$content\\n");else fputs($fp[$i], "\\n"); if($type == 'POST') stream_set_timeout($fp[$i],$this->setopt['timeOut']); if(!$quick){ $res = ''; while (!feof($fp[$i])) $res .= fgets($fp[$i], 128); $info=stream_get_meta_data($fp[$i]); fclose($fp[$i]); if(!$info['timed_out']) { $header = split("\r\n\r\n",$res); $this->header = $header[0]; unset($header[0]); $this->body = join('',$header); $this->parseCookies(); }else{ $this->header = null; $this->body = null; } return $res; }else{ fclose($fp[$i]); return true; } } private function parseCookies(){ $header = $this->header ? split("\r\n",$this->header) : array() ; $cookie = array(); foreach($header as $val){ if(preg_match("/^Set-Cookie/",$val)){ $t = split('; ',str_replace("Set-Cookie: ","",$val)); parse_str($t[0],$t); $t2 = array_keys($t); $cookie[$t2[0]] = $t2[0].'='.$t[$t2[0]]; } } $cache = getCache($this->setopt['uid'],time()+850); $cache = $cache ? array_merge($cache , $cookie) : $cookie ; writeCache($this->setopt['uid'],$cache); return true; } private function _getCookies(){ return getCache($this->setopt['uid'],time()+850); } private function getCookies(){ $cookie = $this->_getCookies(); return $this->cookies = $cookie ? join('; ',$cookie):''; } private function _parsmEncode($params,$isRetStr=true,$encode = false){ if(!is_array($params))return $params; $result = $params; if($encode){ foreach($params as $key=>$value){ $value = urlencode($value); $result[$key] = $value; } } return $isRetStr ? http_build_query($result) : $result; } private function generateCode($length=6) { $chars = "0123456789"; $code = ""; while (strlen($code) < $length) { $code .= $chars[rand(0,strlen($chars)-1)]; } return $code; } } ?>
2.common.php
<?php define('Root',dirname(__FILE__)); //公共函数 /** * safe_file_put_contents() 一次性完成打开文件,写入内容,关闭文件三项工作,并且确保写入时不会造成并发冲突 * * @param string $filename * @param string $content * @param int $flag * * @return boolean */ function safe_file_put_contents($filename, & $content) { $fp = fopen($filename, 'wb'); if ($fp) { flock($fp, LOCK_EX); fwrite($fp, $content); flock($fp, LOCK_UN); fclose($fp); return true; } else { return false; } } if (!function_exists('file_put_contents')) { function file_put_contents($filename, & $content) { return safe_file_put_contents($filename, $content); } } /** * safe_file_get_contents() 用共享锁模式打开文件并读取内容,可以避免在并发写入造成的读取不完整问题 * * @param string $filename * * @return mixed */ function safe_file_get_contents($filename) { $fp = fopen($filename, 'rb'); if ($fp) { flock($fp, LOCK_SH); clearstatcache(); $filesize = filesize($filename); if ($filesize > 0) { $data = fread($fp, $filesize); } else { $data = false; } flock($fp, LOCK_UN); fclose($fp); return $data; } else { return false; } } /** * 创建一个目录树 * * 用法: * <code> * mkdirs('/top/second/3rd'); * </code> * * @param string $dir * @param int $mode */ function mkdirs($dir, $mode = 0777) { if (!is_dir($dir)) { mkdirs(dirname($dir), $mode); return mkdir($dir, $mode); } return true; } /** * 读取指定缓存的内容,如果缓存内容不存在或失效,则返回 false * * example: * <code> * $cacheId = 'my_cache_id'; * if (!($data = FLEA::getCache($cacheId))) { * $data = 'Data'; * FLEA::writeCache($cacheId, $data); * } * </code> * * 如果 $cacheIdIsFilename 参数为 true,则生成的缓存文件会以 $cacheId 参数作为文件名。 * 基于安全原因,尽量不要将 $cacheIdIsFilename 参数设置为 true。 * * $time 参数默认为缓存内容的有效期。其计算依据是以缓存文件的最后更新时间为准(也就是最后一次更新该缓存内容的时间)。 * * 如果 $timeIsLifetime 为 false,则 $time 参数表示用于和缓存文件最更新时间进行比较的依据。 * 如果 $time 指定的时间早于缓存文件的最后更新时间,则判断缓存内容为有效。 * * @param string $cacheId 缓存ID,不同的缓存内容应该使用不同的ID * @param int $time 缓存过期时间或缓存生存周期 * @param boolean $timeIsLifetime 指示 $time 参数的作用 * @param boolean $cacheIdIsFilename 指示是否用 $cacheId 作为文件名 * * @return mixed 返回缓存的内容,缓存不存在或失效则返回 false */ function getCache($cacheId, $time = 900, $timeIsLifetime = true, $cacheIdIsFilename = false) { $cacheDir = Root.'/cache/'.substr(md5($cacheId),0,2).'/'.substr(md5($cacheId),1,2).'/'; if(!is_dir($cacheDir)){ mkdirs($cacheDir);} if (is_null($cacheDir)) { return false; } if ($cacheIdIsFilename) { $cacheFile = $cacheDir . '/' . preg_replace('/[^a-z0-9\-_]/i', '_', $cacheId) . '.php'; } else { $cacheFile = $cacheDir . '/' . md5($cacheId) . '.php'; } if (!file_exists($cacheFile)) { return false; } if ($timeIsLifetime && $time == -1) { $data = safe_file_get_contents($cacheFile); $hash = substr($data, 16, 32); $data = substr($data, 48); if (crc32($data) != $hash || strlen($hash) != 32) { return false; } return $data !== false ? unserialize($data) : false; } $filetime = filemtime($cacheFile); if ($timeIsLifetime) { if (time() >= $filetime + $time) { return false; } } else { if ($time >= $filetime) { return false; } } $data = safe_file_get_contents($cacheFile); $hash = substr($data, 16, 32); $data = substr($data, 48); if (crc32($data) != $hash || strlen($hash) != 32) { return false; } return $data !== false ? unserialize($data) : false; } /** * 将变量内容写入缓存 * * example: * <code> * $data = .....; // 要缓存的数据,可以是任何类型的值 * // cache id 用于唯一指定一个缓存数据,以便稍后取出缓存数据 * $cacheId = 'data_cahce_1'; * FLEA::writeCache($cacheId, $data); * </code> * * @param string $cacheId * @param mixed $data * @param boolean $cacheIdIsFilename * * @return boolean */ function writeCache($cacheId, $data, $cacheIdIsFilename = false) { $cacheDir = Root.'/cache/'.substr(md5($cacheId),0,2).'/'.substr(md5($cacheId),1,2).'/'; if(!is_dir($cacheDir)){ mkdirs($cacheDir);} if (is_null($cacheDir)) { return false; } if ($cacheIdIsFilename) { $cacheFile = $cacheDir . '/' . preg_replace('/[^a-z0-9\-_]/i', '_', $cacheId) . '.php'; } else { $cacheFile = $cacheDir . '/' . md5($cacheId) . '.php'; } $data = serialize($data); $prefix = '<?php die(); ?> '; $hash = sprintf('% 32d', crc32($data)); $data = $prefix . $hash . $data; if (!safe_file_put_contents($cacheFile, $data)) { return false; } else { return true; } } /** * 删除指定的缓存内容 * * @param string $cacheId * @param boolean $cacheIdIsFilename * * @return boolean */ function purgeCache($cacheId, $cacheIdIsFilename = false) { $cacheDir = Root.'/cache/'.substr(md5($cacheId),0,2).'/'.substr(md5($cacheId),1,2).'/'; if(!is_dir($cacheDir)){ mkdirs($cacheDir);} if (is_null($cacheDir)) { return false; } if ($cacheIdIsFilename) { $cacheFile = $cacheDir . '/' . preg_replace('/[^a-z0-9\-_]/i', '_', $cacheId) . '.php'; } else { $cacheFile = $cacheDir . '/' . md5($cacheId). '.php'; } if (file_exists($cacheFile)) { return unlink($cacheFile); } return true; } ?>
3.db.class.php
<?php define('db_host','数据库服务器'); //数据库服务器 define('db_user','roto'); //数据库用户名 define('dbpw','ubkkE'); //数据库密码 define('dbname','snet'); //数据库名 define('dbcharset','utf8'); //数据库编码,不建议修改 class mysql { var $querynum = 0; var $link; var $sql; private $dbhost = db_host; private $dbname = dbname; private $dbuser = db_user; private $dbpw = dbpw; private $dbcharset = dbcharset; function mysql($dbhost='', $dbuser='', $dbpw='', $dbname = '', $pconnect = 0) { $dbhost==''?$dbhost=$this->dbhost:$dbhost; $dbuser==''?$dbuser=$this->dbuser:$dbuser; $dbpw==''?$dbpw=$this->dbpw:$dbpw; $dbname==''?$dbname=$this->dbname:$dbname; if($pconnect) { if(!$this->link = @mysql_pconnect($dbhost, $dbuser, $dbpw)) { $this->halt('Can not connect to MySQL server'); } } else { if(!$this->link = @mysql_connect($dbhost, $dbuser, $dbpw)) { $this->halt('Can not connect to MySQL server'); } } if($this->version() > '4.1') { if($this->dbcharset) { mysql_query("SET character_set_connection=$this->dbcharset, character_set_results=$this->dbcharset, character_set_client=binary", $this->link); } if($this->version() > '5.0.1') { mysql_query("SET sql_mode=''", $this->link); } } if($dbname) { mysql_select_db($dbname, $this->link); } } function select_db($dbname) { return mysql_select_db($dbname, $this->link); } function fetch_array($query, $result_type = MYSQL_ASSOC) { return mysql_fetch_array($query, $result_type); } function fetch_all($query, $result_type = MYSQL_ASSOC) { $result = array(); $num = 0; while($ret = mysql_fetch_array($query, $result_type)) { $result[$num++] = $ret; } return $result; } function fetch_row($query) { $query = mysql_fetch_row($query); return $query; } function result($query, $row) { $query = @mysql_result($query, $row); return $query; } function query($sql, $type = '') { $func = $type == 'UNBUFFERED' && @function_exists('mysql_unbuffered_query') ? 'mysql_unbuffered_query' : 'mysql_query'; if(!($query = $func($sql, $this->link)) && $type != 'SILENT') { $this->halt('MySQL Query Error: ', $sql); } $this->querynum++; return $query; } function insert($table,$row){ if(!$row)return null; $row = $this->_escape($row); $tmp = $tmp2 = array(); foreach($row as $key => $val){ $tmp[] = $key; $tmp2[] = $val; } $sql = "insert into $table (".join(',',$tmp).") values ('".join('\',\'',$tmp2)."')"; return $this->query($sql); } function update($table,$row,$where= null){ if(!$row || !$table)return null; $row = $this->_escape($row); $tmp = array(); foreach($row as $key => $val){ $tmp[] = $key."='$val'"; } $sql = "update $table set ".join(',',$tmp).""; $sql = $where ? $sql.' where '.$where:$sql; return $this->query($sql); } function find($table,$field = '*',$where=null){ if(!$table) return null; $sql = $this->bulidsql($table,$field,$where); $query = $this->query($sql); $data = null; while($row = $this->fech($query)) $data[] = $row; return $data; } function findOne($table,$field = '*',$where=null){ if(!$table) return null; $sql = $this->bulidsql($table,$field,$where); return $this->fech($this->query($sql)); } function bulidsql($table,$field = '*',$where=null){ $sql = 'select '.$field.' from '.$table; $sql = $where ? $sql.' where '.$where:$sql; $this->sql = $sql; return $sql; } function fech($query,$type = MYSQL_ASSOC){ return mysql_fetch_array($query, $type); } function _escape($row){ if(is_array($row)){ foreach($row as $key => $val){ $row[$key] = addslashes($val); } } return $row; } function affected_rows() { return mysql_affected_rows($this->link); } function error() { return (($this->link) ? mysql_error($this->link) : mysql_error()); } function errno() { return intval(($this->link) ? mysql_errno($this->link) : mysql_errno()); } function num_rows($query) { $query = mysql_num_rows($query); return $query; } function num_fields($query) { return mysql_num_fields($query); } function free_result($query) { return mysql_free_result($query); } function insert_id() { return ($id = mysql_insert_id($this->link)) >= 0 ? $id : $this->result($this->query("SELECT last_insert_id()"), 0); } function fetch_fields($query) { return mysql_fetch_field($query); } function version() { return mysql_get_server_info($this->link); } function close() { return mysql_close($this->link); } function halt($message = '', $sql = '') { echo $message . ' ' . $sql; exit; } } ?>