package Mojo::Weixin;
use strict;
use Mojo::Weixin::Const qw(%KEY_MAP_USER %KEY_MAP_GROUP %KEY_MAP_GROUP_MEMBER %KEY_MAP_FRIEND %KEY_MAP_MEDIA_CODE);
use List::Util qw(first);
use Mojo::Weixin::Message;
use Mojo::Weixin::Const;
use Mojo::Weixin::Message;
use Mojo::Weixin::Message::Queue;
use Mojo::Weixin::Message::Remote::_upload_media;
use Mojo::Weixin::Message::Remote::_get_media;
use Mojo::Weixin::Message::Remote::_send_media_message;
use Mojo::Weixin::Message::Remote::_send_text_message;
use Mojo::Weixin::Message::Remote::_revoke_message;
$Mojo::Weixin::Message::LAST_DISPATCH_TIME  = undef;
$Mojo::Weixin::Message::SEND_INTERVAL  = 3;

sub gen_message_queue{
    my $self = shift;
    Mojo::Weixin::Message::Queue->new(callback_for_get=>sub{
        my $msg = shift;
        return if $self->is_stop;
        if($msg->class eq "recv"){
            if($msg->format eq "media"){
                if($self->download_media){
                    $self->_get_media($msg,sub{
                        my ($path,$data,$msg) = @_;
                        if($msg->media_size == 0 and $msg->media_type eq 'emoticon'){
                            $msg->content("[表情](获取数据为空，可能需要手机查看)");
                        }
                        else{
                            $msg->content( $msg->content. "(". $msg->media_path . ")");
                        }
                        $self->emit(receive_media=>$path,$data,$msg);
                        $self->emit(receive_message=>$msg);
                    });
                }
                else{
                    $self->emit(receive_message=>$msg);
                }
            }
            else{ $self->emit(receive_message=>$msg);}
        }
        elsif($msg->class eq "send"){
            if($msg->source ne "local"){
                $msg->send_status(code=>0,msg=>"发送成功",info=>"来自其他设备");
                if($msg->format eq "media"){
                    if($self->download_media){
                        $self->_get_media($msg,sub{
                            my ($path,$data,$msg) = @_;
                            if($msg->media_size == 0 and $msg->media_type eq 'emoticon'){
                                $msg->content("[表情](获取数据为空，可能需要手机查看)");
                            }
                            else{
                                $msg->content( $msg->content. "(". $msg->media_path . ")");
                            }
                            $msg->cb->($self,$msg) if ref $msg->cb eq 'CODE';
                            $self->emit(send_media=>$path,$data,$msg);
                            $self->emit(send_message=>$msg);
                        });
                    }
                    else{
                        $msg->cb->($self,$msg) if ref $msg->cb eq 'CODE';
                        $self->emit(send_message=>$msg);
                    }
                }
                else{ 
                    $msg->cb->($self,$msg) if ref $msg->cb eq 'CODE';
                    $self->emit(send_message=>$msg);
                }
                return;
            }
            #消息的ttl值减少到0则丢弃消息
            if($msg->ttl <= 0){
                $self->debug("消息[ " . $msg->id.  " ]已被消息队列丢弃，当前TTL: ". $msg->ttl);
                $msg->send_status(code=>-5,msg=>"发送失败",info=>"TTL失效");
                if(ref $msg->cb eq 'CODE'){
                    $msg->cb->(
                        $self,
                        $msg,
                    );
                }
                $self->emit(send_message=>
                    $msg,
                );
                return;
            }
            my $ttl = $msg->ttl;
            $msg->ttl(--$ttl);

            my $delay = 0;
            my $now = time;
            if(defined $Mojo::Weixin::Message::LAST_DISPATCH_TIME){
                $delay = $now<$Mojo::Weixin::Message::LAST_DISPATCH_TIME+$Mojo::Weixin::Message::SEND_INTERVAL?
                            $Mojo::Weixin::Message::LAST_DISPATCH_TIME+$Mojo::Weixin::Message::SEND_INTERVAL-$now
                        :   0;
            }
            $self->timer($delay,sub{
                $msg->time(time);
                if($msg->format eq "text"){
                    $self->_send_text_message($msg);
                }
                elsif($msg->format eq "media"){
                    $self->_send_media_message($msg);
                }
            });
            $Mojo::Weixin::Message::LAST_DISPATCH_TIME = $now+$delay;
        }
    });
}
sub _parse_synccheck_data{
    my @logout_code = qw(1100 1101 1102 1205);
    my $self = shift;
    my($retcode,$selector) = @_;
    if(defined $retcode and defined $selector){
        if($retcode == 0 and $selector != 0){
            $self->_synccheck_error_count(0);
            $self->_sync();
        }
        elsif($retcode == 0 and $selector == 0){
            $self->_synccheck_error_count(0);
        }
        elsif($retcode == 1101 and $self->stop_with_mobile){
            $self->stop();
        }
        elsif(first {$retcode == $_} @logout_code){
            $self->relogin($retcode);
            return;
        }
        elsif($self->_synccheck_error_count <= 10){
            my $c = $self->_synccheck_error_count; 
            $self->_synccheck_error_count(++$c);
        }
        else{
            $self->relogin();
            return;
        }
    }
}
sub _parse_sync_data {
    my $self = shift;
    my $json = shift;
    return if not defined $json;
    my @logout_code = qw(1100 1102 1205);
    if($json->{BaseResponse}{Ret} == 1101){#手机端强制下线 或 其他设备登录Web微信
        $self->info("收到下线通知");
        $self->logout($json->{BaseResponse}{Ret});
        $self->stop();
    }
    elsif(first {$json->{BaseResponse}{Ret} == $_} @logout_code  ){
        $self->relogin($json->{BaseResponse}{Ret});
        return;
    }
    elsif($json->{BaseResponse}{Ret} !=0){
        $self->warn("收到无法识别消息代码[$json->{BaseResponse}{Ret}]，已将其忽略");
        $self->emit(unknown_retcode=>$json->{BaseResponse}{Ret});
        return;
    }
    $self->sync_key($json->{SyncKey}) if $json->{SyncKey}{Count}!=0;
    $self->synccheck_key($json->{SyncCheckKey}) if $json->{SyncCheckKey}{Count}!=0;
    $self->skey($json->{SKey}) if $json->{SKey};


    #群组或联系人变更
    if($json->{ModContactCount}!=0){
        for my $e (@{$json->{ModContactList}}){
            if($self->is_group_id($e->{UserName})){#群组
                my $group = {member=>[]};
                for(keys %KEY_MAP_GROUP){
                    $group->{$_} = $e->{$KEY_MAP_GROUP{$_}} // "";
                }
                if($e->{MemberCount} != 0){
                    for my $m (@{$e->{MemberList}}){
                        my $member = {};
                        for(keys %KEY_MAP_GROUP_MEMBER){
                            $member->{$_} = $m->{$KEY_MAP_GROUP_MEMBER{$_}} // "";
                        }
                        push @{ $group->{member} }, $member;
                    }
                }
                my $g = $self->search_group(id=>$group->{id});
                if(not defined $g){#新增群组
                    if(not $self->update_group($group->{id},1)){
                        $self->add_group(Mojo::Weixin::Group->new($group));
                    }
                }
                else{#更新已有联系人
                    $g->update($group);
                }
            }
            else{#联系人
                my $friend = {};
                for(keys %KEY_MAP_FRIEND){
                    $friend->{$_} = $e->{$KEY_MAP_FRIEND{$_}} if defined $e->{$KEY_MAP_FRIEND{$_}};
                }
                my $f = $self->search_friend(id=>$friend->{id});
                if(not defined $f){
                    $self->add_friend(Mojo::Weixin::Friend->new($friend));
                }
                else{$f->update($friend)}
            }
        }
    }

    if($json->{ModChatRoomMemberCount}!=0){
        
    }

    if($json->{DelContactCount}!=0){
        for my $e (@{$json->{DelContactList}}){
            if($self->is_group_id($e->{UserName})){
                my $g = $self->search_group(id=>$e->{UserName});
                $self->remove_group($g) if defined $g;
            }
            else{
                my $f = $self->search_friend(id=>$e->{UserName});
                $self->remove_friend($f) if defined $f;
            }
        }
    }

    #有新消息
    if($json->{AddMsgCount} != 0){
        for my $e (@{$json->{AddMsgList}}){
            my $msg = {};
            for(keys %KEY_MAP_MESSAGE){
                $msg->{$_} = $e->{$KEY_MAP_MESSAGE{$_}} // "";
            }
            if($e->{MsgType} == 1){#好友消息或群消息
                $msg->{format} = "text";
            }
            elsif($e->{MsgType} == 3){#图片消息
                $msg->{format} = "media";
                $msg->{media_type} = "image";
                $msg->{media_code} = $e->{MsgType};
                $msg->{media_id} = $msg->{id} . ":" . $msg->{media_code};
            }
            elsif($e->{MsgType} == 47){#表情或gif图片
                $msg->{format} = "media";
                $msg->{media_type} = "emoticon";
                $msg->{media_code} = $e->{MsgType};
                $msg->{media_id} = $msg->{id} . ":" . $msg->{media_code};
            }
            elsif($e->{MsgType} == 62){#小视频
                $msg->{format} = "media";
                $msg->{media_type} = "microvideo";
                $msg->{media_code} = $e->{MsgType};
                $msg->{media_id} = $msg->{id} . ":" . $msg->{media_code};
            }
            elsif($e->{MsgType} == 43){#视频
                $msg->{format} = "media";
                $msg->{media_type} = "video";
                $msg->{media_code} = $e->{MsgType};
                $msg->{media_id} = $msg->{id} . ":" . $msg->{media_code};
            }
            elsif($e->{MsgType} == 34){#语音
                $msg->{format} = "media";
                $msg->{media_type} = "voice";
                $msg->{media_code} = $e->{MsgType};
                $msg->{media_id} = $msg->{id} . ":" . $msg->{media_code};
            }
            elsif($e->{MsgType} == 37){#好友推荐消息
                $msg->{format} = "text";
                #$msg->{class} = "recv";
                #$msg->{type} = "friend_message";
                #$msg->{receiver_id} = $self->user->id;
                #$msg->{sender_id} = $e->{FromUserName};
                my $id = $e->{RecommendInfo}{UserName};
                my $displayname = $e->{RecommendInfo}{NickName};
                my $verify = $e->{RecommendInfo}{Content};
                my $ticket = $e->{RecommendInfo}{Ticket};
                #$msg->data({id=>$id,verify=>$verify,ticket=>$ticket,displayname=>$displayname});
                #$msg->{content} = "收到[ " . $displayname  . " ]好友验证请求：" . ($verify?$verify:"(验证内容为空)");
                $self->_webwxstatusnotify($e->{FromUserName},1);
                $self->emit("friend_request",$id,$displayname,$verify,$ticket);
                next;
            }
            elsif($e->{MsgType} == 10000){#群提示消息
                $msg->{format} = "text";
            }
            elsif($e->{MsgType} == 10002){#撤回消息
                $msg->{format} = "revoke";
            }
            elsif($e->{MsgType} == 49 and $e->{AppMsgType} == 6) {#文件分享
                $msg->{format} = "media";
                $msg->{media_type} = "file";
                $msg->{media_code} = $e->{AppMsgType};
                $msg->{media_id} = $e->{MediaId} . ":" . $e->{AppMsgType};
                $msg->{media_name} = $e->{FileName};
                $msg->{media_size} = $e->{FileSize};
            }
            elsif($e->{MsgType} == 49 and $e->{AppMsgType} == 5) {#应用分享
                $msg->{format} = "app";
                $msg->{app_title} = $e->{FileName};
                $msg->{app_url}   = $e->{Url};
            }
            elsif($e->{MsgType} == 49 and $e->{AppMsgType} == 2000){#转账信息
                $msg->{format} = "payment";
            }
            elsif($e->{MsgType} == 42){#名片消息
                $msg->{format} = "card";
                $msg->{card_name} = $e->{RecommendInfo}{NickName};
                $msg->{card_id} = $e->{RecommendInfo}{UserName};
                $msg->{card_province} = $e->{RecommendInfo}{Province};
                $msg->{card_city} = $e->{RecommendInfo}{City};
                $msg->{card_account} = $e->{RecommendInfo}{Alias};
                $msg->{card_sex} = $self->code2sex($e->{RecommendInfo}{Sex});
                #$msg->{card_avatar} = '';
            }
            elsif($e->{MsgType} == 51){#会话、联系人信息同步
                if($e->{StatusNotifyCode} == 4 or $e->{StatusNotifyCode} == 2){#联系人、群组信息需要同步
                    my @id = split /,/,$e->{StatusNotifyUserName};
                    my @group_ids;
                    my @friend_ids;
                    for (@id){
                        next if $_ eq $self->user->id;
                        if($self->is_group_id($_)){push @group_ids,$_ if not $self->search_group(id=>$_);}
                        else{push @friend_ids,$_ if not $self->search_friend(id=>$_);}
                    } 
                    $self->update_group(@group_ids) if @group_ids;
                    $self->update_friend(@friend_ids) if @friend_ids;
                }
                next;
            }
            else{next;}
            if($e->{FromUserName} eq $self->user->id){#发送的消息
                $msg->{source} = 'outer';
                $msg->{class} = "send";
                $msg->{sender_id} = $self->user->id;
                if($self->is_group_id($e->{ToUserName})){
                    $msg->{type} = "group_message";
                    $msg->{group_id} = $e->{ToUserName};
                }
                else{
                    $msg->{type} = "friend_message";
                    $msg->{receiver_id} = $e->{ToUserName};
                }
            }
            #elsif($e->{ToUserName} eq $self->user->id){#接收的消息
            else{#接收的消息
                $msg->{class} = "recv";
                $msg->{receiver_id} = $self->user->id;
                $msg->{type} = "group_message";
                if($self->is_group_id($e->{FromUserName})){#接收到群组消息
                    $msg->{group_id} = $e->{FromUserName};
                    if($e->{MsgType} == 10000){#群提示信息
                        $msg->{type} = "group_notice";
                    }
                    elsif( $msg->{content}=~/^(\@.+?):<br\/>(.*)$/s ){
                        my ($member_id,$content) = ($1,$2);
                        if(defined $member_id and defined $content){
                                $msg->{sender_id} = $member_id;
                                $msg->{content} = $content;
                        }
                    }
                }
                else{#接收到的好友消息
                    $msg->{type} = "friend_message";
                    $msg->{sender_id} = $e->{FromUserName};
                }
            }
            if($msg->{format} eq "media"){
                $msg->{content} = '[图片]' if $msg->{media_type} eq "image";
                $msg->{content} = '[语音]' if $msg->{media_type} eq "voice";
                $msg->{content} = '[视频]' if $msg->{media_type} eq "video";
                $msg->{content} = '[小视频]' if $msg->{media_type} eq "microvideo";
                $msg->{content} = '[表情]' if $msg->{media_type} eq "emoticon";
                $msg->{content} = '[文件]' if $msg->{media_type} eq "file";
            }
            elsif(defined $msg->{content}){
                eval{$msg->{content} = Mojo::Util::html_unescape($msg->{content});};
                $self->warn("html entities unescape fail: $@") if $@;
            }
            if($msg->{format} eq "app"){
                eval{
                    $msg->{content}=~s/<br\/>/\n/g;
                    require Mojo::DOM;
                    my $dom = Mojo::DOM->new($msg->{content});
                    if( $dom->at('msg > appmsg > type')->content != 5){
                        $msg->{content} = "[应用分享]标题：$msg->{app_title}\n[应用分享]链接：$msg->{app_url}"; 
                        return;
                    }
                    $msg->{app_id} = $dom->at('msg > appmsg')->attr->{appid};
                    $msg->{app_title} = $dom->at('msg > appmsg > title')->content;
                    $msg->{app_name} = $dom->at('msg > appinfo > appname')->content;
                    $msg->{app_url} = $dom->at('msg > appmsg > url')->content;
                    $msg->{app_desc} = $dom->at('msg > appmsg > des')->content;
                    for( ($msg->{app_title},$msg->{app_desc},$msg->{app_url},$msg->{app_name}) ){
                        s/<!\[CDATA\[(.*?)\]\]>/$1/g;
                    }
                    $msg->{app_url} = Mojo::Util::html_unescape($msg->{app_url});
                    $msg->{content} = "[应用分享]标题：@{[$msg->{app_title} || '未知']}\n[应用分享]描述：@{[$msg->{app_desc} || '未知']}\n[应用分享]应用：@{[$msg->{app_name} || '未知']}\n[应用分享]链接：@{[$msg->{app_url} || '未知']}";
                };
                if($@){
                    $self->warn("app message xml parse fail: $@") if $@;
                    $msg->{content} = "[应用分享]标题：$msg->{app_title}\n[应用分享]链接：$msg->{app_url}";
                }
            }
            elsif($msg->{format} eq "revoke"){
                #<sysmsg type=\"revokemsg\"><revokemsg><session>wxid_8mn2bmkx40so22</session><oldmsgid>1072643834</oldmsgid><msgid>4835386562261263795</msgid><replacemsg><![CDATA[你撤回了一条消息]]></replacemsg></revokemsg></sysmsg>
                eval{
                    require Mojo::DOM;
                    my $dom = Mojo::DOM->new($msg->{content});
                    return if  $dom->at('sysmsg')->attr->{type} ne 'revokemsg';
                    #$msg->{revoke_session} = $dom->at('sysmsg > revokemsg > session')->content;
                    $msg->{revoke_id} = $dom->at('sysmsg > revokemsg > msgid')->content;
                    $msg->{content} = $dom->at('sysmsg > revokemsg > replacemsg')->content;
                    $msg->{content}=~s/<!\[CDATA\[(.*?)\]\]>/$1/g;

                    #纠正自己撤回消息时，消息类型错乱的问题
                    if($msg->{content} eq '你撤回了一条消息' and $msg->{class} eq 'recv'){
                        $msg->{class} = 'send';
                        $msg->{source} = 'outer';
                        if($msg->{type} eq "group_message"){
                            $msg->{sender_id} = $msg->{receiver_id};
                            delete $msg->{receiver_id};
                        }
                        elsif($msg->{type} eq "friend_message"){
                            ($msg->{sender_id},$msg->{receiver_id}) = ($msg->{receiver_id},$msg->{sender_id});
                        }
                    }
                    $msg->{content} = "[撤回消息](" . $msg->{content} . ")";
                };
                if($@){
                    $self->warn("app message xml parse fail: $@") if $@;
                    $msg->{content} = "[撤回消息]";
                }
            }
            elsif($msg->{format} eq "card"){
                #<msg bigheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/k99g2RHrEeib9KMhGmXZGSIGDjgnmiaX2acT2wl04so2ibsq8ysVPRkRRNQyRLmUVptBpcHt6lvUZym5JgOSd4fug/0" smallheadimgurl="http://wx.qlogo.cn/mmhead/ver_1/k99g2RHrEeib9KMhGmXZGSIGDjgnmiaX2acT2wl04so2ibsq8ysVPRkRRNQyRLmUVptBpcHt6lvUZym5JgOSd4fug/132" username="xxx" nickname="xxx"  shortpy="" alias="" imagestatus="3" scene="17" province="xxx" city="xxx" sign="" sex="1" certflag="0" certinfo="" brandIconUrl="" brandHomeUrl="" brandSubscriptConfigUrl="" brandFlags="0" regionCode="CN_Shanghai_Pudong New District" />
                $msg->{content}=~s/<br\/>/\n/g;
                eval{
                    require Mojo::DOM;
                    my $dom = Mojo::DOM->new($msg->{content});
                    $msg->{card_avatar} = $dom->at('msg')->attr->{bigheadimgurl};
                    $msg->{card_name} = $dom->at('msg')->attr->{nickname};
                    $msg->{card_account} = $dom->at('msg')->attr->{alias};
                    $msg->{card_province} = $dom->at('msg')->attr->{province};
                    $msg->{card_city} = $dom->at('msg')->attr->{city};
                    $msg->{card_sex} = $self->code2sex($dom->at('msg')->attr->{sex});
                };
                $self->warn("app message xml parse fail: $@") if $@;
                $msg->{content} = "[名片]昵称：@{[$msg->{card_name} || '未知']}\n[名片]性别：@{[$msg->{card_sex} || '未知']}\n[名片]位置：@{[$msg->{card_province} || '未知']} @{[$msg->{card_city} || '未知']}\n[名片]头像：@{[$msg->{card_avatar} || '未知']}";
            }
            elsif($msg->{format} eq 'payment'){
                #<msg><br/><appmsg appid=\"\" sdkver=\"\"><br/><title><![CDATA[微信转账]]></title><br/><des><![CDATA[X向你转账0.10元。如需收钱，请点此升级至最新版本]]></des><br/><action></action><br/><type>2000</type><br/><content><![CDATA[]]></content><br/><url><![CDATA[https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&text=text001&btn_text=btn_text_0]]></url><br/><thumburl><![CDATA[https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&text=text001&btn_text=btn_text_0]]></thumburl><br/><lowurl></lowurl><br/><extinfo><br/></extinfo><br/><wcpayinfo><br/><paysubtype>1</paysubtype><br/><feedesc><![CDATA[￥0.10]]></feedesc><br/><transcationid><![CDATA[100005020117111600024321086800000000]]></transcationid><br/><transferid><![CDATA[1000050201201711160100300000000]]></transferid><br/><invalidtime><![CDATA[1510910500]]></invalidtime><br/><begintransfertime><![CDATA[1510818700]]></begintransfertime><br/><effectivedate><![CDATA[1]]></effectivedate><br/><pay_memo><![CDATA[]]></pay_memo><br/><br/></wcpayinfo><br/></appmsg><br/></msg>
                $msg->{content}=~s/<br\/>/\n/g;
                eval{
                    require Mojo::DOM;
                    my $dom = Mojo::DOM->new($msg->{content});
                    $msg->{content} = $dom->at('msg > appmsg > des')->content;
                    $msg->{content}=~s/<!\[CDATA\[(.*?)\]\]>/$1/g;
                };
                $self->warn("payment message xml parse fail: $@") if $@;
                $msg->{content} = "[转账](" . $msg->{content} . ")";
            }
            $self->message_queue->put(Mojo::Weixin::Message->new($msg)); 
        }
    }

    if($json->{ContinueFlag}!=0){
        $self->_sync();
        return;
    }
}

