Group
Extension

Mojo-SinaWeibo/lib/Mojo/SinaWeibo.pm

package Mojo::SinaWeibo;
$Mojo::SinaWeibo::VERSION = "1.6";
$Mojo::SinaWeibo::SEND_INTERVAL = 2;
$Mojo::SinaWeibo::LAST_DISPATCH_TIME = undef;
use Mojo::Base 'Mojo::EventEmitter';
use Mojo::JSON qw(encode_json decode_json);
use Mojo::Util qw(b64_encode dumper sha1_sum url_escape url_unescape encode decode);
use Mojo::URL;
use Crypt::RSA::ES::PKCS1v15;
use Crypt::RSA::Key::Public;
use POSIX;
use Carp;
use Time::HiRes qw();
use List::Util qw(first);
use Mojo::IOLoop;
use File::Temp qw/tempfile/;
use Encode::Locale ;
use Fcntl ':flock';

has 'user';
has 'pwd';
has ua_debug                => 0;
has log_level               => 'info';     #debug|info|warn|error|fatal
has log_path                => undef;
has ioloop                  => sub {Mojo::IOLoop->singleton};

has max_timeout_count       => 5;
has timeout                 => 15;
has timeout_count           => 0;

has log => sub{
    require Mojo::Log;
    no warnings 'redefine';
    *Mojo::Log::append = sub{
        my ($self, $msg) = @_;
        return unless my $handle = $self->handle;
        flock $handle, LOCK_EX;
        $handle->print(encode("console_out", $msg)) or croak "Can't write to log: $!";
        flock $handle, LOCK_UN;
    };
    Mojo::Log->new(path=>$_[0]->log_path,level=>$_[0]->log_level,format=>sub{
        my ($time, $level, @lines) = @_;
        my $title="";
        if(ref $lines[0] eq "HASH"){
            my $opt = shift @lines; 
            $time = $opt->{"time"} if defined $opt->{"time"};
            $title = (defined $opt->{"title"})?$opt->{title} . " ":"";
            $level  = $opt->{level} if defined $opt->{"level"};
        }
        @lines = split /\n/,join "",@lines;
        my $return = "";
        $time = POSIX::strftime('[%y/%m/%d %H:%M:%S]',localtime($time));
        for(@lines){
            $return .=
                $time
            .   " " 
            .   "[$level]" 
            . " " 
            . $title 
            . $_ 
            . "\n";
        }
        return $return;
    });
};
has ua                      => sub {
    local $ENV{MOJO_USERAGENT_DEBUG} = 0;
    require Mojo::UserAgent;
    Mojo::UserAgent->new(
        request_timeout    => 30,
        inactivity_timeout => 30,
        max_redirects      => 7,
        transactor => Mojo::UserAgent::Transactor->new( 
            name =>  'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062'
        ),
    );
};

has 'nick';
has login_type  => "rsa";#wsse
has api_form => "HTML";#HTML|JSON
has login_state => "invalid";
has 'need_pin' => 0;
has rsa => sub {Crypt::RSA::ES::PKCS1v15->new};
has 'servertime';
has 'pcid';
has 'pubkey';
has 'nonce';
has 'rsakv';
has 'exectime';
has 'verifycode';
has 'uid';
has 'home';
has 'showpin';
has 'ticket';
has 'im_msg_id' => 0;
has 'im_ack' => -1;
has 'im';
has 'im_clientid';
has 'im_channel';
has 'im_server';
has 'im_connect_interval' => 0;
has 'im_xiaoice_uid' => 5175429989;
has 'im_client_lag_data' => sub{[]};
has 'im_server_lag_data' => sub{[]};
has 'im_ready' => 0;
has im_user => sub {[]};
has 'im_api_server';
has im_queue => sub {[]};

