通过PHP curl向腾讯微博发送广播全过程

2010-8-15

效果见这里: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/美化一下,看到:

function ptui_checkValidate() {
    g_time.time12 = new Date();
    if (f_u.value == "") {
        alert("您还没有输入帐号!");
        f_u.focus();
        return false
    }
.....
    f_p.setAttribute("maxlength", "32");
    ajax_Submit();
    ptui_reportNum(g_changeNum);
    g_changeNum = 0;
    return false
}

这里是检查错误的,如果没有错误就跳到ajax_Submit了,继续找ajax_Submit,美化后看到

function ajax_Submit() {
    if (!isLoadVC) {
        g_uin = 0
    }
    var D = true;
    var E = document.forms[0];
    var B = "";
    for (var A = 0; A < E.length; A++) {
        if (E[A].name == "fp" || E[A].type == "submit") {
            continue
        }
       ....
        B += "&"
    }
    B += "fp=loginerroralert";
    var C = document.createElement("script");
    C.src = "http://ptlogin2.qq.com/login?" + B;
    document.cookie = "login_param=" + encodeURIComponent(login_param) + ";domain=ui.ptlogin2." + g_domain + ";path=/";
    document.body.appendChild(C);
    return
}

就是给B加一堆参数然后建一个script标签去请求它,重要的几句是

var F = "";
F += E.verifycode.value;
F = F.toUpperCase();
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的控制台发送条广播:

    var b = UI.xmlHttp(),
    a = {data:{"content":"发广播啊发广播"},url:"publish.php"}
        b.onreadystatechange = function() {
            if (b.readyState == 4 && b.status == 200) try {
                console.info("df");
            } catch(d) {} else return b
        };
        b.open("POST", a.url, true);
        b.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

            var c = [];
            for (var e in a.data) c.push(e + "=" + encodeURIComponent(a.data[e]));
            a.data = c.join("&")

        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/ 不明白为啥要保存成一个文件这么麻烦,不管,先把效果做出来再再说~

照上面写的,先执行

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,"http://ptlogin2.qq.com/check?uin=@bang&appid=46000101&r=0.6614258849969921");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$response = curl_exec ($ch);

接着提取里面的验证码

$pat = '/\'(!(.*?))\'/i';
preg_match_all($pat, $response, $m);
$verifycode = $m[1][0];

js里密码加密部分(B += md5(md5_3(E.p.value) + F))直接在网上找到PHP对应的加密方法,原来md5_3就是执行3次md5,囧,原来QQ其他产品也常用这种方式加密。

接着通过这个验证码和加密后的代码代入请求第二个网址

$ch = curl_init();
curl_setopt($ch, CURLOPT_COOKIEJAR, $ckfile);
curl_setopt($ch, CURLOPT_URL,"http://ptlogin2.qq.com/login?u=@bang&;p=$code&verifycode=$verifycode&aid=46000101&fp=loginerroralert");
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec ($ch);

结果发现不行,根本没登陆,怎么回事?卡了会,再到t.qq.com的登陆页面兜转一会,发现返回验证码的那个网址响应头信息里是带有SetCookie的,喔,那就得要把第一个请求的cookie也保存下来传给第二个网址了。结果试验后显示登陆成功了。

6.实现广播

接下来就可以发送广播了?

