File Coverage

blib/lib/Mojo/SinaWeibo.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package Mojo::SinaWeibo;
2             $Mojo::SinaWeibo::VERSION = "1.3";
3 1     1   17354 use Mojo::Base 'Mojo::EventEmitter';
  0            
  0            
4             use Mojo::JSON qw(encode_json decode_json);
5             use Mojo::Util qw(b64_encode dumper sha1_sum url_escape url_unescape encode decode);
6             use Mojo::URL;
7             use Crypt::RSA::ES::PKCS1v15;
8             use Crypt::RSA::Key::Public;
9             use POSIX;
10             use Carp;
11             use Time::HiRes qw();
12             use List::Util qw(first);
13             use Mojo::IOLoop;
14             use File::Temp qw/tempfile/;
15             use Encode::Locale ;
16             use Fcntl ':flock';
17              
18             has 'user';
19             has 'pwd';
20             has ua_debug => 0;
21             has log_level => 'info'; #debug|info|warn|error|fatal
22             has log_path => undef;
23              
24             has max_timeout_count => 3;
25             has timeout => 10;
26             has timeout_count => 0;
27              
28             has log => sub{
29             require Mojo::Log;
30             no warnings 'redefine';
31             *Mojo::Log::append = sub{
32             my ($self, $msg) = @_;
33             return unless my $handle = $self->handle;
34             flock $handle, LOCK_EX;
35             $handle->print(encode("console_out", $msg)) or croak "Can't write to log: $!";
36             flock $handle, LOCK_UN;
37             };
38             Mojo::Log->new(path=>$_[0]->log_path,level=>$_[0]->log_level,format=>sub{
39             my ($time, $level, @lines) = @_;
40             my $title="";
41             if(ref $lines[0] eq "HASH"){
42             my $opt = shift @lines;
43             $time = $opt->{"time"} if defined $opt->{"time"};
44             $title = (defined $opt->{"title"})?$opt->{title} . " ":"";
45             $level = $opt->{level} if defined $opt->{"level"};
46             }
47             #$level .= " " if ($level eq "info" or $level eq "warn");
48             @lines = split /\n/,join "",@lines;
49             my $return = "";
50             $time = POSIX::strftime('[%y/%m/%d %H:%M:%S]',localtime($time));
51             for(@lines){
52             $return .=
53             $time
54             . " "
55             . "[$level]"
56             . " "
57             . $title
58             . $_
59             . "\n";
60             }
61             return $return;
62             });
63             };
64             has ua => sub {
65             local $ENV{MOJO_USERAGENT_DEBUG} = 0;
66             require Mojo::UserAgent;
67             Mojo::UserAgent->new(
68             request_timeout => 30,
69             inactivity_timeout => 30,
70             max_redirects => 7,
71             transactor => Mojo::UserAgent::Transactor->new(
72             name => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062'
73             ),
74             );
75             };
76              
77             has 'nick';
78             has login_type => "rsa";#wsse
79             has api_form => "HTML";#HTML|JSON
80             has login_state => "invalid";
81             has 'need_pin' => 0;
82             has rsa => sub {Crypt::RSA::ES::PKCS1v15->new};
83             has 'servertime';
84             has 'pcid';
85             has 'pubkey';
86             has 'nonce';
87             has 'rsakv';
88             has 'exectime';
89             has 'verifycode';
90             has 'uid';
91             has 'home';
92             has 'showpin';
93             has 'ticket';
94             has 'im_msg_id' => 0;
95             has 'im_ack' => -1;
96             has 'im';
97             has 'im_clientid';
98             has 'im_channel';
99             has 'im_server';
100             has 'im_connect_interval' => 0;
101             has 'im_xiaoice_uid' => 5175429989;
102             has 'im_client_lag_data' => sub{[]};
103             has 'im_server_lag_data' => sub{[]};
104             has 'im_ready' => 0;
105             has im_user => sub {[]};
106             has 'im_api_server';
107              
108             sub search_im_user{
109             my $s = shift;
110             my %p = @_;
111             return if 0 == grep {defined $p{$_}} keys %p;
112             if(wantarray){
113             return grep {my $f = $_;(first {$p{$_} ne $f->{$_}} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->im_user};
114             }
115             else{
116             return first {my $f = $_;(first {$p{$_} ne $f->{$_}} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->im_user};
117             }
118             }
119             sub add_im_user{
120             my $s = shift;
121             my $user = shift;
122             $s->die("不支持的数据类型") if ref $user ne "HASH";
123             $s->die("不支持的数据类型") if not exists $user->{uid} ;
124             $s->die("不支持的数据类型") if not exists $user->{nick} ;
125             my $nocheck = shift;
126             if(@{$s->im_user} == 0){
127             push @{$s->im_user},$user;
128             return $s;
129             }
130             if($nocheck){
131             push @{$s->im_user},$user;
132             return $s;
133             }
134             my $u = $s->search_im_user(uid => $user->{uid});
135             if(defined $u){
136             $u = $user;
137             }
138             else{#new user
139             push @{$s->im_user},$user;
140             }
141             return $s;
142             }
143              
144             sub auth {
145             my $s = shift;
146             return $s if $s->login_state eq "success";
147             $s->prelogin();
148             $s->login();
149             if($s->login_state eq "success"){
150             $s->timeout_count(0);
151             return $s
152             }
153             $s->fatal("授权失败,程序处于离线状态");
154             $s->login_state("stop");
155             }
156             sub login {
157             my $s = shift;
158             $s->info("正在登录...");
159             my $api = 'http://login.sina.com.cn/sso/login.php';
160             my $sp = "";
161             if($s->login_type eq "rsa"){
162             $s->debug("登录使用rsa加密算法");
163             my $public = Crypt::RSA::Key::Public->new;
164             $public->n("0x" . $s->pubkey);
165             $public->e("0x10001");
166             $sp =
167             lc join "",unpack "H*",
168             $s->rsa->encrypt(
169             Key=>$public,
170             Message=>$s->servertime . "\t" . $s->nonce . "\n" . $s->pwd
171             );
172             }
173             elsif($s->login_type eq "wsse"){
174             $s->debug("登录使用wsse加密算法");
175             $sp = sha1_sum( "" . sha1_sum(sha1_sum($s->pwd)) . $s->servertime . $s->nonce );
176             }
177             my $post = {
178             entry => "weibo",
179             gateway => 1,
180             from => "",
181             savestate => 7,
182             useticket => 1,
183             pagerefer => '',
184             vsnf => 1,
185             service => "miniblog",
186             pwencode => ($s->login_type eq "rsa"?"rsa2":"wsse"),
187             encoding => "UTF-8",
188             prelt => $s->exectime,
189             url => 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
190             returntype => ($s->api_form eq "JSON"?"TEXT":"META"),
191             servertime => $s->servertime,
192             nonce => $s->nonce,
193             rsakv => $s->rsakv,
194             su => b64_encode(url_escape($s->user),""),
195             sp => $sp,
196             };
197              
198             $post->{door} = $s->verifycode if $s->need_pin;
199             $post->{pcid} = $s->pcid if $s->need_pin;
200             $post->{sr} = "1366*768" if $s->need_pin;
201              
202             my $tx = $s->ua->post($api . '?client=ssologin.js%28v1.4.18%29' ,form=>$post);
203             if($s->ua_debug){
204             print $tx->req->to_string,"\n";
205             print $tx->res->to_string,"\n";
206             }
207             return unless $tx->success;
208             my ($retcode,$reason,$feedbackurl,$json);
209             if($post->{returntype} eq "META"){
210             return unless $tx->res->body =~/location.replace\(['"](.*?)['"]\)/;
211             $feedbackurl = Mojo::URL->new($1);
212             $retcode = $feedbackurl->query->param("retcode");
213             $reason = decode("gb2312",url_unescape($feedbackurl->query->param("reason"))) if defined $feedbackurl->query->param("reason");
214             }
215             elsif($post->{returntype} eq "TEXT"){
216             $json = decode_json($tx->res->body);
217             $retcode = $json->{retcode};
218             $reason = $json->{reason} if exists $json->{reason};
219             }
220             if($retcode == 0){
221             if($post->{returntype} eq "TEXT"){
222             $s->ticket($json->{ticket})
223             ->uid($json->{uid})
224             ->home("http://weibo.com/u/$json->{uid}/home")
225             ->nick($json->{nick})
226             ->login_state("success");
227             $s->info("登录成功");
228             }
229             elsif($post->{returntype} eq "META"){
230             $s->ticket($feedbackurl->query->param("ticket"));
231             if($tx->res->body=~/sinaSSOController\.setCrossDomainUrlList\((.*?)\)/){
232             my $json = decode_json($1);
233             my $i=0;
234             $s->debug("处理跨域访问域名列表...");
235             for (@{ $json->{arrURL} }){
236             my $url = Mojo::URL->new($_);
237             $url->query->merge(
238             callback => "sinaSSOController.doCrossDomainCallBack",
239             scriptId => "ssoscript$i",
240             client => 'ssologin.js(v1.4.18)',
241             _ => $s->time(),
242             );
243             my $tx = $s->ua->get($url->to_string);
244             if($s->ua_debug){
245             print $tx->req->to_string,"\n";
246             print $tx->res->to_string,"\n";
247             }
248             $i++;
249             }
250             }
251             my $tx = $s->ua->get($feedbackurl->to_string);
252             if($s->ua_debug){
253             print $tx->req->to_string,"\n";
254             print $tx->res->to_string,"\n";
255             }
256             return unless $tx->success;
257             return unless $tx->res->body =~/parent\.sinaSSOController\.feedBackUrlCallBack\((.*?)\)/;
258             $s->debug("获取登录回调参数...");
259             my $json = decode_json($1);
260             return unless $json->{result};
261             $s->uid($json->{userinfo}{uniqueid})->home("http://weibo.com/u/$json->{userinfo}{uniqueid}/home");
262             $s->debug("进行首页跳转...");
263             if(defined $json->{redirect}){
264             my $tx = $s->ua->get($json->{redirect}) ;
265             return unless $tx->success;
266             $s->login_state("success");
267             $s->info("登录成功");
268             }
269             else{
270             my $tx = $s->ua->get("http://weibo.com/" . $json->{userinfo}{userdomain});
271             return unless $tx->success;
272             $s->login_state("success");
273             $s->info("登录成功");
274             }
275             }
276             }
277             elsif($retcode ==4049){
278             $s->get_pin() && $s->login();
279             }
280             else{
281             $s->error($reason?"登录失败: $retcode($reason)":"登录失败: $retcode");
282             return;
283             }
284             }
285              
286             sub get_im_info{
287             my $s = shift;
288             return +{channel=>$s->im_channel,server=>$s->im_server} if (defined $s->im_channel and $s->im_server);
289             my $api = "http://nas.im.api.weibo.com/im/webim.jsp";
290             my $callback = "IM_" . $s->time();
291             my $query_string = {
292             uid => $s->uid,
293             returntype => "json",
294             v => "1.1",
295             callback => $callback,
296             __rnd => $s->time(),
297             };
298             $s->debug("获取私信服务器地址...");
299             my $tx = $s->ua->get($api,{Referer=>$s->home},form=>$query_string);
300             if($s->ua_debug){
301             print $tx->req->to_string,"\n";
302             print $tx->res->to_string,"\n";
303             }
304             return unless $tx->success;
305             return unless $tx->res->body=~/\Q$callback\E\((.*?)\)/;
306             my $json = decode_json($1);
307             $json->{server} =~s#^http#ws#;
308             $json->{server} =~s#/$##;
309             $s->debug("私信服务器地址[ " . $json->{server} . $json->{channel} . " ]");
310             $json->{server} .= "/im";
311             $s->im_server($json->{server})->im_channel($json->{channel});
312             return {channel=>$json->{channel},server=>$json->{server}};
313             }
314              
315              
316             sub get_pin{
317             my $s = shift;
318             $s->info("正在获取验证码图片...");
319             my $api = 'http://login.sina.com.cn/cgi/pin.php';
320             my $query_string = {
321             r => POSIX::floor(rand() * (10**8)),
322             s => 0,
323             p => $s->pcid,
324             };
325             my $tx = $s->ua->get($api,form=>$query_string);
326             if($s->ua_debug){
327             print $tx->req->to_string,"\n";
328             print $tx->res->headers->to_string,"\n";
329             }
330             return unless $tx->success;
331             my ($fh, $filename) = tempfile("sinaweibo_img_verfiy_XXXX",SUFFIX =>".png",TMPDIR => 1);
332             binmode $fh;
333             print $fh $tx->res->body;
334             close $fh;
335             my $filename_for_console = decode("locale_fs",$filename);
336             my $info = $s->log->format->(CORE::time,"info","请输入图片验证码 [ $filename_for_console ]: ");
337             chomp $info;
338             $s->log->append($info);
339             my $input;
340             chomp($input=);
341             $s->verifycode($input)->need_pin(1);
342             return 1;
343             }
344              
345              
346             sub prelogin{
347             my $s = shift;
348             $s->info("准备登录微博帐号[ ".$s->user." ]");
349             my $api = 'http://login.sina.com.cn/sso/prelogin.php';
350             my $query_string = {
351             entry => 'weibo',
352             client => 'ssologin.js(v1.4.18)',
353             callback => 'sinaSSOController.preloginCallBack',
354             su => 'TGVuZGZhdGluZyU0MHNpbmEuY29t',
355             rsakt => 'mod',
356             checkpin => '1',
357             _ => $s->time(),
358             };
359             my $tx = $s->ua->get($api,form=>$query_string);
360             if($s->ua_debug){
361             print $tx->req->to_string,"\n";
362             print $tx->res->to_string,"\n";
363             }
364             return unless $tx->success;
365             return unless $tx->res->body =~ /^sinaSSOController\.preloginCallBack\((.*)\)$/;
366             my $json = decode_json($1);
367             return if $json->{retcode}!=0;
368             for (qw(servertime pcid pubkey nonce rsakv exectime showpin)){
369             $s->$_($json->{$_}) if exists $json->{$_};
370             }
371             }
372              
373             sub gen_im_msg_id {
374             my $s = shift;
375             my $last_id = $s->im_msg_id;
376             $s->im_msg_id(++$last_id);
377             return $last_id;
378             }
379             sub gen_im_ack{
380             my $s = shift;
381             my $last_ack = $s->im_ack;
382             if($last_ack == -1){
383             $s->im_ack(0);
384             return $last_ack;
385             }
386             else{
387             $s->im_ack(++$last_ack);
388             return $last_ack;
389             }
390             }
391              
392             sub time{
393             my $s = shift;
394             return int(Time::HiRes::time * 1000);
395             }
396             sub gmtime_string {
397             my $s = shift;
398             my $time = shift;
399             $time = CORE::time unless defined $time;
400             my @DoW = qw(Sun Mon Tue Wed Thu Fri Sat);
401             my @MoY = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
402             my %MoY;
403             @MoY{@MoY} = (1..12);
404             my ($sec, $min, $hour, $mday, $mon, $year, $wday) = CORE::gmtime($time);
405             sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
406             $DoW[$wday],
407             $mday, $MoY[$mon], $year+1900,
408             $hour, $min, $sec);
409             }
410              
411             sub gen_im_msg{
412             my $s = shift;
413             my $type = shift;
414             my $msg = {};
415             if($type eq "handshake"){
416             $msg =
417             {
418             version => "1.0",
419             minimumVersion => "0.9",
420             channel => "/meta/handshake",
421             supportedConnectionTypes=> ["websocket",],#"callback-polling"],
422             advice => {timeout=>60000,interval=>0},
423             id => $s->gen_im_msg_id,
424             ext => {ack => Mojo::JSON->true,timesync=>{tc=>$s->time,l=>0,o=>0}},
425             timestamp => $s->gmtime_string,
426             };
427             }
428             elsif($type eq "connect"){
429             $msg =
430             {
431             channel => "/meta/connect",
432             connectionType => "websocket",
433             clientId => $s->im_clientid,
434             id => $s->gen_im_msg_id(),
435             ext => {ack => $s->gen_im_ack(),timesync=>{tc=>$s->time,l=>0,o=>0}},
436             timestamp => $s->gmtime_string,
437             };
438             $msg->{advice} = {timeout=>0,} if $msg->{ext}{ack} == -1;
439             }
440             elsif($type eq "subscribe"){
441             my %p = @_;
442             $msg =
443             {
444             channel => "/meta/subscribe",
445             subscription => $p{channel},
446             id => $s->gen_im_msg_id,
447             clientId => $s->im_clientid,
448             ext => {timesync=>{tc=>$s->time,l=>0,o=>0}},
449             timestamp => $s->gmtime_string,
450             };
451             }
452             elsif($type eq "cmd"){
453             my %p = @_;
454             my $data ={};
455             $data = {cmd=>"recents"} if $p{cmd} eq "recents";
456             $data = {cmd=>"usersetting",subcmd=>"get",seq=>"get"} if $p{cmd} eq "usersetting";
457             if($p{cmd} eq "msg"){
458             $data = {cmd=>"msg",uid=>$p{uid},msg=>$p{msg}} ;
459             }
460             $msg =
461             {
462             channel => "/im/req",
463             data => $data,
464             id => $s->gen_im_msg_id,
465             clientId => $s->im_clientid,
466             timestamp => $s->gmtime_string,
467             };
468             }
469             return $msg;
470             }
471             sub parse_im_msg{
472             my $s = shift;
473             my $msg = shift;
474             print encode_json($msg),"\n" if $s->ua_debug;
475             for my $m(@{$msg}){
476             if($m->{channel} eq '/meta/handshake'){
477             $s->debug("收到服务器握手消息");
478             return unless first {$_ eq "websocket"} @{$m->{supportedConnectionTypes}};
479             return unless $m->{successful};
480             $s->debug("服务器握手成功");
481             $s->im_clientid($m->{clientId});
482             $s->im_send($s->gen_im_msg("subscribe",channel=>$s->im_channel));
483             $s->im_send($s->gen_im_msg("connect"));
484             }
485             elsif($m->{channel} eq "/meta/connect"){
486             $s->debug("收到服务器心跳响应 ack: ".$m->{ext}{ack});
487             return unless $m->{successful};
488             if(exists $m->{advice} and exists $m->{advice}{interval}){
489             $s->im_connect_interval($m->{advice}{interval}/1000);
490             }
491             $s->timer( $s->im_connect_interval,sub{
492             my $msg = $s->gen_im_msg("connect");
493             if(exists $m->{ext}{timesync}){
494             my $i = $s->time;
495             my $k = ($i -$m->{ext}{timesync}{tc} - $m->{ext}{timesync}{p})/2;
496             my $l = $m->{ext}{timesync}{ts} - $m->{ext}{timesync}{ts} - $k;
497             push @{$s->im_client_lag_data},$k;
498             push @{$s->im_server_lag_data},$l;
499             if(10<@{$s->im_server_lag_data}){
500             shift @{$s->im_server_lag_data};shift @{$s->im_client_lag_data};
501             }
502             my $n=0;
503             my $o=0;
504             for(my $p=0;$p<@{$s->im_server_lag_data};$p++){
505             $n+=$s->im_client_lag_data->[$p];
506             $o+=$s->im_server_lag_data->[$p];
507             }
508              
509             my $g = int($n/@{$s->im_server_lag_data});my $h=int($o/@{$s->im_server_lag_data});
510             $msg->{ext}{timesync}{l} = $g;
511             $msg->{ext}{timesync}{o} = $h;
512             }
513             $s->im_send($msg);
514             });
515             }
516             elsif($m->{channel} eq "/meta/subscribe"){
517             return unless $m->{successful};
518             $s->debug("收到服务器订阅响应消息");
519             if(@{$s->im_user} == 0){
520             $s->im_send($s->gen_im_msg("cmd",cmd=>"usersetting"));
521             $s->im_send($s->gen_im_msg("cmd",cmd=>"recents"));
522             }
523             else{
524             $s->im_ready(1);
525             $s->debug("私信服务器状态准备就绪");
526             $s->emit("im_ready");
527             }
528             }
529             elsif($m->{channel} eq "/im/req"){
530             next unless $m->{successful};
531             }
532             elsif($m->{channel} eq $s->im_channel){
533             return unless exists $m->{data}{type};
534             if($m->{data}{type} eq "recents"){
535             $s->im_user([ map {{uid=>$_->[0],nick=>$_->[1]}} @{$m->{data}{recents}} ]);
536             if(!$s->im_ready){
537             $s->im_ready(1);
538             $s->debug("私信服务器状态准备就绪");
539             $s->emit("im_ready");
540             }
541             }
542              
543             elsif( $m->{data}{type} eq "msg"){
544             for(@{$m->{data}{items}}){
545             my($uid,$msg,$time) = @$_[0..2];
546             my $u = $s->search_im_user(uid=>$uid);
547             my $nick = defined $u?$u->{nick}:"未知昵称";
548             $s->emit("receive_message",{uid=>$uid,nick=>$nick,content=>$msg,'time'=>int($time/1000)},{is_success=>1,code=>200,msg=>"正常响应"});
549             $s->emit_one("answer_message",{uid=>$uid,nick=>$nick,content=>$msg,'time'=>int($time/1000)},{is_success=>1,code=>200,msg=>"正常响应"});
550             }
551             }
552            
553             elsif($m->{data}{type} eq "synchroniz" ){
554             return unless exists $m->{data}{syncData};
555             my $syncdata = decode_json(encode("utf8",$m->{data}{syncData}));
556             return unless exists $syncdata->{msg};
557             return unless exists $syncdata->{uid};
558             my $time = exists $syncdata->{'time'}?int($syncdata->{'time'}/1000):CORE::time;
559             my($uid,$msg) = ($syncdata->{uid}, $syncdata->{msg});
560             my $u = $s->search_im_user(uid=>$uid);
561             my $nick = defined $u?$u->{nick}:"未知昵称";
562             $s->emit("send_message",{uid=>$uid,nick=>$nick,content=>$msg,'time'=>$time});
563             }
564             }
565             }
566            
567             }
568              
569             sub im_init{
570             my $s = shift;
571             return if $s->im_ready;
572             $s->im_msg_id(0)
573             ->im_ack(-1)
574             ->im_ready(0)
575             ->im(undef)
576             ->im_clientid(undef)
577             ->im_connect_interval(0);
578             my $im_info = $s->get_im_info();
579             return unless defined $im_info;
580             $s->ua->websocket($im_info->{server},sub{
581             my ($ua, $tx) = @_;
582             $s->error("Websocket服务器连接失败") and return unless $tx->is_websocket;
583             $s->im($tx);
584             $s->im->on(finish => sub {
585             my ($tx, $code, $reason) = @_;
586             $s->debug("WebSocket服务器关闭($code)");
587             $s->im_ready(0);
588             $s->debug("私信服务器状态失效");
589             });
590             $s->im->on(json=>sub{
591             my ($tx, $msg) = @_;
592             $s->parse_im_msg($msg);
593             });
594             if($s->im->is_established){
595             $s->debug("Websocket服务器连接成功");
596             $s->im_send($s->gen_im_msg("handshake"));
597             }
598             });
599             }
600              
601             sub im_speek{
602             my $s = shift;
603             my $uid = shift;
604             my $content = shift;
605             my $callback = pop;
606             $content = decode("utf8",$content) if defined $content;
607             if($s->login_state eq "stop"){
608             $callback->(undef,{is_success=>0,code=>503,msg=>encode("utf8","响应超时")});
609             return;
610             }
611             $s->auth() if $s->login_state eq "invalid";
612             #timeout handle
613             my $cb = {
614             cb=>sub{
615             #Mojo::IOLoop->remove($id);
616             $callback->(@_) if ref $callback eq "CODE";
617             },
618             status => 'wait',
619             };
620             $s->timer($s->timeout,sub{$s->emit(im_timeout=>$cb);});
621             if($s->im_ready){
622             my $msg = $s->gen_im_msg("cmd",cmd=>"msg",uid=>$uid,msg=>$content);
623             $s->im_send($msg,$cb);
624            
625             }
626             else{
627             $s->once(im_ready=>sub{
628             my $s = shift;
629             return if $cb->{status} eq "abort";
630             my $msg = $s->gen_im_msg("cmd",cmd=>"msg",uid=>$uid,msg=>$content);
631             $s->im_send($msg,$cb);
632             });
633             $s->im_init();
634             }
635              
636             }
637              
638             sub ask_xiaoice{
639             my $s = shift;
640             my $uid = $s->im_xiaoice_uid;
641             my $content = shift;
642             my $callback = pop;
643             $s->im_speek($uid,$content,$callback);
644             }
645             sub im_send{
646             my $s= shift;
647             my $msg = shift;
648             my $cb = shift;
649             if($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "msg" and ref $cb->{cb} eq "CODE"){
650             $s->once(answer_message=>sub{
651             my $s = shift;
652             return if $cb->{status} eq "abort";
653             my($msg,$status) = @_;
654             if(defined $msg){
655             $msg->{nick} = encode("utf8",$msg->{nick});
656             $msg->{content} = encode("utf8",$msg->{content});
657             }
658             $status->{msg} = encode("utf8",$status->{msg});
659             $cb->{cb}->($msg,$status);
660             $cb->{status} = "done";
661             });
662             #push @{$s->im_send_callback},$cb;
663             };
664             $s->im->send({json=>[$msg]},sub{
665             print encode_json($msg),"\n" if $s->ua_debug;
666             $s->debug("发送usersetting消息") if ($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "usersetting");
667             $s->debug("发送recents消息") if ($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "recents");
668             $s->debug("发送握手消息") if $msg->{channel} eq "/meta/handshake";
669             $s->debug("发送心跳消息 ack: " . $msg->{ext}{ack}) if $msg->{channel} eq "/meta/connect";
670             $s->debug("发送订阅消息") if $msg->{channel} eq "/meta/subscribe";
671             if($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "msg"){
672             my $u=$s->search_im_user(uid=>$msg->{data}{uid});
673             $s->emit("send_message"=>{
674             uid=>$msg->{data}{uid},
675             nick=>(defined $u?$u->{nick}:"未知昵称"),
676             'time'=>CORE::time,
677             content=>$msg->{data}{msg},
678             })
679             }
680             });
681             }
682             sub run{
683             my $s = shift;
684             my %p = @_ if @_>1 and @_%2==0;
685             $s->on(im_timeout=>sub{
686             my $s = shift;
687             my $cb = shift;
688             return if $cb->{status} eq "done";
689             $s->warn("私信消息响应超时,放弃等待");
690             $cb->{status} = 'abort';
691             $cb->{cb}->(undef,{is_success=>0,code=>503,msg=>encode("utf8","响应超时")}) if ref $cb->{cb} eq "CODE";
692             my $count = $s->timeout_count;
693             $s->timeout_count(++$count);
694             if($s->timeout_count >= $s->max_timeout_count){
695             $s->im_ready(0);
696             $s->login_state("invalid");
697             $s->emit("invalid");
698             }
699             });
700             $s->on(receive_message=>sub{
701             my $s = shift;
702             my $msg = shift;
703             return if ref $msg ne "HASH";
704             $s->info({level=>"私信消息",'time'=>$msg->{'time'},title=>"$msg->{nick} :"},$msg->{content});
705             });
706             $s->on(send_message=>sub{
707             my $s = shift;
708             my $msg = shift;
709             return if ref $msg ne "HASH";
710             $s->info({level=>"私信消息",'time'=>$msg->{'time'},title=>"我->$msg->{nick} :"},$msg->{content});
711             });
712              
713             $s->on(invalid=>sub{
714             my $s = shift;
715             $s->warn("程序当前状态不可用,尝试重新授权");
716             $s->auth();
717             });
718              
719             if(exists $p{enable_api_server} and $p{enable_api_server} ==1){
720             package Mojo::SinaWeibo::Openxiaoice;
721             use Encode;
722             use Mojolicious::Lite;
723             any [qw(GET POST)] => '/openxiaoice/ask' => sub{
724             my $c = shift;
725             my $q = $c->param("q");
726             $c->render_later;
727             $s->ask_xiaoice($q,sub{
728             my($msg,$status) = @_;
729             if($status->{is_success}){
730             $c->render(json=>{code=>1,answer=>$msg->{content}});
731             }
732             else{
733             $c->render(json=>{code=>0,answer=>undef,reason=>decode("utf8",$status->{msg})});
734             }
735             });
736             };
737             any '/*whatever' => sub{whatever=>'',$_[0]->render(text => "request error",status=>403)};
738             package Mojo::SinaWeibo;
739             require Mojo::SinaWeibo::Server;
740             my $data = [{host=>$p{host}||"0.0.0.0",port=>$p{port}||3000}] ;
741             my $server = Mojo::SinaWeibo::Server->new();
742             $s->im_api_server($server);
743             $server->app($server->build_app("Mojo::SinaWeibo::Openxiaoice"));
744             $server->app->secrets("hello world");
745             $server->app->log($s->log);
746             $server->listen($data) if ref $data eq "ARRAY" ;
747             $server->start;
748             }
749              
750             Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
751             }
752              
753             sub emit_one{
754             my ($s, $name) = (shift, shift);
755             if (my $e = $s->{events}{$name}) {
756             my $cb = shift @$e;
757             $s->$cb(@_);
758             }
759             return $s;
760             }
761             sub timer{
762             my $s = shift;
763             Mojo::IOLoop->timer(@_);
764             }
765             sub recurring{
766             my $s = shift;
767             Mojo::IOLoop->recurring(@_);
768             }
769              
770             sub die{
771             my $s = shift;
772             local $SIG{__DIE__} = sub{$s->log->fatal(@_);exit -1};
773             Carp::confess(@_);
774             }
775             sub info{
776             my $s = shift;
777             $s->log->info(@_);
778             $s;
779             }
780             sub warn{
781             my $s = shift;
782             $s->log->warn(@_);
783             $s;
784             }
785             sub error{
786             my $s = shift;
787             $s->log->error(@_);
788             $s;
789             }
790             sub fatal{
791             my $s = shift;
792             $s->log->fatal(@_);
793             $s;
794             }
795             sub debug{
796             my $s = shift;
797             $s->log->debug(@_);
798             $s;
799             }
800             1;