package Mojo::Webqq::Plugin::Openqq;
our $PRIORITY = 98;
use strict;
use POSIX qw();
use Mojo::Util qw();
use List::Util qw(first);
use Mojo::Webqq::Server;
use Mojo::Webqq::List;
my $server;
my $check_event_list;
sub call{
my $client = shift;
my $data = shift;
$check_event_list = Mojo::Webqq::List->new(max_size=>$data->{check_event_list_max_size} || 20);
$data->{post_media_data} = 1 if not defined $data->{post_media_data};
$data->{post_event} = 1 if not defined $data->{post_event};
$data->{post_event_list} = [qw(login stop state_change input_qrcode new_group new_friend new_group_member lose_group lose_friend lose_group_member)]
if ref $data->{post_event_list} ne 'ARRAY';
$data->{post_stdout} = 0 if not defined $data->{post_stdout};
if(defined $data->{poll_api}){
$client->on('_mojo_webqq_plugin_openqq_poll_over' => sub{
$client->http_get($data->{poll_api},sub{
$client->timer($data->{poll_interval} || 5,sub {$client->emit('_mojo_webqq_plugin_openqq_poll_over');});
});
});
$client->emit('_mojo_webqq_plugin_openqq_poll_over');
}
$client->on(all_event => sub{
my($client,$event,@args) =@_;
return if not first {$event eq $_} @{ $data->{post_event_list} };
if($event eq 'login' or $event eq 'stop' or $event eq 'state_change' ){
my $post_json = {};
$post_json->{post_type} = "event";
$post_json->{event} = $event;
$post_json->{params} = [@args];
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
if(defined $data->{post_api}){
my($data,$ua,$tx) = $client->http_post($data->{post_api},{ua_connect_timeout=>5,ua_request_timeout=>5,ua_inactivity_timeout=>5,ua_retry_times=>1},json=>$post_json);
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]事件[".$event . "](@args)上报成功");
}
else{
$client->warn("插件[".__PACKAGE__ . "]事件[".$event."](@args)上报失败:" . $client->encode("utf8",$tx->error->{message}));
}
}
}
elsif($event eq 'input_qrcode'){
my ($qrcode_path,$qrcode_data) = @args;
eval{ $qrcode_data = Mojo::Util::b64_encode($qrcode_data);};
if($@){
$client->warn("插件[".__PACKAGE__ . "]事件[".$event."]上报失败: $@");
return;
}
my $post_json = {};
$post_json->{post_type} = "event";
$post_json->{event} = $event;
$post_json->{params} = [$qrcode_path,$qrcode_data];
push @{$post_json->{params} },$client->qrcode_upload_url if defined $client->qrcode_upload_url;
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
if(defined $data->{post_api}){
my($data,$ua,$tx) = $client->http_post($data->{post_api},json=>$post_json);
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]事件[".$event . "]上报成功");
}
else{
$client->warn("插件[".__PACKAGE__ . "]事件[".$event."]上报失败:" . $client->encode("utf8",$tx->error->{message}));
}
}
}
elsif($event =~ /^new_group|lose_group|new_friend|lose_friend|new_discuss|lose_discuss|new_group_member|lose_group_member|new_discuss_member|lose_discuss_member$/){
my $post_json = {};
$post_json->{post_type} = "event";
$post_json->{event} = $event;
if($event =~ /^new_group_member|lose_group_member$/){
$post_json->{params} = [$args[0]->to_json_hash(0),$args[1]->to_json_hash(0)];
}
else{
$post_json->{params} = [$args[0]->to_json_hash(0)];
}
$check_event_list->append($post_json);
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
$client->http_post($data->{post_api},json=>$post_json,sub{
my($data,$ua,$tx) = @_;
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]事件[".$event."]上报成功");
}
else{
$client->warn("插件[".__PACKAGE__ . "]事件[".$event."]上报失败: ".$client->encode("utf8",$tx->error->{message}));
}
}) if defined $data->{post_api};
}
elsif($event =~ /^group_property_change|group_member_property_change|friend_property_change|user_property_change$/){
my ($object,$property,$old,$new) = @args;
my $post_json = {
post_type => "event",
event => $event,
params => [$object->to_json_hash(0),$property,$old,$new],
};
$check_event_list->append($post_json);
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
$client->http_post($data->{post_api},json=>$post_json,sub{
my($data,$ua,$tx) = @_;
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]事件[".$event."]上报成功");
}
else{
$client->warn("插件[".__PACKAGE__ . "]事件[".$event."]上报失败: ".$client->encode("utf8",$tx->error->{message}));
}
}) if defined $data->{post_api};
}
elsif($event =~ /^update_user|update_friend|update_group$/){
my ($ref) = @args;
my $post_json = {
post_type => "event",
event => $event,
params => [$event eq 'update_user'?$ref->to_json_hash():map {$_->to_json_hash()} @{$ref}],
};
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
$client->http_post($data->{post_api},json=>$post_json,sub{
my($data,$ua,$tx) = @_;
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]事件[".$event."]上报成功");
}
else{
$client->warn("插件[".__PACKAGE__ . "]事件[".$event."]上报失败: ".$client->encode("utf8",tx->error->{message}));
}
}) if defined $data->{post_api};
}
}) if $data->{post_event};
$client->on(receive_message=>sub{
my($client,$msg) = @_;
return if $msg->type !~ /^friend_message|group_message|discuss_message|sess_message$/;
my $post_json = $msg->to_json_hash;
#delete $post_json->{media_data} if ($post_json->{format} eq "media" and ! $data->{post_media_data});
$post_json->{post_type} = "receive_message";
$check_event_list->append($post_json);
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
$client->http_post($data->{post_api},json=>$post_json,sub{
my($data,$ua,$tx) = @_;
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]接收消息[".$msg->id."]上报成功");
if($tx->res->headers->content_type =~m#text/json|application/json#){
#文本类的返回结果必须是json字符串
my $json;
eval{$json = $client->decode_json($tx->res->body);$client->reform($json)};
if($@){$client->warn($@);return}
if(defined $json){
#暂时先不启用format的属性
#{code=>0,reply=>"回复的消息",format=>"text"}
#if((!defined $json->{format}) or (defined $json->{format} and $json->{format} eq "text")){
# $msg->reply(Encode::encode("utf8",$json->{reply})) if defined $json->{reply};
#}
$msg->reply($json->{reply}) if defined $json->{reply};
if($msg->type eq "group_message" and defined $json->{shutup} and $json->{shutup} == 1){
$msg->sender->shutup($json->{shutup_time} || 60);
}
#$msg->reply_media($json->{media}) if defined $json->{media} and $json->{media} =~ /^https?:\/\//;
}
}
#elsif($tx->res->headers->content_type =~ m#image/#){
# #发送图片,暂未实现
#}
}
else{
$client->warn("插件[".__PACKAGE__ . "]接收消息[".$msg->id."]上报失败: ". $client->encode("utf8",$tx->error->{message}));
}
}) if defined $data->{post_api};
});
$client->on(send_message=>sub{
my($client,$msg) = @_;
return if $msg->type !~ /^friend_message|group_message|discuss_message|sess_message$/;
my $post_json = $msg->to_json_hash;
#delete $post_json->{media_data} if ($post_json->{format} eq "media" and ! $data->{post_media_data});
$post_json->{post_type} = "send_message";
$check_event_list->append($post_json);
$client->stdout_line($client->to_json($post_json)) if $data->{post_stdout};
$client->http_post($data->{post_api},json=>$post_json,sub{
my($data,$ua,$tx) = @_;
if($tx->res->is_success){
$client->debug("插件[".__PACKAGE__ ."]发送消息[".$msg->id."]上报成功");
if($tx->res->headers->content_type =~m#text/json|application/json#){
#文本类的返回结果必须是json字符串
my $json;
eval{$json = $client->decode_json($tx->res->body);$client->reform($json)};
if($@){$client->warn($@);return}
if(defined $json){
#{code=>0,reply=>"回复的消息",format=>"text"}
if((!defined $json->{format}) or (defined $json->{format} and $json->{format} eq "text")){
$msg->reply($json->{reply}) if defined $json->{reply};
}
}
}
#elsif($tx->res->headers->content_type =~ m#image/#){
# #发送图片,暂未实现
#}
}
else{
$client->warn("插件[".__PACKAGE__ . "]发送消息[".$msg->id."]上报失败: ".$client->encode("utf8",$tx->error->{message}));
}
}) if defined $data->{post_api};
});
package Mojo::Webqq::Plugin::Openqq::App::Controller;
use Mojo::JSON ();
use Mojo::Util ();
use base qw(Mojolicious::Controller);
sub render{
my $self = shift;
if($_[0] eq 'json'){
$self->res->headers->content_type('application/json');
$self->SUPER::render(data=>Mojo::JSON::to_json($_[1]),@_[2..$#_]);
}
else{$self->SUPER::render(@_)}
}
sub safe_render{
my $self = shift;
$self->render(@_) if (defined $self->tx and !$self->tx->is_finished);
}
sub param{
my $self = shift;
my $data = $self->SUPER::param(@_);
defined $data?Mojo::Util::encode("utf8",$data):undef;
}
sub params {
my $self = shift;
my $hash = $self->req->params->to_hash ;
$client->reform($hash);
return $hash;
}
package Mojo::Webqq::Plugin::Openqq::App;
use Encode ();
use Mojolicious::Lite;
no utf8;
app->controller_class('Mojo::Webqq::Plugin::Openqq::App::Controller');
app->hook(after_render=>sub{
my ($c, $output, $format) = @_;
$c->res->headers->header("Access-Control-Allow-Origin" => "*");
my $datatype = $c->param("datatype");
return if not defined $datatype;
return if defined $datatype and $datatype ne 'jsonp';
my $jsoncallback = $c->param("callback") || 'jsoncallback' . time;
return if not defined $jsoncallback;
$$output = "$jsoncallback($$output)";
});
under sub {
my $c = shift;
if(ref $data eq "HASH" and ref $data->{auth} eq "CODE"){
my $hash = $c->params;
my $ret = 0;
eval{
$ret = $data->{auth}->($hash,$c);
};
$client->warn("插件[Mojo::Webqq::Plugin::Openqq]认证回调执行错误: $@") if $@;
$c->safe_render(json=>{code=>-6,status=>"auth failure"}) if not $ret;
return $ret;
}
else{return 1}
};
options '/*' => sub{
my $c = shift;
$c->res->headers->header("Access-Control-Allow-Origin" => "*");
$c->res->headers->header("Access-Control-Allow-Methods" => "OPTIONS, HEAD, GET, POST");
$c->res->headers->header("Access-Control-Allow-Headers" => "X-Requested-With, X-Auth-Token, Content-Type, Content-Length, Authorization");
$c->rendered(200);
};
get '/openqq/get_user_info' => sub {$_[0]->safe_render(json=>$client->user->to_json_hash());};
get '/openqq/get_friend_info' => sub {$_[0]->safe_render(json=>[map {$_->to_json_hash()} @{$client->friend}]); };
get '/openqq/get_group_info' => sub {$_[0]->safe_render(json=>[map {$_->to_json_hash()} @{$client->group}]); };
get '/openqq/get_group_basic_info' => sub {$_[0]->safe_render(json=>[map {delete $_->{member};$_} map {$_->to_json_hash()} @{$client->group}]); };
get '/openqq/get_discuss_info' => sub {$_[0]->safe_render(json=>[map {$_->to_json_hash()} @{$client->discuss}]); };
any [qw(GET POST)] => '/openqq/send_friend_message' => sub{
my $c = shift;
my $p = $c->params;
my $friend = $client->search_friend(id=>$p->{id},uid=>$p->{uid},name=>$p->{name},displayname=>$p->{displayname});
if(defined $friend){
if($p->{async}){
$client->send_friend_message($friend,$p->{content},sub{$_[1]->from("api")});
$c->safe_render(json=>{code=>0,status=>"request already in execution"});
}
else{
$c->render_later;
$client->send_friend_message($friend,$p->{content},sub{
my $msg= $_[1];
$msg->cb(sub{
my($client,$msg)=@_;
$c->safe_render(json=>{id=>$msg->id,code=>$msg->code,status=>$msg->msg,info=>$msg->info});
});
$msg->from("api");
});
}
}
else{$c->safe_render(json=>{id=>undef,code=>100,status=>"friend not found"});}
};
get '/openqq/relogin' => sub {
my $c = shift;
$c->safe_render(json=>{
code=>0,
account=>$client->account,
status=>"success, client($$) will relogin in 3 seconds",
});
$client->timer(3=>sub{$client->relogin()});#3秒后再执行,让客户端可以收到该api的响应
};
any [qw(GET POST)] => 'openqq/send_group_message' => sub{
my $c = shift;
my $p = $c->params;
my $group = $client->search_group(id=>$p->{id},uid=>$p->{uid},name=>$p->{name},displayname=>$p->{displayname});
if(defined $group){
if($p->{async}){
$client->send_group_message($group,$p->{content},sub{$_[1]->from("api")});
$c->safe_render(json=>{code=>0,status=>"request already in execution"});
}
else{
$c->render_later;
$client->send_group_message($group,$p->{content},sub{
my $msg = $_[1];
$msg->cb(sub{
my($client,$msg)=@_;
$c->safe_render(json=>{id=>$msg->id,code=>$msg->code,status=>$msg->msg,info=>$msg->info});
});
$msg->from("api");
});
}
}
else{$c->safe_render(json=>{id=>undef,code=>101,status=>"group not found"});}
};
any [qw(GET POST)] => 'openqq/send_discuss_message' => sub{
my $c = shift;
my $p = $c->params;
my $discuss = $client->search_discuss(id=>$p->{id});
if(defined $discuss){
if($p->{async}){
$client->search_discuss($discuss,$p->{content},sub{$_[1]->from("api")});
$c->safe_render(json=>{code=>0,status=>"request already in execution"});
}
else{
$c->render_later;
$client->send_discuss_message($discuss,$p->{content},sub{
my $msg = $_[1];
$msg->cb(sub{
my($client,$msg)=@_;
$c->safe_render(json=>{id=>$msg->id,code=>$msg->code,status=>$msg->msg,info=>$msg->info});
});
$msg->from("api");
});
}
}
else{$c->safe_render(json=>{id=>undef,code=>102,status=>"discuss not found"});}
};
any [qw(GET POST)] => '/openqq/send_sess_message' => sub{
my $c = shift;
my $p = $c->params;
if(defined $p->{group_id} or defined $p->{group_uid}){
my $group = $client->search_group(id=>$p->{group_id},uid=>$p->{group_uid});
my $member = defined $group?$group->search_group_member(uid=>$p->{uid},id=>$p->{id}):undef;
if(defined $member){
$c->render_later;
$client->send_sess_message($member,$p->{content},sub{
my $msg = $_[1];
$msg->cb(sub{
my($client,$msg)=@_;
$c->safe_render(json=>{id=>$msg->id,code=>$msg->code,status=>$msg->msg,info=>$msg->info});
});
$msg->from("api");
});
}
else{$c->safe_render(json=>{id=>undef,code=>103,status=>"group member not found"});}
}
elsif(defined $p->{discuss_id}){
my $discuss = $client->search_discuss(id=>$p->{discuss_id});
my $member = defined $discuss?$discuss->search_discuss_member(uid=>$p->{uid},id=>$p->{id}):undef;
if(defined $member){
$c->render_later;
$client->send_sess_message($member,$p->{content},sub{
my $msg = $_[1];
$msg->cb(sub{
my($client,$msg)=@_;
$c->safe_render(json=>{id=>$msg->id,code=>$msg->code,status=>$msg->msg,info=>$msg->info});
});
$msg->from("api");
});
}
else{$c->safe_render(json=>{id=>undef,code=>104,status=>"discuss member not found"});}
}
else{$c->safe_render(json=>{id=>undef,code=>105,status=>"discuss member or group member not found"});}
};
any [qw(GET POST)] => '/openqq/search_friend' => sub{
my $c = shift;
my @params = map {defined $_?Encode::encode("utf8",$_):$_} @{$c->req->params->pairs};
my @objects = $client->search_friend(@params);
if(@objects){
$c->safe_render(json=>[map {$_->to_json_hash()} @objects]);
}
else{
$c->safe_render(json=>{code=>100,status=>"object not found"});
}
};
any [qw(GET POST)] => '/openqq/search_group' => sub{
my $c = shift;
my @params = map {defined $_?Encode::encode("utf8",$_):$_} @{$c->req->params->pairs};
my @objects = $client->search_group(@params);
for(@objects){$client->update_group_member($_,is_blocking=>1,is_update_group_member_ext=>1) if $_->is_empty or !$_->_is_hold_member_ext};
if(@objects){
$c->safe_render(json=>[map {$_->to_json_hash()} @objects]);
}
else{
$c->safe_render(json=>{code=>100,status=>"object not found"});
}
};
any [qw(GET POST)] => '/openqq/kick_group_member' => sub{
my $c = shift;
my $p = $c->params;
my $group = $client->search_group(id=>$p->{group_id},uid=>$p->{group_uid});
if(not defined $group){
$c->safe_render(json=>{code=>100,status=>"object not found"});
return;
}
my @id = split /,/,($p->{member_id} // $p->{member_uid});
if(@id){
my @members;
for(@id){
my $member = $group->search_group_member(defined($p->{member_id})?(id=>$_):(uid=>$_));
if(not defined $member){
$c->safe_render(json=>{code=>100,status=>"member $_ not found"});
return;
}
push @members,$member;
}
if($group->kick_group_member(@members)){
$c->safe_render(json=>{code=>0,status=>"success"});
}
else{
$c->safe_render(json=>{code=>201,status=>"failure"});
}
}
else{$c->safe_render(json=>{code=>200,status=>"member id empty"});}
};
any [qw(GET POST)] => '/openqq/shutup_group_member' => sub{
my $c = shift;
my $p = $c->params;
my $group = $client->search_group(id=>$p->{group_id},uid=>$p->{group_uid});
if(not defined $group){
$c->safe_render(json=>{code=>100,status=>"object not found"});
return;
}
if(not defined $p->{time} or $p->{time}!~/^\d+$/){
$c->safe_render(json=>{code=>400,status=>"shutup time missing or error"});
return;
}
my @id = split /,/,($p->{member_id} // $p->{member_uid});
if(@id){
my @members;
for(@id){
my $member = $group->search_group_member(defined($p->{member_id})?(id=>$_):(uid=>$_));
if(not defined $member){
$c->safe_render(json=>{code=>100,status=>"member $_ not found"});
return;
}
push @members,$member;
}
if($group->shutup_group_member($p->{time} * 60 ,@members)){
$c->safe_render(json=>{code=>0,status=>"success"});
}
else{
$c->safe_render(json=>{code=>201,status=>"failure"});
}
}
else{$c->safe_render(json=>{code=>200,status=>"member id empty"});}
};
any [qw(GET POST)] => '/openqq/check_event' => sub{
my $c = shift;
$c->render_later;
if($check_event_list->size > 0){
$c->safe_render(json=>scalar($check_event_list->pick_all));
return;
}
else{
$c->inactivity_timeout(120);
my($timer,$cb);
$timer = Mojo::IOLoop->timer( 30 ,sub { $check_event_list->unsubscribe(append=>$cb);$c->safe_render(json=>[]) });
$cb = $check_event_list->once(append=>sub{
my($list,$element) = @_;
Mojo::IOLoop->remove($timer);
$c->safe_render(json=>[ $list->pick ]);
});
}
};
any [qw(GET POST)] => '/openqq/get_client_info' => sub{
my $c = shift;
$c->safe_render(json=>{
code=>0,
pid=>$$,
account=>$client->account,
os=>$^O,
version=>$client->version,
starttime=>$client->start_time,
runtime=>int(time - $client->start_time),
http_debug=>$client->http_debug,
log_encoding=>$client->log_encoding,
log_path=>$client->log_path||"",
log_level=>$client->log_level,
status=>"success",
});
};
any [qw(GET POST)] => '/openqq/stop_client' => sub{
my $c = shift;
$c->safe_render(json=>{
code=>0,
account=>$client->account,
pid=>$$,
starttime=>$client->start_time,
runtime=>int(time - $client->start_time),
status=>"success, client($$) will stop in 3 seconds",
});
$client->timer(3=>sub{$client->stop()});#3秒后再执行,让客户端可以收到该api的响应
};
any '/*' => sub{$_[0]->safe_render(text=>"api not found",status=>403)};
package Mojo::Webqq::Plugin::Openqq;
$server = Mojo::Webqq::Server->new();
$server->app($server->build_app("Mojo::Webqq::Plugin::Openqq::App"));
$server->app->secrets("hello world");
$server->app->log($client->log);
my @listen;
if(ref $data eq "HASH" and ref $data->{listen} eq "ARRAY"){
for my $listen (@{$data->{listen}}) {
if($listen->{tls}){
my $listen_url = 'https://' . ($listen->{host} // "0.0.0.0") . ":" . ($listen->{port}//443);
my @ssl_option;
for(keys %$listen){
next if ($_ eq 'tls' or $_ eq 'host' or $_ eq 'port');
my $key = $_;
my $val = $listen->{val};
$key=~s/^tls_//g;
push @ssl_option,"$_=$listen->{$_}";
}
$listen_url .= "?" . join("&",@ssl_option) if @ssl_option;
push @listen,$listen_url;
}
else{
push @listen,'http://' . ($listen->{host} // "0.0.0.0") . ":" . ($listen->{port}//5000) ;
}
}
}
else{ @listen = ( 'http://0.0.0.0:5000' ); }
$server->listen(\@listen) ;
$client->info("插件[Mojo::Webqq::Plugin::Openqq]监听地址: [ " . join(", ",@listen) . " ]");
$server->start;
}
1;