NAME
Mojo::Weixin - A Weixin Client Framework base on Mojolicious
SYNOPSIS
use Mojo::Weixin;
my $client = Mojo::Weixin->new(
ua_debug => 0, #是否打印详细的debug信息
log_level => "info", #日志打印级别,debug|info|warn|error|fatal
);
#客户端进行登录
$client->login();
#客户端加载ShowMsg插件,用于打印发送和接收的消息到终端
$client->load("ShowMsg");
#ready事件触发时 表示客户端一切准备就绪:已经成功登录、已经加载完个人/好友/群信息等
#你的代码建议尽量写在 ready 事件中
$client->on(ready=>sub{
my $client = shift;
#设置接收消息事件的回调函数,在回调函数中对消息以相同内容进行回复
$client->on(receive_message=>sub{
my ($client,$msg)=@_;
$msg->reply($msg->content); #已以相同内容回复接收到的消息
#你也可以使用$msg->dump() 来打印消息结构
});
#你的其他代码写在此处
});
#客户端开始运行
$client->run();
#run相当于执行一个死循环,不会跳出循环之外
#所以run应该总是放在代码最后执行,并且不要在run之后再添加任何自己的代码了
DESCRIPTION
通过该项目,你可以完成微信基本的登录、接收和发送消息,在此基础上你可以通过插件的形式实现更多附加功能,比如:
群管理、聊天记录统计、消息报警、智能问答机器人、在群聊中执行 Perl 代码,查询 Perldoc 文档、消息转发、微信和IRC联通等
此项目是Weixin::Client模块的重构,基于Mojolicious框架,具体更多良好特性,比如:
基于Mojo::Base更好的对象模型、基于Mojo::EventEmitter灵活的事件管理机制、
基于Mojo::UserAgent统一的阻塞和非阻塞HTTP请求、基于Mojo::Log轻量级的日志记录框架 等等
推荐你在使用本模块的同时也更多的了解Mojolicious
对象和方法
客户端对象
客户端对象属性
$client->log_level #日志记录等级 默认info
$client->log_path #日志记录路径,默认undef,打印到STDERR
$client->version #客户端版本
#获取客户端属性
$client->log_level #返回结果为 "info"
#设置客户端属性
$client->log_level("debug"); #设置客户的log_level等级为 debug
客户端对象方法
new 初始化一个微信客户端对象
$client = Mojo::Weixin->new(ua_debug=>0, log_level=>"info", );
#支持的参数:
ua_debug #设置该参数,打印调试信息
keep_cookie #默认为1,0表示不保存登录cookie,1表示保存登录cookie方便下次直接恢复登录
log_level #默认级别为info,可以设置debug|info|warn|error|fatal
log_path #默认客户端输出内容打印到STDERR 设置该参数可以将客户端输出重定向到文件
log_encoding #输出日志的编码,默认自动检测,如果出现乱码可以尝试手动设置一下输出编码
#编码必须是 Encode 模块支持的格式,比如utf8 gbk等
tmpdir #程序使用的临时目录,主要用于保存一些验证码、二维码等数据,默认为系统临时目录
cookie_dir #登录cookie的保存目录,默认为 tmpdir 参数所设置的目录
pic_dir #图片接收默认地址,默认为 tmpdir 参数所设置的目录
qrcode_path #二维码保存路径,默认是 tmpdir 目录下固定文件名
login 客户端登录
relogin 客户端重新登录
logout 客户端注销
stop 客户端停止运行
run 启动主事件循环
on 注册事件
基于Mojo::EventEmitter的事件注册方法,可支持同时设置多个事件回调
$client->on("event1"=>sub{...},"event2"=>sub{...},);
参考下文客户端支持的事件
timer 定时执行
指定多少秒之后执行对应的回调函数
$client->timer(10,sub{ print "hello world\n" }); #10s后打印hello world
interval
设置每隔多少秒重复执行对应的回调函数
$client->interval(10,sub{ print "hello world\n" }); #每隔10s后打印hello world
add_job 定时执行任务
#支持的时间格式为 HH:MM 或 HH:MM:SS
$client->add_job("定时提醒","07:00",sub{$client->send_message($friend,"起床啦");});
spawn(%opt) 执行外部命令
在单独的进程中执行代码或命令
客户端采用的是单进程异步事件驱动模式,如果在代码中执行system/exec等来调用其他命令
或者执行某些阻塞的代码,比如sleep等 均会导致客户端主进程被阻塞而影响正常接收和发送消息
这种情况下,可以采用客户端提供的spawn方法,将阻塞的代码放置到单独的进程中执行,捕获进程的标准输出和标准错误
在回调函数中获取到进程执行的结果
该方法实际上参考Mojo::Run模块 并在该模块的基础上做了进一步完善
#支持的参数:
max_forks #产生的最大进程数
cmd #要执行的命令或代码
param #命令的参数
exec_timeout #命令或代码的执行超时时间
stdout_cb #命令或代码执行过程中 STDOUT 一旦有数据则会触发此回调
stderr_cb #命令或代码执行过程中 STDERR 一旦有数据则会触发此回调
exit_cb #命令或代码执行结束的回调
代码示例:
$client->spawn(
cmd => sub {print "hello world";return "ok"},
exec_timeout => 3,
exit_cb => sub{
my($pid,$hash) = @_;
#$pid 是执行程序的进程号
#$hash是一个执行结果的hash引用,结构如下:
#{
# 'cmd' => 'CODE', #执行的命令或代码
# 'time_stopped' => '1441011558.30559', #进程停止时间
# 'time_started' => '1441011557.30242', #进程开始时间
# 'time_duration_total' => '1.00345897674561', #进程执行总时间
# 'time_duration_exec' => '1.00317192077637', #进程执行时长
# 'is_timeout' => undef, #是否是超时退出
# 'exit_status' => 1, #进程退出返回值
# 'exit_core' => 0, #是否有core
# 'exit_signal' => 0, #进程退出信号
# 'param' => undef, #命令或代码执行参数
# 'stderr' => '', #进程的标准错误输出结果
# 'stdout' => 'hello world', #进程的标准输出结果
# 'result' => [ #代码的返回值
# 'ok'
# ]
#}
},
);
$client->spawn(
cmd => "ping www.qq.com", #或者写成 ['ping','www.qq.com']
exec_timeout => 3,
stdout_cb => sub{
my($pid,$chunk) = @_;
$client->print("从标准输出中实时收到数据:",$chunk,"\n");
},
stderr_cb => sub {
my($pid,$chunk) = @_;
$client->print("从标准错误中实时收到数据:",$chunk,"\n");
},
exit_cb => sub{
my($pid,$res) = @_;
$client->print("从标准输出中接收的全部数据:",$res->{stdout},"\n");
$client->print("从标准错误中接收的全部数据:",$res->{stderr},"\n");
}
);
mail(%opt,[$callback]) 非阻塞发送邮件
该方法实际上是Mojo::SMTP::Client的封装,使用该方法之前请确保你已经安装了Mojo::SMTP::Client模块
发送邮件需要设置的参数:
smtp #smtp服务器地址,例如smtp.163.com
port #smtp服务器端口,默认25
tls #0|1 是否使用tls,默认为 0
tls_ca #tls证书路径
tls_cert #tls公钥路径
tls_key #tls密钥路径
user #smtp帐号
pass #smtp密码
from #发送邮箱
to #接收邮箱
cc #抄送邮箱
subject #主题
html #邮件正文内容,html格式
text #邮件正文内容,纯文本格式
charset #主题,邮件正文的编码格式,默认UTF-8
data #设置该选项表示使用MIME::Lite生成的发送数据
$client->mail(smtp=>smtp.163.com,user=>xxx,pass=>xxx,from=>xxx,to=>xxx,subject=>"邮件测试",text=>"hello world",sub{
my ($send_status,$err) = @_;
if($send_status){print "发送成功"}
else{print "发送失败"}
});
其实也支持阻塞发送
my ($send_status,$err) = $client->mail(...);
http_get http阻塞或非阻塞http get请求
该方法为Mojo::UserAgent的get方法的封装,调用方式基本和Mojo::UserAgent->get相同,但也存在细微差别
阻塞http请求:
#标量上下文 返回http请求内容,若请求失败,返回内容为undef
my $http_body = $client->http_get($url,$header);
#列表上下文,返回http请求内容以及$ua,$tx
my ($http_body,$ua,$tx) = $client->http_get($url,$header);
#可以在http header设置一些请求相关的选项,比如:
#json=>1 表示将响应的json数据进行json decode得到perl的hash引用
#retry_times=>3 表示请求失败自动重试次数,默认使用$client->ua_retry_times的值
my $json_decode_hash = $client->http_get($url,{json=>1,retry_times=>3,Host=>"www.qq.com"});
#http post请求
$client->http_post($url,$header,form=>$post_data);
非阻塞http请求:
$client->http_get($url,$header,sub{
my($http_body,$ua,$tx) = @_;
#请求失败 $http_body 返回undef
});
注意:由于采用事件驱动,因此,你应该始终选择使用非阻塞的http请求模式,如果采用阻塞的http请求,在http请求完成之前
整个程序都是被阻塞的,无法做其他任何事(包括接收和发送消息等)
http_post 阻塞或非阻塞http post请求
和 http_get 方法类似,不再赘述
search_friend() 搜索好友
#支持按任意好友对象属性进行组合搜索,标量上下文返回符合条件的第一个好友对象,列表上下文返回全部对象
my $friend = $client->search_friend(name=>xxx,id=>xxx,);
my @friends = $client->search_friend(city=>"北京");
search_group() 搜索群组
#支持按任意群对象属性进行组合搜索,标量上下文返回符合条件的第一个好友对象,列表上下文返回全部对象
my $group = $client->search_group(name=>xxx);
send_message($friend|$group,$content,$callback) 发送文本消息
#给指定的好友对象发送好友消息
$client->send_message($friend,"hello world"); #给指定的好友发送好友消息
$friend->send("hello world"); #直接利用好友对象的方法,更简洁
$client->send_message($group,"hello world"); #给指定的群对象发送群消息
$group->send("hello world"); #直接利用群对象的方法,更简洁
#在回调中对原始即将发送的消息对象进行再次的修改
$client->send_message($friend,"hello world",sub{
my($client,$msg) = @_;
my $content = $msg->content;
$msg->content($content . "我是消息小尾巴");
});
reply_message($msg,$content,$callback) 回复指定的消息
#回复消息,如果是群消息,就回复给该群,如果是好友消息,就回复给该好友
$client->reply_message($msg,$content);
个人对象
属性:
id #唯一标识,每次登录期间有效,多次登录会发生变化
name #昵称
account #帐号
province #省份
city #城市
sex #性别
signature #个性签名
displayname #显示名称,和name相同
markname #备注名称
方法:
dump #打印对象结构
成功登录后,可以通过 $client->user 来获取到个人对象
$client->user->name; #获取个人昵称
$client->user->dump();
好友对象
属性:
id #唯一标识,每次登录期间有效,多次登录会发生变化
name #昵称
account #帐号
province #省份
city #城市
sex #性别
signature #个性签名
displayname #显示名称,如果设置了备注名称就返回备注名称,否则返回昵称
markname #备注名称
方法:
dump #打印对象结构
send #给该好友发送消息
my $friend = $client->search_friend(name=>"小灰");
$friend->dump();
$friend->send("hello world");
群组对象
属性:
id #唯一标识,每次登录期间有效,多次登录会发生变化
name #群名称
displayname #群显示名称
方法:
search_group_member #搜索群成员
me #返回群成员中的自己对象
send #发送消息给该群
dump #打印群对象结构
my $group = $client->search_group(displayname=>"红包群");
$group->send("大家好");
print "我在群中的称呼是:" . $group->me->displayname;
群成员对象
属性:
id #唯一标识,每次登录期间有效,多次登录会发生变化
name #昵称
account #微信号
province #省份
city #城市
sex #性别
signature #个性签名
displayname #成员在群组中的显示名称
markname #备注名称
方法:
dump #打印对象结构
group #返回群成员对应的群组对象
my $group = $client->search_group(displayname=>"红包群");
my $group_member = $group->search_group_member(displayname=>"小灰");
$group_member->dump;
$group_member->group->displayname;
消息对象
属性:
id #消息id
type #消息类型 friend_message|group_message 好友消息或群消息
class #消息类别 send|recv 发送消息或接收消息
time #消息发送或接收的时间
ttl #发送消息的ttl,默认是5,当ttl为0 会被消息队列丢弃
cb #消息发送完成后的回调函数,会在消息发送完之后执行
content #消息内容
format #消息格式,当前仅支持文本格式 text
方法:
sender #消息的发送者对象
receiver #消息接收者对象
group #消息对应的群组对象
reply #回复该消息
#当接收到消息时 会产生receive_message事件
#注册receive_message事件对应的回调函数
$client->on(receive_message=>sub{
my($client,$msg) = @_;
if($msg->type eq "friend_message"){#接收到好友消息
my $friend = $msg->sender; #获取到该好友对象
print "我的好友:" . $friend->displayname . "给我发了一个消息,消息的内容是:" . $msg->content;
}
elsif($msg->type eq "group_message"){#接收到群消息
my $group = $msg->group; #获取到消息对应的群组对象
my $sender = $msg->sender; #获取到发送该消息的群成员对象
print $sender->displayname . "在群:", $group->displayname . "中发了一条消息,消息的内容是:" . $msg->content;
}
$msg->reply("消息已收到"); #回复该消息
});
事件
receive_message 接收到消息
$client->on(receive_message=>sub{
my($client,$msg) = @_; #传给回调的参数是接收到的消息对象
...;
});
send_message 消息发送完成
$client->on(send_message=>sub{
my($client,$msg,$status) = @_; #传给回调的参数是发送完毕的 消息对象 和 发送状态对象
if($status->is_success){
print "消息" . $msg->id . "发送成功\n";
}
else{
print "消息" . $msg->id . "发送失败,失败原因:" . $status->info . "\n";
}
});
input_qrcode 需要扫描二维码
$client->on(input_qrcode=>sub{
my($client,$qrcode_path) = @_; #传给回调的参数是二维码图片的路径
...;
});
ready 客户端准备就绪
$client->on(input_qrcode=>sub{
my($client,) = @_;
...;
});
group_property_change 群组属性改变
$client->on(group_property_change=>sub{
my($client,$group,$property,$old_value,$new_value)=@_;
});
group_member_property_change 群成员属性改变
$client->on(group_member_property_change=>sub{
my($client,$member,$property,$old_value,$new_value)=@_;
});
friend_property_change 好友属性改变
$client->on(friend_property_change=>sub{
my($client,$friend,$property,$old_value,$new_value)=@_;
});
user_property_change 用户属性改变
$client->on(user_property_change=>sub{
my($client,$user,$property,$old_value,$new_value)=@_;
});
new_group_member 新增群成员
$client->on(new_group_member=>sub{my ($client,$member)=@_});
new_friend 新增好友
$client->on(new_friend=>sub{my ($client,$friend)=@_});
new_group 新增群组
$client->on(new_group=>sub{my ($client,$group)=@_});
关于插件
load
加载一个或者多个插件,多个插件使用数组引用,支持的插件参数包括:
priority #可选,设置插件优先级,默认是0,较高的优先级能够使得插件优先执行
auto_call #可选,设置是否加载完成后自动执行,默认为1
call_on_load #可选,加载完插件马上执行,默认为0
data #可选,设置加载插件时可以携带的数据,将会在call的时候传递给插件本身
$client->load(["plugin1","plugin2"],data=>[1,2,3,]);
$client->load("plugin",priority=>0,auto_call=>1);
加载插件时,可以通过auto_call设置是否自动执行(默认在run的时候会执行),priority可以设置插件执行的优先级
数字越大,优先级越高,插件会被优先执行
call
手动执行一个插件、适合auto_call=>0的插件的手动执行模式,当auto_call=>1时,会自动执行call
$client->call("plugin",[可选参数]);
客户端实现了一个简单的插件管理机制,插件是一个简单的call函数,包名默认是Mojo:Weixin::Plugin::
比如,我编写一个简单的hello world插件,效果是对接收到的任意消息回复一个"hello world"
编写一个包 Mojo:Weixin::Plugin::HelloWorld
package Mojo:Weixin::Plugin::HelloWorld;
our $PRIORITY = 10; #可省略,除了在load中使用priority设置优先级,也可以通过包变量设置
our $AUTO_CALL = 1; #可省略,通过包变量设置插件是否默认加载后立刻执行
sub call{
my $client = shift;
my $data = shift; #可能包含的data数据
$client->on(receive_message=>sub{
my($client,$msg)=@_;
$client->reply_message($msg,"hello world");
});
}
1;
客户端加载和执行插件的操作:
#如果你的插件并非Mojo::Weixin::Plugin::相对命名规则,则可以在名称前使用"+"表示插件绝对名称
$client->load("HelloWorld");
$client->run();
当客户端运行时,插件将会被加载并自动执行,收到消息时会自动回复hello world
注意:
当多个消息处理类的插件对同一个消息进行处理时,往往存在冲突的情况
比如一个插件对消息处理完并不希望其他插件再继续处理该消息(默认情况下,receive_message事件会广播给所有订阅该事件的回调)
这种情况下,可以通过设置不同的插件优先级,使得事件被触发时,优先级较高的插件获得优先执行
执行完成后,再通过设置$msg->allow_plugin(0) 来禁止其他插件继续处理该消息,每个消息都带有一个allow_plugin的属性
这是一种建议性的插件协议,并非强制遵守
除此之外,也可以采用插件的手动执行模式,自己根据需要来执行插件
插件列表
Mojo::Weixin::Plugin::ShowMsg
Mojo::Weixin::Plugin::Openwx
提供HTTP API接口,方便获取客户端帐号、好友、群、讨论组信息,以及通过接口发送和接收好友消息、群消息、群临时消息和讨论组临时消息
#ata是一个HASH引用
$client->load("Openqq",data=>{
listen => [ {host=>"127.0.0.1",port=>3000}, ] , #监听的地址和端口,支持多个,默认监听0.0.0.0:3000
auth => sub {my($param,$controller) = @_}, #可选,认证回调函数,用于进行请求鉴权
post_api => 'http://xxxx', #可选,你自定义的接收消息上报接口
});
#若data中设置了auth函数引用,则表示api接口开启认证
#认证函数返回值为真,认证通过,函数返回值为假,认证失败,接口返回403
#认证回调函数的第一个参数是一个HASH引用,包含get或post提交的参数信息
#第二个参数是一个Mojolicious的controller对象,适合对Mojolicious比较熟悉的情况下,利用controller进行高级的认证控制
#auth函数示例:
#简单的时间戳过期防盗链
#http://127.0.0.1:3000/openwx/send_message?id=xxxx&content=xxxx&key=xxxx&exp=xxxx
sub {
my $param = shift;
my $secret = 'this is your secret key';
return 0 if time() >= $param->{exp}; #参数值的exp为过期时间,超过过期时间链接已失效
if($param->{key} eq md5_sum($secret . join "",@$param{qw(id gid did content exp)} )){
return 1; #secret和相关参数值拼接成一个字符串后计算md5 再和参数key的值进行比较
}
else{
return 0;
}
}
#利用controller允许指定的IP可以访问,更多关于controller的资料,可以参考 Mojolicious::Controller
sub{
my ($param,$controller) = @_;
if($controller->tx->remote_address eq "127.0.0.1"){
return 1;
}
return 0;
}
#接收消息上报接口示例:
$client->load("Openwx",data=>{
listen => [{host=>xxx,port=>xxx}],
post_api=> 'http://127.0.0.1:4000/post_api',
});
#接收到消息后,插件会通过HTTP POST请求的方式将json格式的消息上报到http://127.0.0.1:3000/post_api
connect to 127.0.0.1 port 4000
POST /post_api
{ "receiver":"小灰",
"time":"1442542632",
"content":"测试一下",
"class":"recv",
"sender_id":"2372835507",
"receiver_id":"4072574066",
"group":"PERL学习交流",
"group_id":"2617047292",
"sender":"灰灰",
"id":"10856",
"type":"group_message"
}
#支持好友消息、群消息 上报
当前支持的信息获取和发送消息的API接口(均返回json格式数据):
#信息获取
/openwx/get_user_info #查询用户信息
/openwx/get_friend_info #查询好友信息
/openwx/get_group_info #查询群信息
#消息发送,均支持GET和POST
/openwx/send_message #发送消息 参数id=xxx&content=xxx 或 account=xxx&content=xxx
调用示例
http://127.0.0.1:3000/openwx/get_user_info
http://127.0.0.1:3000/openwx/send_message?id=xxx&content=hello
http://127.0.0.1:3000/openwx/send_message?account=xxx&content=hello
http://127.0.0.1:3000/openqq/send_message?id=xxx&content=%e4%bd%a0%e5%a5%bd (中文需要utf8编码并进行urlencode)
SEE ALSO
https://github.com/sjdy521/Mojo-Weixin
AUTHOR
sjdy521, <sjdy521@163.com>
COPYRIGHT AND LICENSE
Copyright (C) 2014 by sjdy521
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.1 or, at your option, any later version of Perl 5 you may have available.