sub search_im_user{
    my $s = shift;
    my %p = @_;
    return if 0 == grep {defined $p{$_}} keys %p;
    if(wantarray){
        return grep {my $f = $_;(first {$p{$_} ne $f->{$_}} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->im_user};
    }
    else{
        return first {my $f = $_;(first {$p{$_} ne $f->{$_}} grep {defined $p{$_}} keys %p) ? 0 : 1;} @{$s->im_user};
    }
}
sub add_im_user{
    my $s = shift;
    my $user = shift;
    $s->die("不支持的数据类型") if ref $user ne "HASH";
    $s->die("不支持的数据类型") if not exists $user->{uid} ;
    $s->die("不支持的数据类型") if not exists $user->{nick} ;
    my $nocheck = shift;
    if(@{$s->im_user}  == 0){
        push @{$s->im_user},$user;
        return $s;
    }
    if($nocheck){
        push @{$s->im_user},$user;
        return $s;
    }
    my $u = $s->search_im_user(uid => $user->{uid});
    if(defined $u){
        $u = $user;
    }
    else{#new user
        push @{$s->im_user},$user;
    }
    return $s;
}

sub auth {
    my $s = shift;
    return $s if $s->login_state eq "success";
    $s->prelogin();
    $s->login();
    if($s->login_state eq "success"){
        $s->timeout_count(0);
        return $s
    }
    $s->fatal("授权失败,程序处于离线状态");
    $s->login_state("stop");
}
sub login {
    my $s = shift;
    $s->info("正在登录...");
    my $api = 'http://login.sina.com.cn/sso/login.php';
    my $sp = "";
    if($s->login_type eq "rsa"){
        $s->debug("登录使用rsa加密算法");
        my $public = Crypt::RSA::Key::Public->new;
        $public->n("0x" . $s->pubkey);
        $public->e("0x10001");
        $sp = 
            lc join "",unpack "H*",
            $s->rsa->encrypt(
                Key=>$public,
                Message=>$s->servertime . "\t" . $s->nonce . "\n" . $s->pwd
            );
    }
    elsif($s->login_type eq "wsse"){
        $s->debug("登录使用wsse加密算法");
        $sp = sha1_sum( "" . sha1_sum(sha1_sum($s->pwd)) . $s->servertime . $s->nonce );
    }
    my $post = {
        entry       => "weibo",
        gateway     => 1,
        from        => "",
        savestate   => 7,
        useticket   => 1,
        pagerefer   => '',
        vsnf        => 1,
        service     => "miniblog",
        pwencode    => ($s->login_type eq "rsa"?"rsa2":"wsse"),
        encoding    => "UTF-8",
        prelt       => $s->exectime,
        url         => 'http://weibo.com/ajaxlogin.php?framelogin=1&callback=parent.sinaSSOController.feedBackUrlCallBack',
        returntype  => ($s->api_form eq "JSON"?"TEXT":"META"),
        servertime  => $s->servertime,
        nonce       => $s->nonce,
        rsakv       => $s->rsakv,
        su          => b64_encode(url_escape($s->user),""),
        sp          => $sp,
    };

    $post->{door} = $s->verifycode if $s->need_pin;
    $post->{pcid} = $s->pcid if $s->need_pin;
    $post->{sr} = "1366*768" if $s->need_pin;

    my $tx = $s->ua->post($api . '?client=ssologin.js%28v1.4.18%29' ,form=>$post);
    if($s->ua_debug){
        print $tx->req->to_string,"\n";
        print $tx->res->to_string,"\n";
    }
    return unless $tx->success;
    my ($retcode,$reason,$feedbackurl,$json);
    if($post->{returntype} eq "META"){
        return unless $tx->res->body =~/location.replace\(['"](.*?)['"]\)/;
        $feedbackurl = Mojo::URL->new($1);
        $retcode = $feedbackurl->query->param("retcode");
        $reason = decode("gb2312",url_unescape($feedbackurl->query->param("reason"))) if defined $feedbackurl->query->param("reason");
    }
    elsif($post->{returntype} eq "TEXT"){
        $json = decode_json($tx->res->body);
        $retcode = $json->{retcode};
        $reason = $json->{reason} if exists $json->{reason};
    }
    if($retcode == 0){
        if($post->{returntype} eq "TEXT"){
          $s->ticket($json->{ticket})
            ->uid($json->{uid})
            ->home("http://weibo.com/u/$json->{uid}/home")
            ->nick($json->{nick})
            ->login_state("success");
            $s->info("登录成功");
        }
        elsif($post->{returntype} eq "META"){   
            $s->ticket($feedbackurl->query->param("ticket"));
            if($tx->res->body=~/sinaSSOController\.setCrossDomainUrlList\((.*?)\)/){
                my $json = decode_json($1);
                my $i=0;
                $s->debug("处理跨域访问域名列表...");
                for  (@{ $json->{arrURL} }){
                    my $url = Mojo::URL->new($_);
                    $url->query->merge(
                        callback    =>  "sinaSSOController.doCrossDomainCallBack",
                        scriptId    =>  "ssoscript$i",
                        client      =>  'ssologin.js(v1.4.18)',
                        _           =>  $s->time(),
                    );
                    my $tx = $s->ua->get($url->to_string);
                    if($s->ua_debug){
                        print $tx->req->to_string,"\n";
                        print $tx->res->to_string,"\n";
                    }
                    $i++;
                } 
            }   
            my $tx = $s->ua->get($feedbackurl->to_string);
            if($s->ua_debug){
                print $tx->req->to_string,"\n";
                print $tx->res->to_string,"\n";
            }
            return unless $tx->success;
            return unless $tx->res->body =~/parent\.sinaSSOController\.feedBackUrlCallBack\((.*?)\)/;
            $s->debug("获取登录回调参数...");
            my $json = decode_json($1);
            return unless $json->{result};
            $s->uid($json->{userinfo}{uniqueid})->home("http://weibo.com/u/$json->{userinfo}{uniqueid}/home");
            $s->debug("进行首页跳转...");
            if(defined $json->{redirect}){
                my $tx = $s->ua->get($json->{redirect}) ;
                return unless $tx->success;
                $s->login_state("success");
                $s->info("登录成功");
            }
            else{
                my $tx = $s->ua->get("http://weibo.com/" . $json->{userinfo}{userdomain});
                return unless $tx->success;
                $s->login_state("success");
                $s->info("登录成功");
            }
        }
    }
    elsif($retcode ==4049){
        $s->get_pin() && $s->login();
    }
    else{
        $s->error($reason?"登录失败: $retcode($reason)":"登录失败: $retcode"); 
        return;
    }
}

sub get_im_info{
    my $s = shift;
    return +{channel=>$s->im_channel,server=>$s->im_server} if (defined $s->im_channel and $s->im_server);
    my $api = "http://nas.im.api.weibo.com/im/webim.jsp";
    my $callback = "IM_" . $s->time();
    my $query_string = {
        uid             => $s->uid,
        returntype      => "json",
        v               => "1.1",
        callback        => $callback,
        __rnd           => $s->time(),
    };
    $s->debug("获取私信服务器地址...");
    my $tx = $s->ua->get($api,{Referer=>$s->home},form=>$query_string);
    if($s->ua_debug){
        print $tx->req->to_string,"\n";
        print $tx->res->to_string,"\n";
    }
    return unless $tx->success;
    return unless $tx->res->body=~/\Q$callback\E\((.*?)\)/;
    my $json = decode_json($1);
    $json->{server} =~s#^http#ws#;
    $json->{server} =~s#/$##;
    $s->debug("私信服务器地址[ " .  $json->{server} . $json->{channel} . " ]");
    $json->{server} .= "/im";
    $s->im_server($json->{server})->im_channel($json->{channel});
    return {channel=>$json->{channel},server=>$json->{server}};
}


sub get_pin{
    my $s = shift;
    $s->info("正在获取验证码图片...");
    my $api = 'http://login.sina.com.cn/cgi/pin.php';
    my $query_string = {
        r   => POSIX::floor(rand() * (10**8)), 
        s   => 0,
        p   => $s->pcid,
    };
    my $tx = $s->ua->get($api,form=>$query_string);
    if($s->ua_debug){
        print $tx->req->to_string,"\n";
        print $tx->res->headers->to_string,"\n";
    }
    return unless $tx->success;
    my ($fh, $filename) = tempfile("sinaweibo_img_verfiy_XXXX",SUFFIX =>".png",TMPDIR => 1);
    binmode $fh;
    print $fh $tx->res->body;
    close $fh;
    my $filename_for_console = decode("locale_fs",$filename);
    my $info = $s->log->format->(CORE::time,"info","请输入图片验证码 [ $filename_for_console ]: "); 
    chomp $info;
    $s->log->append($info);
    my $input;
    chomp($input=<STDIN>);
    $s->verifycode($input)->need_pin(1);
    return 1;
}


sub prelogin{
    my $s = shift;
    $s->info("准备登录微博帐号[ ".$s->user." ]");
    my $api = 'http://login.sina.com.cn/sso/prelogin.php';
    my $query_string = {
        entry   => 'weibo',
        client  => 'ssologin.js(v1.4.18)',
        callback => 'sinaSSOController.preloginCallBack',
        su      => 'TGVuZGZhdGluZyU0MHNpbmEuY29t',
        rsakt   => 'mod',
        checkpin => '1',
        _        => $s->time(),
    };
    my $tx = $s->ua->get($api,form=>$query_string);
    if($s->ua_debug){
        print $tx->req->to_string,"\n";
        print $tx->res->to_string,"\n";
    }
    return unless $tx->success;
    return unless $tx->res->body =~ /^sinaSSOController\.preloginCallBack\((.*)\)$/;
    my $json = decode_json($1); 
    return  if $json->{retcode}!=0;
    for (qw(servertime pcid pubkey nonce rsakv exectime showpin)){
        $s->$_($json->{$_}) if exists $json->{$_};
    }
}

sub gen_im_msg_id {
    my $s = shift;
    my $last_id = $s->im_msg_id;
    $s->im_msg_id(++$last_id);
    return $last_id;
}
sub gen_im_ack{
    my $s = shift;
    my $last_ack = $s->im_ack;
    if($last_ack == -1){
        $s->im_ack(0);
        return $last_ack;
    }
    else{
        $s->im_ack(++$last_ack);
        return $last_ack;
    }
}

sub time{
    my $s = shift;
    return int(Time::HiRes::time * 1000);
}
sub gmtime_string {
    my $s = shift;
    my $time = shift;
    $time = CORE::time unless defined $time;
    my @DoW = qw(Sun Mon Tue Wed Thu Fri Sat);
    my @MoY = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
    my %MoY;
    @MoY{@MoY} = (1..12);
    my ($sec, $min, $hour, $mday, $mon, $year, $wday) = CORE::gmtime($time);
    sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT",
            $DoW[$wday],
            $mday, $MoY[$mon], $year+1900,
            $hour, $min, $sec);
}

sub gen_im_msg{
    my $s = shift;
    my $type = shift;
    my $msg = {};
    if($type eq "handshake"){
        $msg = 
            {
                version         =>  "1.0",
                minimumVersion  =>  "0.9",
                channel         =>  "/meta/handshake",
                supportedConnectionTypes=> ["websocket",],#"callback-polling"],
                advice          => {timeout=>60000,interval=>0},
                id              => $s->gen_im_msg_id,
                ext             => {ack => Mojo::JSON->true,timesync=>{tc=>$s->time,l=>0,o=>0}},
                timestamp       => $s->gmtime_string,
            };
    }
    elsif($type eq "connect"){
        $msg = 
            {
                channel         =>  "/meta/connect",
                connectionType  =>  "websocket",
                clientId        => $s->im_clientid,
                id              => $s->gen_im_msg_id(),
                ext             => {ack => $s->gen_im_ack(),timesync=>{tc=>$s->time,l=>0,o=>0}},
                timestamp       => $s->gmtime_string,
            };
        $msg->{advice} = {timeout=>0,} if $msg->{ext}{ack} == -1;
    }
    elsif($type eq "subscribe"){
        my %p = @_;
        $msg = 
            {
                channel         =>  "/meta/subscribe",
                subscription    => $p{channel},
                id              => $s->gen_im_msg_id,
                clientId        => $s->im_clientid,
                ext             => {timesync=>{tc=>$s->time,l=>0,o=>0}},
                timestamp       => $s->gmtime_string,
            };
    }
    elsif($type eq "cmd"){
        my %p = @_;
        my $data ={};
        $data = {cmd=>"recents"} if $p{cmd} eq "recents";
        $data = {cmd=>"usersetting",subcmd=>"get",seq=>"get"} if $p{cmd} eq "usersetting";
        if($p{cmd} eq "msg"){
            $data = {cmd=>"msg",uid=>$p{uid},msg=>$p{msg}} ;
        }
        $msg = 
            {
                channel         =>  "/im/req",
                data            => $data,
                id              => $s->gen_im_msg_id,
                clientId        => $s->im_clientid,
                timestamp       => $s->gmtime_string,
            };
    }
    return $msg;
}
sub parse_im_msg{
    my $s = shift;
    my $msg = shift;
    print encode_json($msg),"\n" if $s->ua_debug;
    for my $m(@{$msg}){
        if($m->{channel} eq '/meta/handshake'){
            $s->debug("收到服务器握手消息");
            return unless first {$_ eq "websocket"} @{$m->{supportedConnectionTypes}};
            return unless $m->{successful};
            $s->debug("服务器握手成功");
            $s->im_clientid($m->{clientId});
            $s->im_send($s->gen_im_msg("subscribe",channel=>$s->im_channel));
            $s->im_send($s->gen_im_msg("connect"));
        }
        elsif($m->{channel} eq "/meta/connect"){
            $s->debug("收到服务器心跳响应 ack: ".$m->{ext}{ack});
            return unless $m->{successful};
            if(exists $m->{advice} and exists $m->{advice}{interval}){
                $s->im_connect_interval($m->{advice}{interval}/1000);
            }
            $s->timer( $s->im_connect_interval,sub{
                my $msg = $s->gen_im_msg("connect");
                if(exists $m->{ext}{timesync}){
                    my $i = $s->time;
                    my $k = ($i -$m->{ext}{timesync}{tc} - $m->{ext}{timesync}{p})/2;
                    my $l = $m->{ext}{timesync}{ts} - $m->{ext}{timesync}{ts} - $k;
                    push @{$s->im_client_lag_data},$k;
                    push @{$s->im_server_lag_data},$l;
                    if(10<@{$s->im_server_lag_data}){
                        shift @{$s->im_server_lag_data};shift @{$s->im_client_lag_data};
                    }
                    my $n=0;
                    my $o=0;
                    for(my $p=0;$p<@{$s->im_server_lag_data};$p++){
                        $n+=$s->im_client_lag_data->[$p];
                        $o+=$s->im_server_lag_data->[$p];
                    }

                    my $g = int($n/@{$s->im_server_lag_data});my $h=int($o/@{$s->im_server_lag_data});
                    $msg->{ext}{timesync}{l} = $g;
                    $msg->{ext}{timesync}{o} = $h;
                }
                $s->im_send($msg);
            });
        }
        elsif($m->{channel} eq "/meta/subscribe"){
            return unless $m->{successful};
            $s->debug("收到服务器订阅响应消息");
            if(@{$s->im_user} == 0){
                $s->im_send($s->gen_im_msg("cmd",cmd=>"usersetting"));
                $s->im_send($s->gen_im_msg("cmd",cmd=>"recents"));
            }
            else{
                $s->im_ready(1);
                $s->debug("私信服务器状态准备就绪");
                $s->emit("im_ready");
            }
        }
        elsif($m->{channel} eq "/im/req"){
            next unless $m->{successful};
        }
        elsif($m->{channel} eq $s->im_channel){
            return unless exists $m->{data}{type};
            if($m->{data}{type} eq "recents"){
                $s->im_user([ map {{uid=>$_->[0],nick=>$_->[1]}} @{$m->{data}{recents}} ]);
                if(!$s->im_ready){
                    $s->im_ready(1);
                    $s->debug("私信服务器状态准备就绪");
                    $s->emit("im_ready");
                }
            }            

            elsif( $m->{data}{type} eq "msg"){
                for(@{$m->{data}{items}}){
                    my($uid,$msg,$time) = @$_[0..2];
                    my $u = $s->search_im_user(uid=>$uid);
                    my $nick = defined $u?$u->{nick}:"小冰";
                    $s->emit("receive_message",{uid=>$uid,nick=>$nick,content=>$msg,'time'=>int($time/1000)},{is_success=>1,code=>200,msg=>"正常响应"});
                } 
            }
        
            elsif($m->{data}{type} eq "synchroniz" ){
                return unless exists $m->{data}{syncData};
                my $syncdata = decode_json(encode("utf8",$m->{data}{syncData}));
                return unless exists $syncdata->{msg};
                return unless exists $syncdata->{uid};
                my $time = exists $syncdata->{'time'}?int($syncdata->{'time'}/1000):CORE::time;
                my($uid,$msg) = ($syncdata->{uid}, $syncdata->{msg}); 
                my $u = $s->search_im_user(uid=>$uid);
                my $nick = defined $u?$u->{nick}:"小冰"; 
                $s->emit("send_message",{uid=>$uid,nick=>$nick,content=>$msg,'time'=>$time},"sync");
            }
        }
    }
    
}

sub im_init{
    my $s = shift;
    return if $s->im_ready;
    $s->im_msg_id(0)
      ->im_ack(-1)
      ->im_ready(0)
      ->im(undef)
      ->im_clientid(undef)
      ->im_connect_interval(0);
    my $im_info = $s->get_im_info();
    return unless defined $im_info;
    $s->ua->websocket($im_info->{server},sub{
        my ($ua, $tx) = @_;
        $s->error("Websocket服务器连接失败") and return unless $tx->is_websocket;
        $s->im($tx);
        $s->im->on(finish => sub {
            my ($tx, $code, $reason) = @_;
            $s->debug("WebSocket服务器关闭($code)");
            $s->im_ready(0);
            $s->debug("私信服务器状态失效");
        });
        $s->im->on(json=>sub{
            my ($tx, $msg) = @_;
            $s->parse_im_msg($msg);
        });
        if( 
            ($s->im->can("is_established") and $s->im->is_established) 
         or ($s->im->can("established") and $s->im->established)
        ){
            $s->debug("Websocket服务器连接成功");
            $s->im_send($s->gen_im_msg("handshake"));
        }
    });
}

sub im_speek{
    my $s = shift;
    my $uid = shift;
    my $content = shift;
    my $callback = pop;
    $content = decode("utf8",$content) if defined $content;
    $s->auth() if $s->login_state eq "invalid";
    if($s->login_state eq "stop"){
        $callback->(undef,{is_success=>0,code=>503,msg=>encode("utf8","响应超时")}) if ref $callback eq "CODE";
        return;
    }
    my $delay = 0;
    my $now = CORE::time;
    if(defined $Mojo::SinaWeibo::LAST_DISPATCH_TIME){
        $delay = $now<$Mojo::SinaWeibo::LAST_DISPATCH_TIME+$Mojo::SinaWeibo::SEND_INTERVAL? 
                    $Mojo::SinaWeibo::LAST_DISPATCH_TIME+$Mojo::SinaWeibo::SEND_INTERVAL-$now
                :   0;
    };

    $s->timer($delay,sub{
        if(ref $callback eq "CODE"){
            my $ask = {cb=>$callback,,status=>"wait"};
            my $id = $s->timer($s->timeout,sub{
                return if $ask->{status} eq "done";
                $ask->{status} = "done";
                $ask->{cb}->(undef,{is_success=>0,code=>503,msg=>encode("utf8","响应超时")}) if ref $ask->{cb} eq "CODE";
                $s->warn("消息响应超时,放弃等待");
            });
            $ask->{timer} = $id;
            push @{$s->im_queue},$ask;
        }
        if($s->im_ready){
            my $msg = $s->gen_im_msg("cmd",cmd=>"msg",uid=>$uid,msg=>$content);
            $s->im_send($msg);
            
        }
        else{
            $s->once(im_ready=>sub{
                my $s = shift;
                my $msg = $s->gen_im_msg("cmd",cmd=>"msg",uid=>$uid,msg=>$content);
                $s->im_send($msg);
            });
            $s->im_init();
        }
    });

    $Mojo::SinaWeibo::LAST_DISPATCH_TIME = $now+$delay;

}

sub ask_xiaoice{
    my $s = shift;
    my $uid = $s->im_xiaoice_uid;
    my $content = shift;
    my $callback = pop;
    $s->im_speek($uid,$content,$callback);
}
sub im_send{
    my $s= shift;
    my $msg = shift;
    $s->im->send({json=>[$msg]},sub{
        print encode_json($msg),"\n" if $s->ua_debug;
        $s->debug("发送usersetting消息") if ($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "usersetting");
        $s->debug("发送recents消息") if ($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "recents");
        $s->debug("发送握手消息") if $msg->{channel} eq "/meta/handshake";
        $s->debug("发送心跳消息 ack: " . $msg->{ext}{ack}) if $msg->{channel} eq "/meta/connect";
        $s->debug("发送订阅消息") if $msg->{channel} eq "/meta/subscribe";
        if($msg->{channel} eq "/im/req" and $msg->{data}{cmd} eq "msg"){
            my $u=$s->search_im_user(uid=>$msg->{data}{uid});
            $s->emit("send_message"=>{
                uid=>$msg->{data}{uid},
                nick=>(defined $u?$u->{nick}:"小冰"),
                'time'=>CORE::time,
                content=>$msg->{data}{msg},
            },"api") ;
        }
    });
}
sub start{
    my $s = shift;
    my %p = @_ if @_>1 and @_%2==0;
    $s->on(im_timeout=>sub{
        my $s = shift;
        my $callback = shift;
        $s->warn("私信消息响应超时,放弃等待");
        $callback->(undef,{is_success=>0,code=>503,msg=>encode("utf8","响应超时")});
        my $count = $s->timeout_count;
        $s->timeout_count(++$count);
        if($s->timeout_count >= $s->max_timeout_count){
            $s->im_ready(0);
            $s->login_state("invalid");
            $s->emit("invalid");
        }
    });
    $s->on(receive_message=>sub{
        my $s = shift;
        my $msg = shift;
        my $status = shift;
        return if ref $msg ne "HASH";
        $s->info({level=>"私信消息",'time'=>$msg->{'time'},title=>"$msg->{nick} :"},$msg->{content}); 
        my $ask = @{$s->im_queue}?shift(@{$s->im_queue}):undef;
        return unless defined $ask;
        return if $ask->{status} eq "done";
        if(ref $ask->{cb} eq "CODE" and defined $msg and defined $status){
            $ask->{cb}->({
                    uid=>$msg->{uid},
                    nick=>encode("utf8",$msg->{nick}),
                    content=>encode("utf8",$msg->{content}),
                    'time'=>$msg->{'time'},
                },
                {
                    is_success=>$status->{is_success},
                    code=>$status->{code},
                    msg=>encode("utf8",$status->{msg}),
                }
            );
        }

        $ask->{status} = "done";    
        $s->ioloop->remove($ask->{timer}) if defined $ask->{timer};
    });
    $s->on(send_message=>sub{
        my $s = shift;
        my $msg = shift;
        my $type =  shift;
        return if ref $msg ne "HASH";
        $s->info({level=>"私信消息",'time'=>$msg->{'time'},title=>"我->$msg->{nick} :"},$msg->{content});
        push @{$s->im_queue},{cb=>undef,status=>"wait"} if $type eq "sync";
    });

    $s->on(invalid=>sub{
        my $s = shift;
        $s->warn("程序当前状态不可用,尝试重新授权");
        $s->auth();
    });

    if(exists $p{enable_api_server} and $p{enable_api_server} ==1){
        package Mojo::SinaWeibo::Openxiaoice;
        use Encode;
        use Mojolicious::Lite;
        any [qw(GET POST)] => '/openxiaoice/ask'         => sub{
            my $c = shift;
            my $q = encode("utf8",$c->param("q"));
            $c->render_later;
            $s->ask_xiaoice($q,sub{
                my($msg,$status) = @_;
                if($status->{is_success}){
                    $c->render(json=>{code=>1,answer=>decode("utf8",$msg->{content})});      
                }
                else{
                    $c->render(json=>{code=>0,answer=>undef,reason=>decode("utf8",$status->{msg})});
                }
            });
        };
        any '/*whatever'  => sub{whatever=>'',$_[0]->render(text => "request error",status=>403)};
        package Mojo::SinaWeibo;          
        require Mojo::SinaWeibo::Server;
        my $data = [{host=>$p{host}||"0.0.0.0",port=>$p{port}||3000}] ;
        my $server = Mojo::SinaWeibo::Server->new(); 
        $s->im_api_server($server);
        $server->app($server->build_app("Mojo::SinaWeibo::Openxiaoice"));
        $server->app->secrets("hello world");
        $server->app->log($s->log);
        $server->listen([ map { 'http://' . (defined $_->{host}?$_->{host}:"0.0.0.0") .":" . (defined $_->{port}?$_->{port}:3000)} @$data]) if ref $data eq "ARRAY" ;
        $server->start;
    }
}

sub run {
    my $s = shift;
    $s->start(@_);
    $s->ioloop->start unless $s->ioloop->is_running;
}
sub emit_one{
  my ($s, $name) = (shift, shift);
  if (my $e = $s->{events}{$name}) {
    my $cb = shift @$e;
    $s->$cb(@_);
  }
  return $s;
}
sub timer{
    my $s = shift;
    $s->ioloop->timer(@_);
}
sub interval{
    my $s = shift;
    $s->ioloop->recurring(@_);
}

sub die{
    my $s = shift; 
    local $SIG{__DIE__} = sub{$s->log->fatal(@_);exit -1};
    Carp::confess(@_);
}
sub info{
    my $s = shift;
    $s->log->info(@_);
    $s;
}
sub warn{
    my $s = shift;
    $s->log->warn(@_);
    $s;
}
sub error{
    my $s = shift;
    $s->log->error(@_);
    $s;
}
sub fatal{
    my $s = shift;
    $s->log->fatal(@_);
    $s;
}
sub debug{
    my $s = shift;
    $s->log->debug(@_);
    $s;
}
1;


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.