File Coverage

blib/lib/Mojo/Weixin.pm
Criterion Covered Total %
statement 21 123 17.0
branch 0 40 0.0
condition 0 12 0.0
subroutine 7 29 24.1
pod 3 8 37.5
total 31 212 14.6


line stmt bran cond sub pod time code
1             package Mojo::Weixin;
2             our $VERSION = '1.4.4';
3 1     1   66195 use Mojo::Weixin::Base 'Mojo::EventEmitter';
  1         2  
  1         5  
4 1     1   509 use Mojo::IOLoop;
  1         141348  
  1         6  
5 1     1   473 use Mojo::Weixin::Log;
  1         2  
  1         9  
6 1     1   39 use File::Spec ();
  1         2  
  1         16  
7 1     1   5 use POSIX ();
  1         1  
  1         13  
8 1     1   4 use Carp ();
  1         2  
  1         31  
9 1     1   6 use base qw(Mojo::Weixin::Util Mojo::Weixin::Model Mojo::Weixin::Client Mojo::Weixin::Plugin Mojo::Weixin::Request);
  1         2  
  1         511  
10              
11             has http_debug => sub{$ENV{MOJO_WEIXIN_HTTP_DEBUG} || 0 } ;
12             has ua_debug => sub{$_[0]->http_debug};
13             has ua_debug_req_body => sub{$_[0]->ua_debug};
14             has ua_debug_res_body => sub{$_[0]->ua_debug};
15             has ua_retry_times => 5;
16             has ua_connect_timeout => 10;
17             has ua_request_timeout => 35;
18             has ua_inactivity_timeout => 35;
19             has log_level => 'info'; #debug|info|msg|warn|error|fatal
20             has log_path => undef;
21             has log_encoding => undef; #utf8|gbk|...
22             has log_head => undef;
23             has log_console => 1;
24             has log_unicode => 0;
25             has download_media => 1;
26             has disable_color => ($^O eq 'MSWin32' ? 1 : 0); #是否禁用终端打印颜色
27             has send_interval => 3; #全局发送消息间隔
28             has json_codec_mode => 0; #0表示使用from_json/to_json 1表示使用decode_json/encode_json
29              
30             has notice_api => 'https://raw.githubusercontent.com/sjdy521/Mojo-Weixin/master/NOTICE';
31             has is_fetch_notice => 1; #是否启动时获取公告
32              
33             has is_init_group_member => 0;
34             has is_update_group_member => 1;
35             has is_update_all_friend => 1;
36              
37             has account => sub{ $ENV{MOJO_WEIXIN_ACCUNT} || 'default'};
38             has start_time => time;
39             has tmpdir => sub {$ENV{MOJO_WEIXIN_TMPDIR} || File::Spec->tmpdir();};
40             has media_dir => sub {$_[0]->tmpdir};
41             has cookie_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_weixin_cookie_',$_[0]->account || 'default','.dat'))};
42             has qrcode_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_weixin_qrcode_',$_[0]->account || 'default','.jpg'))};
43             has pid_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_weixin_pid_',$_[0]->account || 'default','.pid'))};
44             has state_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_weixin_state_',$_[0]->account || 'default','.json'))};
45             has ioloop => sub {Mojo::IOLoop->singleton};
46             has keep_cookie => 1;
47             has fix_media_loop => 1;
48             has synccheck_interval => 1;
49             has synccheck_delay => 1;
50             has _synccheck_interval => sub{ $_[0]->synccheck_interval};
51             has sync_interval => 0;
52             has emoji_to_text => 1;
53             has stop_with_mobile => 0;
54             has http_max_message_size => undef; #16777216;
55             has controller_pid => sub{$ENV{MOJO_WEIXIN_CONTROLLER_PID}};
56              
57             has user => sub {+{}};
58             has friend => sub {[]};
59             has group => sub {[]};
60             has data => sub {+{}};
61              
62             has version => $Mojo::Weixin::VERSION;
63             has plugins => sub{+{}};
64             has log => sub{
65             Mojo::Weixin::Log->new(
66             encoding => $_[0]->log_encoding,
67             unicode_support => $_[0]->log_unicode,
68             path => $_[0]->log_path,
69             level => $_[0]->log_level,
70             head => $_[0]->log_head,
71             disable_color => $_[0]->disable_color,
72             console_output => $_[0]->log_console,
73             )
74             };
75              
76             has is_ready => 0;
77             has is_stop => 0;
78             has is_first_login => -1;
79             has login_state => 'init';
80             has qrcode_upload_url => undef;
81             has qrcode_uuid => undef;
82             has qrcode_count => 0;
83             has qrcode_count_max => 10;
84             has media_size_max => sub{20 * 1024 * 1024}; #运行上传的最大文件大小
85             has media_chunk_size => sub{512 * 1024};#chunk upload 每个分片的大小
86             has http_agent => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062';
87             has ua => sub {
88             my $self = $_[0];
89             #local $ENV{MOJO_USERAGENT_DEBUG} = $_[0]->ua_debug;
90             local $ENV{MOJO_MAX_MESSAGE_SIZE} = $_[0]->http_max_message_size if defined $_[0]->http_max_message_size;
91             require Mojo::UserAgent;
92             require Mojo::UserAgent::Proxy;
93             require Storable if $_[0]->keep_cookie;
94             my $transactor = Mojo::UserAgent::Transactor->new(name => $self->http_agent);
95             my $default_form_generator = $transactor->generators->{form};
96             $transactor->add_generator(form => sub{
97             #my ($self, $tx, $form, %options) = @_;
98             $self->reform($_[2],unicode=>1,recursive=>1,filter=>sub{
99             my($type,$deep,$key) = @_;
100             return 1 if $type ne 'HASH';
101             return 1 if $deep == 0;
102             return 0 if $deep == 1 and $key =~ /^filename|file|content$/;
103             return 1;
104             });
105             $default_form_generator->(@_);
106             });
107             $transactor->add_generator(json=>sub{
108             $_[1]->req->body($self->to_json($_[2]))->headers->content_type('application/json');
109             return $_[1];
110             });
111             Mojo::UserAgent->new(
112             proxy => sub{ my $proxy = Mojo::UserAgent::Proxy->new;$proxy->detect;$proxy}->(),
113             max_redirects => 7,
114             connect_timeout => $self->ua_connect_timeout,
115             request_timeout => $self->ua_request_timeout,
116             inactivity_timeout => $self->ua_inactivity_timeout,
117             transactor => $transactor,
118             );
119             };
120              
121             has message_queue => sub{$_[0]->gen_message_queue()};
122             has pass_ticket => '';
123             has skey => '';
124             has wxsid => '';
125             has wxuin => '';
126             has domain => 'wx.qq.com';
127             has lang => 'zh_CN';
128              
129             has _sync_running => 0;
130             has _synccheck_running => 0;
131             has _synccheck_error_count => 0;
132             has _synccheck_connection_id => undef;
133              
134             has sync_key => sub{+{List=>[]}};
135             sub synccheck_key {
136 0     0 0   my $self = shift;
137 0 0         if(@_==0){
138 0   0       return $self->{synccheck_key} // $self->sync_key;
139             }
140             else{
141 0           $self->{synccheck_key} = $_[0];
142 0           return $self;
143             }
144             }
145              
146 0     0 0   sub deviceid { return "e" . substr(rand() . ("0" x 15),2,15);}
147             sub state {
148 0     0 0   my $self = shift;
149 0 0         $self->{state} = 'init' if not defined $self->{state};
150 0 0 0       if(@_ == 0){#get
    0          
151 0           return $self->{state};
152             }
153             elsif($_[0] and $_[0] ne $self->{state}){#set
154 0           my($old,$new) = ($self->{state},$_[0]);
155 0           $self->{state} = $new;
156 0           $self->emit(state_change=>$old,$new);
157             }
158 0           $self;
159             }
160             sub on {
161 0     0 1   my $self = shift;
162 0           my @return;
163 0           while(@_){
164 0           my($event,$callback) = (shift,shift);
165 0           push @return,$self->SUPER::on($event,$callback);
166             }
167 0 0         return wantarray?@return:$return[0];
168             }
169             sub emit {
170 0     0 1   my $self = shift;
171 0           $self->SUPER::emit(@_);
172 0           $self->SUPER::emit(all_event=>@_);
173             }
174              
175             sub wait_once {
176 0     0 0   my $self = shift;
177 0           my($timeout,$timeout_callback,$event,$event_callback)=@_;
178 0           my ($timer_id, $subscribe_id);
179             $timer_id = $self->timer($timeout,sub{
180 0     0     $self->unsubscribe($event,$subscribe_id);
181 0 0         $timeout_callback->(@_) if ref $timeout_callback eq "CODE";
182 0           });
183             $subscribe_id = $self->once($event=>sub{
184 0     0     $self->ioloop->remove($timer_id);
185 0 0         $event_callback->(@_) if ref $event_callback eq "CODE";
186 0           });
187 0           $self;
188             }
189              
190             sub wait {
191 0     0 0   my $self = shift;
192 0           my($timeout,$timeout_callback,$event,$event_callback)=@_;
193 0           my ($timer_id, $subscribe_id);
194             $timer_id = $self->timer($timeout,sub{
195 0     0     $self->unsubscribe($event,$subscribe_id);
196 0 0         $timeout_callback->(@_) if ref $timeout_callback eq "CODE";;
197 0           });
198             $subscribe_id = $self->on($event=>sub{
199 0 0   0     my $ret = ref $event_callback eq "CODE"?$event_callback->(@_):0;
200 0 0         if($ret){ $self->ioloop->remove($timer_id);$self->unsubscribe($event,$subscribe_id); }
  0            
  0            
201 0           });
202 0           $self;
203             }
204             sub new {
205 0     0 1   my $class = shift;
206 0           my $self = $class->SUPER::new(@_);
207             #$ENV{MOJO_USERAGENT_DEBUG} = $self->{ua_debug};
208            
209 0           for my $env(keys %ENV){
210 0 0         if($env=~/^MOJO_WEIXIN_([A-Z_]+)$/){
211 0           my $attr = lc $1;
212 0 0         next if $attr =~ /^plugin_/;
213 0 0         $self->$attr($ENV{$env}) if $self->can($attr);
214             }
215             }
216              
217 0           $self->info("当前正在使用 Mojo-Weixin v" . $self->version);
218             $self->ioloop->reactor->on(error=>sub{
219 0     0     return;
220 0           my ($reactor, $err) = @_;
221 0           $self->error("reactor error: " . Carp::longmess($err));
222 0           });
223 0     0     $SIG{__WARN__} = sub{$self->warn(Carp::longmess @_);};
  0            
224             $self->on(error=>sub{
225 0     0     my ($self, $err) = @_;
226 0           $self->error(Carp::longmess($err));
227 0           });
228 0           $self->check_pid();
229 0           $self->check_controller(1);
230 0           $self->load_cookie();
231 0           $self->save_state();
232 0           $SIG{CHLD} = 'IGNORE';
233             $SIG{INT} = $SIG{TERM} = $SIG{HUP} = sub{
234 0 0 0 0     if($^O ne 'MSWin32' and $_[0] eq 'INT' and !-t){
      0        
235 0           $self->warn("后台程序捕获到信号[$_[0]],已将其忽略,程序继续运行");
236 0           return;
237             }
238 0           $self->info("捕获到停止信号[$_[0]],准备停止...");
239 0           $self->stop();
240 0           };
241             $self->on(stop=>sub{
242 0     0     my $self = shift;
243 0           $self->clean_qrcode();
244 0           $self->clean_pid();
245 0           });
246             $self->on(state_change=>sub{
247 0     0     my $self = shift;
248 0           $self->save_state(@_);
249 0           });
250             $self->on(qrcode_expire=>sub{
251 0     0     my($self) = @_;
252 0           my $count = $self->qrcode_count;
253 0           $self->qrcode_count(++$count);
254 0 0         if($self->qrcode_count >= $self->qrcode_count_max){
255 0           $self->stop();
256             }
257 0           });
258 0 0         if($self->fix_media_loop){
259             $self->on(receive_message=>sub{
260 0     0     my($self,$msg) = @_;
261 0 0         $self->synccheck_interval($msg->format eq "media"?3:1);
262 0           });
263             $self->on(send_message=>sub{
264 0     0     my($self,$msg,$status) = @_;
265 0 0         $self->synccheck_interval($msg->format eq "media"?3:1);
266 0 0         $msg->reply(" ") if $self->fix_media_loop == 2;
267 0           });
268             }
269             $self->on(update_friend=>sub{
270 0     0     my $self = shift;
271 0           my $filehelper = Mojo::Weixin::Friend->new(name=>"文件传输助手",id=>"filehelper");
272 0 0         $self->add_friend($filehelper) if not $self->search_friend(id=>"filehelper");
273 0           });
274 0           $Mojo::Weixin::Message::SEND_INTERVAL = $self->send_interval;
275 0           $Mojo::Weixin::_CLIENT = $self;
276 0           $self->check_notice();
277 0           $self;
278             }
279              
280             1;