sub send_message{
    my $self = shift;
    my $object = shift;
    my $content = shift;
    my $callback = shift;
    if( ref($object) ne "Mojo::Weixin::Friend" and ref($object) ne "Mojo::Weixin::Group") { 
        $self->error("无效的发送消息对象");
        return;
    }
    my $id = sub{my $r = sprintf "%.3f", rand();$r=~s/\.//g;my $t = $self->now() . $r;return $t}->();
    my $msg = Mojo::Weixin::Message->new(
        id => $id,
        uid=> $id,
        content => $content,
        sender_id => $self->user->id,
        receiver_id => (ref $object eq "Mojo::Weixin::Friend"?$object->id : undef),
        group_id =>(ref $object eq "Mojo::Weixin::Group"?$object->id : undef),
        type => (ref $object eq "Mojo::Weixin::Group"?"group_message":"friend_message"),
        class => "send",
        format => "text", 
        from  => "code",
    );

    $callback->($self,$msg) if ref $callback eq "CODE";
    $self->emit(before_send_message=>$msg);
    $self->message_queue->put($msg);

}
my %KEY_MAP_MEDIA_TYPE = reverse %KEY_MAP_MEDIA_CODE;
sub send_media {
    my $self = shift;
    my $object = shift;
    my $media = shift;
    my $callback = shift;
    if( ref($object) ne "Mojo::Weixin::Friend" and ref($object) ne "Mojo::Weixin::Group") {
        $self->error("无效的发送消息对象");
        return;
    }
    my $media_info = {};
    if(ref $media eq ""){
        $media_info->{media_path} = $media;
    }
    elsif(ref $media eq "HASH"){
        $media_info = $media;
        if(defined $media_info->{media_id}){#定义了media_id意味着不会上传文件，忽略media_path
            my ($id,$code) = split(/:/,$media_info->{media_id},2);
            $media_info->{media_id} = $id if $id;
            $media_info->{media_code} = $code if $code;
            if(!defined $media_info->{media_code} and defined $media_info->{media_type}){
               $media_info->{media_code} = $KEY_MAP_MEDIA_CODE{$media_info->{media_type}} // 6; 
            }
            elsif(!defined $media_info->{media_code}){
                $media_info->{media_code} = 6;
            }
        }
        if(defined $media_info->{media_code} and !defined $media_info->{media_type}){
            $media_info->{media_type} = $KEY_MAP_MEDIA_TYPE{$media_info->{media_code}} || 'file';
        }

    }

    my $media_type =    $media_info->{media_type} eq "image"     ?  "[图片]"
                    :   $media_info->{media_type} eq "emoticon"  ?  "[表情]"
                    :   $media_info->{media_type} eq "video"     ?  "[视频]"
                    :   $media_info->{media_type} eq "microvideo"?  "[小视频]"
                    :   $media_info->{media_type} eq "voicce"    ?  "[语音]"
                    :   $media_info->{media_type} eq "file"      ?  "[文件]"
                    :   "[文件]"
    ;
    
    my $id = sub{my $r = sprintf "%.3f", rand();$r=~s/\.//g;my $t = $self->now() . $r;return $t}->();
    my $msg = Mojo::Weixin::Message->new(
        id => $id,
        uid=> $id,
        media_id   => $media_info->{media_id},
        media_name => $media_info->{media_name},
        media_type => $media_info->{media_type},
        media_code => $media_info->{media_code},
        media_path => $media_info->{media_path},
        media_data => $media_info->{media_data},
        media_mime => $media_info->{media_mime},
        media_size => $media_info->{media_size},
        media_mtime => $media_info->{media_mtime},
        media_ext => $media_info->{media_ext},
        content => "$media_type(" . ($media_info->{media_path} || $media_info->{media_id}) . ")",
        sender_id => $self->user->id,
        receiver_id => (ref $object eq "Mojo::Weixin::Friend"?$object->id : undef),
        group_id =>(ref $object eq "Mojo::Weixin::Group"?$object->id : undef),
        type => (ref $object eq "Mojo::Weixin::Group"?"group_message":"friend_message"),
        class => "send",
        format => "media",
    );

    $callback->($self,$msg) if ref $callback eq "CODE";
    $self->message_queue->put($msg);
}

sub upload_media {
    my $self = shift;
    my $opt = shift;
    my $callback = pop;
    my $msg = Mojo::Weixin::Message->new(%$opt);
    $self->_upload_media($msg,sub{
        my($msg,$json) = @_;
        $callback->({
            media_id    => $msg->media_id,
            media_code  => $msg->media_code,
            media_type  => $msg->media_type,
            media_path  => $msg->media_path,
            media_name  => $msg->media_name,
            media_size  => $msg->media_size,
            media_mime  => $msg->media_mime,
            media_mtime => $msg->media_mtime,
            media_ext   => $msg->media_ext,
        }) if ref $callback eq "CODE";
    });
}
sub reply_message{
    my $self = shift;
    my $msg = shift;
    my $content = shift;
    my $callback = shift;
    if($msg->class eq "recv"){
        if($msg->type eq "group_message"){
            $self->send_message($msg->group,$content,$callback);
        }
        elsif($msg->type eq "friend_message"){
            $self->send_message($msg->sender,$content,$callback);
        }
    }
    elsif($msg->class eq "send"){
        if($msg->type eq "group_message"){
            $self->send_message($msg->group,$content,$callback);
        }
        elsif($msg->type eq "friend_message"){
            $self->send_message($msg->receiver,$content,$callback);
        }

    }
}

sub reply_media_message {
    my $self = shift;
    my $msg = shift;
    my $media = shift;
    my $callback = shift; 
    if($msg->class eq "recv"){
        if($msg->type eq "group_message"){
            $self->send_media($msg->group,$media,$callback);
        }
        elsif($msg->type eq "friend_message"){
            $self->send_media($msg->sender,$media,$callback);
        }
    }
    elsif($msg->class eq "send"){
        if($msg->type eq "group_message"){
            $self->send_media($msg->group,$media,$callback);
        }
        elsif($msg->type eq "friend_message"){
            $self->send_media($msg->receiver,$callback);
        }

    }
}
sub revoke_message {
    my $self = shift;
    my ($msg_id, $receiver_id);
    if(not $_[0]){
        $self->error("撤回消息失败: 无效的msg对象或者msg_id");
        return;
    }
    elsif(ref $_[0] eq "Mojo::Weixin::Message"){
        if( not $_[0]->is_success){
            $self->error("撤回消息失败: 无法撤回未成功发送的消息");
            return;
        }
        $_[0]->dump;
        $msg_id  = $_[0]->id;
        $receiver_id = $_[0]->type eq 'group_message'?$_[0]->group_id:$_[0]->receiver_id;
        if(not defined $msg_id or not defined $receiver_id){
            $self->error("撤回消息失败: msg对象中包含无效的msg_id");
            return;
        }
    }
    else{
        ($msg_id, $receiver_id)  = $_[0] =~ /^([^:]+):(.+)$/;
        if(not defined $msg_id or not defined $receiver_id){
            $self->error("撤回消息失败: 无效的msg_id");
            return;
        }
    }
    my $ret = $self->_revoke_message($msg_id,$receiver_id);
    if($ret){
        $self->debug("消息[$msg_id]撤回成功");
    }
    else{
        $self->debug("消息[$msg_id]撤回失败");
    }
    return $ret;
}


1;
