[转]通过PHP curl向腾讯微博发送广播全过程
效果见这里:http://cnbang.net/lab/txwb/
由于朋友分布在不同的微博,我希望有个同步到twitter、新浪微博、腾讯微博的工具,但等这么久都没出现这样的工具,微博通似乎停工了,也不支持twitter和腾讯微博,于是想自己做个。
新浪微博和twitter都开放API,要实现同步发送很简单,腾讯微博未开放,就需要hack一下了。这次把研究过程都记录下来了,写得很长~实际上在curl实现那部分我花的时间比较多,走的弯路也多,但由于篇幅原因那部分都简省了~
0.原理
基本原理就是通过php的curl发送带cookie的请求,需要解决:
1.模拟登陆,获取cookie
2.通过curl带上cookie发送广播
最难办的就是第一个问题。
1.研究登陆方式
上http://t.qq.com/查看登录页面的源代码,看到登录表单form的onSubmit这么写:
<form id=”loginform” method=”post”>
form里没有action,说明全都是在onsubmit的js里处理的,应该找下ptui_checkValidate这里执行了啥,查看网页下面包含的js文件,搜索到ptui_checkValidate,把这一部分通过http://js.clicki.cc/美化一下,看到:
01.
function
ptui_checkValidate() {
02.
g_time.time12 =
new
Date();
03.
if
(f_u.value ==
""
)
{
04.
alert(
"您还没有输入帐号!"
);
05.
f_u.focus();
06.
return
false
07.
}
08.
.....
09.
f_p.setAttribute(
"maxlength"
,
"32"
);
10.
ajax_Submit();
11.
ptui_reportNum(g_changeNum);
12.
g_changeNum = 0;
13.
return
false
14.
}
这里是检查错误的,如果没有错误就跳到ajax_Submit了,继续找ajax_Submit,美化后看到
01.
function
ajax_Submit() {
02.
if
(!isLoadVC) {
03.
g_uin = 0
04.
}
05.
var
D =
true
;
06.
var
E = document.forms[0];
07.
var
B =
""
;
08.
for
(
var
A = 0; A
< E.length; A++) {
09.
if
(E[A].name ==
"fp"
|| E[A].type ==
"submit"
) {
10.
continue
11.
}
12.
....
13.
B +=
"&"
14.
}
15.
B +=
"fp=loginerroralert"
;
16.
var
C = document.createElement(
"script"
);
17.
C.src =
"http://ptlogin2.qq.com/login?"
+ B;
18.
document.cookie =
"login_param="
+
encodeURIComponent(login_param) +
";domain=ui.ptlogin2."
+ g_domain +
";path=/"
;
19.
document.body.appendChild(C);
20.
return
21.
}
就是给B加一堆参数然后建一个script标签去请求它,重要的几句是
1.
var
F =
""
;
2.
F +=
E.verifycode.value;
3.
F =
F.toUpperCase();
4.
B +=
md5(md5_3(E.p.value) + F)
这里F是一个验证码的东西,E.p.value是用户输入的密码,先给密码用md5_3加密再串上验证码再用md5加密,就构成后台需要验证的密码了。
而这个请求其他参数可以直接在firebug的网络里看到,随便输入点东西点登录,就会出现个这样的请求
http://ptlogin2.qq.com/login?u=@name&p=1EA1F449CB05D395E148A6C949F9E1E5&verifycode=!KL1 &aid=46000101&u1=http%3A%2F%2Ft.qq.com&ptredirect=1 &h=1&from_ui=1&dumy=&fp=loginerroralert
后来发现有些参数是没用的,可以缩短成
http://ptlogin2.qq.com/login?u=@name&p=1EA1F449CB05D395E148A6C949F9E1E5&verifycode=!KL1 &aid=46000101&fp=loginerroralert
u是用户名,verifycode是验证码,p是上面说的加密过后的东西。
2.获取验证码
那就是说只要知道验证码就可以直接发送这个网址进行验证获取cookie了。
在页面源码里找到:
<input id=”verifycode” class=”inputstyle” style=”ime-mode: disabled” maxlength=”4″ name=”verifycode” value=”!LKD” tabindex=”3″ />
那验证码应该就是这个!LKD了吧?接下来在firebug里执行md5(md5_3(“password”) + “!LKD”)获得加密后的一串东西,带入上面网址的p里,把!LKD也代入verifycode,直接在浏览器打开这个网址请求,结果不行,登录失败。
啥原因?折腾了一会,发现每次点登录它都会自动请求一个地址http://ptlogin2.qq.com/check?uin=@name&appid=46000101&r=0.6614258849969921,响应的是ptui_checkVC(’0′,’!51B’);这样的数据,喔,soga,验证码是要更新的,源码HTML里的那个验证码是没用的,这个js返回的才是最新的验证码。把这个验证码拿出来重新做一次上面的操作,在浏览器输入修改了那些参数的网址,结果成功了,cookie在这时候写入,再次打开t.qq.com已经登录了。
3.登陆总结
后来发现,不需要打开登录页面,登录过程可以简化成两步,
1.请求http://ptlogin2.qq.com/check?uin=@name&appid=46000101&r=0.6614258849969921
获取验证码
2.加密后把数据代入请求http://ptlogin2.qq.com/login?u=@name&p=1EA1F449CB05D395E148A6C949F9E1E5&verifycode=!KL1
&aid=46000101&fp=loginerroralert 就完成登录了。
4.找发送广播网址
知道怎么模拟登录了,接下来找找发送广播的网址。
发送框不是普通的表单,而是都用js处理的,得在js里去找那个网址。可以像上面寻找登陆框处理函数那样去寻找网址,这里只是寻找网址,也有另外的方法,就是随便发一条广播,在firebug的网络里看看请求了哪些网址,一看发现请求了这个网址http://t.qq.com/publish.php
带了content参数,于是测试一下看能不能在firebug的控制台发送条广播:
01.
var
b = UI.xmlHttp(),
02.
a =
{data:{
"content"
:
"发广播啊发广播"
},url:
"publish.php"
}
03.
b.onreadystatechange =
function
() {
04.
if
(b.readyState == 4 && b.status == 200)
try
{
05.
console.info(
"df"
);
06.
}
catch
(d) {}
else
return
b
07.
};
08.
b.open(
"POST"
, a.url,
true
);
09.
b.setRequestHeader(
"Content-type"
,
"application/x-www-form-urlencoded"
);
10.
var
c = [];
11.
for
(
var
e
in
a.data) c.push(e +
"="
+
encodeURIComponent(a.data[e]));
12.
a.data =
c.join(
"&"
)
13.
b.send(a.data);
结果发送成功了,这个地址没错,参数也就content一个。好,接下来就用php的curl执行这整个过程。
5.用curl实现登陆
先研究半天curl怎么获取cookie和发送cookie的,最后照这里做了:http://coderscult.com/php/php-curl/2008/05/20/php-curl-cookies-example/ 不明白为啥要保存成一个文件这么麻烦,不管,先把效果做出来再再说~
照上面写的,先执行
1.
$ch
= curl_init();
2.
curl_setopt(
$ch
, CURLOPT_URL,
"http://ptlogin2.qq.com/check?uin=@bang&appid=46000101&r=0.6614258849969921"
);
3.
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, 1);
4.
$response
= curl_exec (
$ch
);
接着提取里面的验证码
1.
$pat
=
'/\'(!(.*?))\'/i'
;
2.
preg_match_all(
$pat
,
$response
,
$m
);
3.
$verifycode
=
$m
[1][0];
js里密码加密部分(B += md5(md5_3(E.p.value) + F))直接在网上找到PHP对应的加密方法,原来md5_3就是执行3次md5,囧,原来QQ其他产品也常用这种方式加密。
接着通过这个验证码和加密后的代码代入请求第二个网址
1.
$ch
= curl_init();
2.
curl_setopt(
$ch
, CURLOPT_COOKIEJAR,
$ckfile
);
3.
curl_setopt(
$ch
, CURLOPT_URL,
"http://ptlogin2.qq.com/login?u=@bang&;p=$code&verifycode=$verifycode&aid=46000101&fp=loginerroralert"
);
4.
curl_setopt
(
$ch
,
CURLOPT_RETURNTRANSFER, true);
5.
$result
= curl_exec (
$ch
);
结果发现不行,根本没登陆,怎么回事?卡了会,再到t.qq.com的登陆页面兜转一会,发现返回验证码的那个网址响应头信息里是带有SetCookie的,喔,那就得要把第一个请求的cookie也保存下来传给第二个网址了。结果试验后显示登陆成功了。
6.实现广播
接下来就可以发送广播了?
01.
$ch
= curl_init ();
02.
$params
=
"content=测试&;viewModel=0"
;
03.
curl_setopt(
$ch
, CURLOPT_URL,
"http://t.qq.com/publish.php"
);
04.
curl_setopt
(
$ch
, CURLOPT_COOKIEFILE,
$ckfile2
);
//获取第二个网址curl保存的cookie
05.
curl_setopt
(
$ch
,
CURLOPT_RETURNTRANSFER, true);
06.
curl_setopt(
$ch
, CURLOPT_POST, 1);
07.
curl_setopt(
$ch
, CURLOPT_POSTFIELDS,
$params
);
08.
$output
= curl_exec (
$ch
);
09.
echo
$output
;
结果发现这样又不行,显示发送失败,在这里卡了很久,怎么回事呢。试验了很多很多方法,最后试验到Request Header,这里没加Header,在firebug里看到在t.qq.com发送时它附带了许多Header过去,于是加一些Header试试,结果加了”Referer:http://t.qq.com/”这个Header就大功告成了,想来后台是得判断一下Referer~~终于成功了,研究了这么久,真兴奋啊,接下来就是加加外壳了。不过以这样的方式登陆和发送,腾讯微博要是有小小的改动就挂了,希望它开放API前别改了~也希望早点开放API~