-
Notifications
You must be signed in to change notification settings - Fork 320
/
Webqq.pm
349 lines (327 loc) · 14.4 KB
/
Webqq.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
package Mojo::Webqq;
use strict;
use Carp ();
$Mojo::Webqq::VERSION = "2.2.7";
use Mojo::Webqq::Base 'Mojo::EventEmitter';
use Mojo::Webqq::Log;
use Mojo::Webqq::Cache;
use Time::HiRes qw(gettimeofday);
use File::Spec ();
use base qw(Mojo::Webqq::Model Mojo::Webqq::Client Mojo::Webqq::Plugin Mojo::Webqq::Request Mojo::Webqq::Util Mojo::Webqq::Model::Ext);
has domain => 'w.qq.com';
has account => sub{ $ENV{MOJO_WEBQQ_ACCOUNT} || 'default'};
has start_time => time;
has pwd => undef;
has security => 0;
has mode => 'online'; #online|away|busy|silent|hidden|offline|callme,
has type => 'smartqq'; #smartqq
has login_type => 'qrlogin'; #qrlogin|login
has http_debug => sub{$ENV{MOJO_WEBQQ_HTTP_DEBUG} || 0 };
has ua_debug => sub{$_[0]->http_debug};
has ua_debug_req_body => sub{$_[0]->ua_debug};
has ua_debug_res_body => sub{$_[0]->ua_debug};
has ua_connect_timeout => 10;
has ua_request_timeout => 120;
has ua_inactivity_timeout => 120;
has model_update_timeout => 15;#sub{$_[0]->ua_request_timeout};
has log_level => 'info'; #debug|info|msg|warn|error|fatal
has log_path => undef;
has log_encoding => undef; #utf8|gbk|...
has log_head => undef;
has log_unicode => 0;
has log_console => 1;
has send_interval => 3; #全局发送消息间隔时间
has check_account => 0; #是否检查预设账号与实际登录账号是否匹配
has disable_color => ($^O eq 'MSWin32' ? 1 : 0); #是否禁用终端打印颜色
has ignore_send_retcode => sub{[1202,100100]}; #对发送消息返回这些状态码不认为发送失败,不重试
has ignore_poll_retcode => sub{[102,109,110,1202,100012]}; #对接收消息返回这些状态码不认为接收失败,不重新登录
has ignore_poll_http_code => sub{[504,502]}; #忽略接收消息请求返回的502/504状态码,因为并不影响消息接收,以免引起恐慌
has ignore_unknown_id => 1; #其他设备上自己发送的消息,在webqq上会以接受消息的形式再次接收到,id还未知,是否忽略掉这种消息
has allow_message_sync => 0; #是否允许同步来自其他设备登录账号发送的消息,由于webqq自身发送消息后也会收到服务端重复的消息,且没办法和来自其他设备的消息区分,会导致出现一些不期望的结果,比如某些插件会陷入死循环等,因此默认不开启消息同步,如果你只是用来api发送消息或者irc聊天,则开启此选项,可以同步来自其他设备的消息,体验会更好一些
has json_codec_mode => 0; #0表示使用from_json/to_json 1表示使用decode_json/encode_json
has default_send_real_comp_sign => 0; #设为真值则不对发送出的<>进行转化。
# 然而这样便只能送出<>。
has group_member_card_cut_length => 21; #群名片截取长度
has group_member_card_ext_only => 0; #群名片信息是否只从扩展接口中获取,这样能够获取到完整的群名片,但并不是100%可靠
has group_member_use_fullcard => 0; #使用完整的群名片。
has group_member_use_friend_markname => 1; #使用备注名(如果存在)。
#原始信息中包含id/name/card
#扩展信息中包含uid/name/card
#二者没办法直接建立关联,只能够通过 name+card 相同时认为是匹配同一个用户,并非严谨,但大部分情况下可以满足要求
#group_member_identify_callback提供了对name和card进行自定义处理
#传递给group_member_identify_callback的参数是群成员的 ($name,$card)
#默认 group_member_identify_callback 不设置,相当于sub { my($name,$card)=@_; return $name . $card};
has group_member_identify_callback => undef;
has notice_api => 'https://raw.githubusercontent.com/sjdy521/Mojo-Webqq/master/NOTICE';
has is_fetch_notice => 1; #是否启动时获取公告
has is_init_friend => 1; #是否在首次登录时初始化好友信息
has is_init_group => 1; #是否在首次登录时初始化群组信息
has is_init_discuss => 1; #是否在首次登录时初始化讨论组信息
has is_update_user => 0; #是否定期更新个人信息
has is_update_group => 1; #是否定期更新群组信息
has is_update_group_member => 1; #是否定期更新群成员信息
has is_update_group_member_ext => 0; #是否定期更新群成员扩展信息
has is_update_friend => 1; #是否定期更新好友信息
has is_update_discuss => 1; #是否定期更新讨论组信息
has update_interval => 600; #定期更新的时间间隔
has encrypt_method => "perl"; #perl|js
has tmpdir => sub {$ENV{MOJO_WEBQQ_TMPDIR} || File::Spec->tmpdir();};
has pic_dir => sub {$_[0]->tmpdir};
has cookie_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_webqq_cookie_',$_[0]->account || 'default','.dat'))};
has verifycode_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_webqq_verifycode_',$_[0]->account || 'default','.jpg'))};
has qrcode_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_webqq_qrcode_',$_[0]->account || 'default','.png'))};
has pid_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_webqq_pid_',$_[0]->account || 'default','.pid'))};
has state_path => sub {File::Spec->catfile($_[0]->tmpdir,join('','mojo_webqq_state_',$_[0]->account || 'default','.json'))};
has ioloop => sub {Mojo::IOLoop->singleton};
has keep_cookie => 1;
has msg_ttl => 3;
has controller_pid => sub{$ENV{MOJO_WEBQQ_CONTROLLER_PID}};
has version => $Mojo::Webqq::VERSION;
has user => sub {+{}};
has friend => sub {[]};
has group => sub {[]};
has discuss => sub {[]};
has plugins => sub{+{}};
has log => sub{
Mojo::Webqq::Log->new(
encoding => $_[0]->log_encoding,
unicode_support => $_[0]->log_unicode,
path => $_[0]->log_path,
level => $_[0]->log_level,
head => $_[0]->log_head,
disable_color => $_[0]->disable_color,
console_output => $_[0]->log_console,
)
};
has sess_sig_cache => sub {Mojo::Webqq::Cache->new};
has id_to_qq_cache => sub {Mojo::Webqq::Cache->new};
has is_stop => 0;
has is_ready => 0;
has is_polling => 0;
has ua_retry_times => 5;
has is_first_login => -1;
has login_state => "init";#init|relogin|success|scaning|confirming
has qrcode_upload_url => undef;
has qrcode_count => 0;
has qrcode_count_max => 10;
has send_failure_count => 0;
has send_failure_count_max => 5;
has poll_failure_count => 0;
has poll_failure_count_max => 3;
has poll_connection_id => undef;
has message_queue => sub { $_[0]->gen_message_queue };
has ua => sub {
my $self = shift;
require Mojo::UserAgent;
require Mojo::UserAgent::Proxy;
#local $ENV{MOJO_USERAGENT_DEBUG} = $_[0]->ua_debug;
require Storable if $self->keep_cookie;
my $transactor = Mojo::UserAgent::Transactor->new(
name => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062'
);
my $default_form_generator = $transactor->generators->{form};
$transactor->add_generator(form => sub{
#my ($self, $tx, $form, %options) = @_;
$self->reform($_[2],unicode=>1,recursive=>1,filter=>sub{
my($type,$deep,$key) = @_;
return 1 if $type ne 'HASH';
return 1 if $deep == 0;
return 0 if $deep == 1 and $key =~ /^filename|file|content$/;
return 1;
});
$default_form_generator->(@_);
});
$transactor->add_generator(json=>sub{
$_[1]->req->body($self->to_json($_[2]))->headers->content_type('application/json');
return $_[1];
});
Mojo::UserAgent->new(
proxy => sub{ my $proxy = Mojo::UserAgent::Proxy->new;$proxy->detect;$proxy}->(),
max_redirects => 7,
connect_timeout => $self->ua_connect_timeout,
request_timeout => $self->ua_request_timeout,
inactivity_timeout => $self->ua_inactivity_timeout,
transactor => $transactor,
);
};
has is_need_img_verifycode => 0;
has send_msg_id => sub {
my ( $second, $microsecond ) = gettimeofday;
my $send_msg_id = $second * 1000 + $microsecond;
$send_msg_id = ( $send_msg_id - $send_msg_id % 1000 ) / 1000;
$send_msg_id = ( $send_msg_id % 10000 ) * 10000;
$send_msg_id;
};
has uid => undef;
has clientid => 53999199;
has psessionid => undef;
has ptwebqq => undef;
has skey => undef;
has vfwebqq => undef;
has passwd_sig => '';
has verifycode => undef;
has pt_verifysession => undef,
has ptvfsession => undef;
has md5_salt => undef;
has cap_cd => undef;
has isRandSalt => 0;
has rc => 1;
has api_check_sig => undef;
has pt_login_sig => undef;
has csrf_token => undef;
has model_ext => undef;
#{user=>0,friend=>0,friend_ext=>0,group=>0,group_ext=>0,discuss=>0}
has model_status => sub {+{}};
sub state {
my $self = shift;
$self->{state} = 'init' if not defined $self->{state};
if(@_ == 0){#get
return $self->{state};
}
elsif($_[0] and $_[0] ne $self->{state}){#set
my($old,$new) = ($self->{state},$_[0]);
$self->{state} = $new;
$self->emit(state_change=>$old,$new);
}
$self;
}
sub on {
my $self = shift;
my @return;
while(@_){
my($event,$callback) = (shift,shift);
push @return,$self->SUPER::on($event,$callback);
}
return wantarray?@return:$return[0];
}
sub emit {
my $self = shift;
$self->SUPER::emit(@_);
$self->SUPER::emit(all_event=>@_);
}
sub wait_once {
my $self = shift;
my($timeout,$timeout_callback,$event,$event_callback)=@_;
my ($timer_id, $subscribe_id);
$timer_id = $self->timer($timeout,sub{
$self->unsubscribe($event,$subscribe_id);
$timeout_callback->(@_) if ref $timeout_callback eq "CODE";
});
$subscribe_id = $self->once($event=>sub{
$self->ioloop->remove($timer_id);
$event_callback->(@_) if ref $event_callback eq "CODE";
});
$self;
}
sub wait {
my $self = shift;
my($timeout,$timeout_callback,$event,$event_callback)=@_;
my ($timer_id, $subscribe_id);
$timer_id = $self->timer($timeout,sub{
$self->unsubscribe($event,$subscribe_id);
$timeout_callback->(@_) if ref $timeout_callback eq "CODE";;
});
$subscribe_id = $self->on($event=>sub{
my $ret = ref $event_callback eq "CODE"?$event_callback->(@_):0;
if($ret){ $self->ioloop->remove($timer_id);$self->unsubscribe($event,$subscribe_id); }
});
$self;
}
sub new {
my $class = shift;
my $self = $class->Mojo::Base::new(@_);
for my $env(keys %ENV){
if($env=~/^MOJO_WEBQQ_([A-Z_]+)$/){
my $attr = lc $1;
next if $attr =~ /^plugin_/;
$self->$attr($ENV{$env}) if $self->can($attr);
}
}
$self->info("当前正在使用 Mojo-Webqq v" . $self->version);
#$self->warn("当前版本与1.x.x版本不兼容,改动详情参见更新日志");
$self->ioloop->reactor->on(error=>sub{
my ($reactor, $err) = @_;
$self->error("reactor error: " . Carp::longmess($err));
});
local $SIG{__WARN__} = sub{$self->warn(Carp::longmess @_);};
$self->on(error=>sub{
my ($self, $err) = @_;
$self->error(Carp::longmess($err));
});
$self->check_pid();
$self->check_controller(1);
$self->load_cookie();
$self->save_state();
$SIG{CHLD} = 'IGNORE';
$SIG{INT} = $SIG{TERM} = $SIG{HUP} = sub{
if($^O ne 'MSWin32' and $_[0] eq 'INT' and !-t){
$self->warn("后台程序捕获到信号[$_[0]],已将其忽略,程序继续运行");
return;
}
$self->info("捕获到停止信号[$_[0]],准备停止...");
$self->stop();
};
$self->on(stop=>sub{
my $self = shift;
$self->clean_qrcode();
$self->clean_pid();
});
$self->on(state_change=>sub{
my $self = shift;
$self->save_state(@_);
});
$self->on(qrcode_expire=>sub{
my($self) = @_;
my $count = $self->qrcode_count;
$self->qrcode_count(++$count);
if($self->qrcode_count >= $self->qrcode_count_max){
$self->stop();
}
});
$self->on(model_update=>sub{
my($self,$type,$status)=@_;
$self->model_status->{$type} = $status;
$self->emit("model_update_fail") if $self->get_model_status == 0;
});
$self->on(model_update_fail=>sub{
my $self = shift;
$self->info("检测到登录状态失效(1),尝试重新登录");
$self->relogin();
});
$self->on(before_send_message=>sub{
my($self,$msg) = @_;
if ($msg->send_real_comp_sign
// $self->default_send_real_comp_sign) {
return;
}
my $content = $msg->content;
$content =~s/>/>/g;
$content =~s/</</g;
$msg->content($content);
});
$self->on(send_message=>sub{
my($self,$msg)=@_;
if($msg->is_success){$self->send_failure_count(0);}
elsif($msg->code == -3){my $count = $self->send_failure_count;$self->send_failure_count(++$count);}
if($self->send_failure_count >= $self->send_failure_count_max){
$self->relogin();
}
});
$self->on(new_group=>sub{
my($self,$group)=@_;
$self->update_group($group,is_blocking=>1,is_update_group_ext=>1,is_update_group_member_ext=>1);
});
$self->on(new_group_member=>sub{
my($self,$member)=@_;
$member->group->update_group_member_ext(is_blocking=>1);
});
$self->on(new_friend=>sub{
my($self,$friend)=@_;
$self->update_friend_ext(is_blocking=>1);
});
$Mojo::Webqq::Message::SEND_INTERVAL = $self->send_interval;
$Mojo::Webqq::_CLIENT = $self;
$self->check_notice();
$self;
}
1;