$ch = curl_init ();
$params = "content=测试&;viewModel=0";
curl_setopt($ch, CURLOPT_URL,"http://t.qq.com/publish.php");
curl_setopt ($ch, CURLOPT_COOKIEFILE, $ckfile2); //获取第二个网址curl保存的cookie
curl_setopt ($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
$output = curl_exec ($ch);
echo $output;

结果发现这样又不行,显示发送失败,在这里卡了很久,怎么回事呢。试验了很多很多方法,最后试验到Request Header,这里没加Header,在firebug里看到在t.qq.com发送时它附带了许多Header过去,于是加一些Header试试,结果加了”Referer:http://t.qq.com/”这个Header就大功告成了,想来后台是得判断一下Referer~~终于成功了,研究了这么久,真兴奋啊,接下来就是加加外壳了。不过以这样的方式登陆和发送,腾讯微博要是有小小的改动就挂了,希望它开放API前别改了~也希望早点开放API~

分类:技术文章 Tags:
评论

2010年8月15日 15:11

一个技术小白表示,很多技术名词不懂,想问一下,不知道能不能放出成品,谢谢!

希望腾讯能开放API,这样同步腾讯微博就容易多了。

2010年8月15日 15:15

@陈佳 成品第一行就写了 同步三个微博的还没做

2010年8月15日 15:28

赞!用户名填啥?QQ号还是?

2010年8月15日 15:29

@lichgo 微博账号 @后面的

2010年8月15日 15:39

@bang 有没有php文件下载,我自己也做来玩玩。

2010年8月15日 15:56

先写评论再看文章
我也在找一个可以同步到腾讯微博的东西,不过我觉得其实原理很简单,只是自己懒的去测试去写出来,总想用别人写的现成的。还有麻花藤说腾讯微博马上会出API了,也许也是在等他的API
还有,其他微博同步可以用tw2other,只是作者好像很久没更新过了,我在twitter上@他,没鸟我

2010年8月15日 17:14

bang是否考虑弄个tw2other plus?

2010年8月15日 18:49

先研究半天curl怎么获取cookie和发送cookie的,

2010年8月15日 20:24

使用php自有函数3次md5后+效验码再次md5,得到的结果和抓包得到的p值不同,无法登陆成功。
效验码使用同一值验证。

2010年8月15日 21:08

有没有完成的代码下载,可以联系下tw2other的作者,让他把腾讯微波的功能加进去…

2010年8月15日 21:32

@海风 是这样的
function preprocess($password,$verifycode) {
return strtoupper(md5(md5_3($password).strtoupper($verifycode)));
}
function md5_3($str) {
return strtoupper(md5(md5(md5($str,true),true)));
}

2010年8月15日 23:15

佩服仁兄的技术!

2010年8月16日 2:56

看了你这文章后,我几乎花了10多个小时才搞定这个东西。
登陆用的URL要有完整的参数,而且部分参数必须要做urlencode
就是这个参数 和urlencode的问题把我给烦死了

用到tw2other里的部分代码

$loginInfo = array ();
$u=$this->username;
$loginInfo [‘u’] = urlencode($u);
$loginInfo [‘p’] = preprocess($this->password,$verifycode);
$loginInfo [‘verifycode’] = strtoupper($verifycode);
$data=”aid=46000101&fp=loginerroralert&from_ui=1&u1=”.urlencode(‘http://t.qq.com’).”&h=1&ptredirect=1&”.createKeyString ( $loginInfo );

2010年8月16日 19:52

我的一个评论是不是被当作垃圾评论了?显示不出来呀

2010年8月30日 16:48

– -#登录不了 。

2010年8月30日 17:20

(‘7′,’0′,”,’0′,’您的输入有误, 请重试。’);

汗 搞了半天了 还是这样 – -#登录不了

2010年8月30日 17:33

按照 BB 的家了 urlencode 就提示非法参数 郁闷死。!

2010年8月30日 18:01

@tc 你是在试我的demo还是自己写程序?demo刚试了没问题,程序过几天放出吧

2010年8月30日 19:16

你的demo一直 提示 密码错误! 请返回重试 – -!!

2010年8月30日 19:19

{result:-4,msg:’发表失败’}

怎么解决 这步

2010年8月30日 19:31

终于搞定了 – -!~和bb兄弟一样 用了一整个下午。

2010年9月1日 22:38

@tc 我目前把发图功能也加上了。我主要是用来同步twitter到新浪和腾讯,如果tweet里带图片链接,则自动发图到新浪和腾讯。 效果可以参看http://t.qq.com/bbshijie和http://t.sina.com.cn/mcflychina

2010年11月15日 11:01

“喔,soga,验证码是要更新的,源码HTML里的那个验证码是没用的,这个js返回的才是最新的验证码。”

http://ptlogin2.qq.com/check 返回ptui_checkVC(0,’xxxxxxxx’)的时候验证码没有用,但是返回ptui_checkVC(1,’xxxxxxxx’)的时候验证码还是有用处的:)

function ptui_checkVC(A, B) {
g_loadcheck = false;
g_checkTime = new Date().getTime() – g_checkTime;
if (g_submitting) {
return
}
var D = new Date();
if (firstuin != “” && g_changeNum <= 1) {
g_time.time7 = D;
var C = {
“12”: g_time.time7 – g_time.time6
};
if (firstuin != “”) {
C[“16”] = g_time.time6 – g_time.time3,
C[“17”] = g_time.time7 – g_time.time3
}
if (!xuiFrame) {
ptui_speedReport(C)
}
} else {
g_time.time10 = D;
var C = {
“13”: g_time.time10 – g_time.time9
};
ptui_speedReport(C)
}
if (A == “0”) {
f_v.value = B;
loadVC(false)
} else {
vc_type = B;
f_v.value = “”;
loadVC(true)
}
}

2011年2月16日 11:02

不知道为什么测试会出现这个错误:”ptuiCB(‘7′,’0′,”,’0′,’您的输入有误, 请重试。’);” 密码是经过md5_3加密了,验证码也是动态获取的,是不是在登录的那个地址里验证码需要和之前md5_3加密过的密码一起md5加密一遍呢?还请指教,谢谢!

2012年1月25日 16:09

有研究过发图片网址么???

2015年12月21日 22:32

用最少的悔恨面对过去。用最少的浪费面对现在。用最多的梦面对未来。