wechat-bot2 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2d129e298145340b212f0f23de82dbf6c571580d29984553c68e77339fa8ce4d
4
- data.tar.gz: 15d8e87b1bebc29ff8b8727f64dbe62b3dad74d5b61bd2920050e7d9b320b50e
3
+ metadata.gz: 653832898538ffcb6e9d48f9415fbd3ee1da167e4e5d43a4d97ad8da3649e37e
4
+ data.tar.gz: '08a1a65f53a4aa5c479d85fd29605c4c358dbebbae48c00055124818564ce480'
5
5
  SHA512:
6
- metadata.gz: 2fb047ab3e2829968a859c16e38ba8ad5cb51f74d21f6395903ecc94a363727dd4b11d4e54bf8e408db75ba3112a6a3b921f6f0ffc1ec431df2b0c91ed59e0f8
7
- data.tar.gz: 5de0e2c9a843309fc2ecf67aeff30ad71377193d5b0e72c2160a740b9470c25a8d78fd141d5adc6c6c4f702bc156b6ea0af8cd8cfc475421196bd8d0c9d47442
6
+ metadata.gz: 97487df4a67f2591420baa1e5aea86f60f89dd7f27f1c6162bd57225204085be3b21f88a78ab2369807b89f62b2c0ba19e0ea77b1e62843a664ecb8ddeb2192d
7
+ data.tar.gz: '07833b7ba0df389c343929405bb6bfbaa97f23e248bfc2b81c0f3b5cdd5d3787e850167452da9045b9735079884b4f464cb609e5b2540e542b4b5275c9c87775'
data/Rakefile CHANGED
@@ -8,6 +8,8 @@ RSpec::Core::RakeTask.new(:spec)
8
8
  require 'rubocop/rake_task'
9
9
  RuboCop::RakeTask.new
10
10
 
11
+ require 'irb'
12
+
11
13
  task :default => [:rubocop, :spec]
12
14
 
13
15
  desc 'Run a sample wechat bot'
@@ -39,3 +41,19 @@ task :bot do
39
41
 
40
42
  bot.start
41
43
  end
44
+
45
+ desc 'Enable irb with var `bot` & `client`'
46
+ task :irb do
47
+ bot = WeChat::Bot.new do
48
+ logger = self.logger
49
+ on :message do |m|
50
+ logger.info "Message Raw: #{m.raw}"
51
+ end
52
+ end
53
+
54
+ client = bot.client
55
+ client.login
56
+ client.contacts
57
+
58
+ binding.irb # since ruby-2.4
59
+ end
@@ -1,6 +1,8 @@
1
1
  require "rqrcode"
2
2
  require "logger"
3
3
  require "uri"
4
+ require "digest"
5
+ require "json"
4
6
 
5
7
  module WeChat::Bot
6
8
  # 微信 API 类
@@ -141,7 +143,7 @@ module WeChat::Bot
141
143
  #
142
144
  # @return [Array]
143
145
  def login_status(uuid)
144
- timestamp = timestamp
146
+ timestamp = timestamp()
145
147
  params = {
146
148
  "loginicon" => "true",
147
149
  "uuid" => uuid,
@@ -195,7 +197,7 @@ module WeChat::Bot
195
197
  #
196
198
  # 掉线后 300 秒可以重新使用此 api 登录获取的联系人和群ID保持不变
197
199
  def login_loading
198
- url = "#{store(:index_url)}/webwxinit?r=#{timestamp}"
200
+ url = api_url('webwxinit', r: timestamp)
199
201
  r = @session.post(url, json: params_base_request)
200
202
  data = r.parse(:json)
201
203
 
@@ -215,7 +217,7 @@ module WeChat::Bot
215
217
  #
216
218
  # 需要解密参数 Code 的值的作用,目前都用的是 3
217
219
  def update_notice_status
218
- url = "#{store(:index_url)}/webwxstatusnotify?lang=zh_CN&pass_ticket=#{store(:pass_ticket)}"
220
+ url = api_url('webwxstatusnotify', lang: 'zh_CN', pass_ticket: store(:pass_ticket))
219
221
  params = params_base_request.merge({
220
222
  "Code" => 3,
221
223
  "FromUserName" => @bot.profile.username,
@@ -267,12 +269,11 @@ module WeChat::Bot
267
269
  # 根据 {#sync_check} 接口返回有数据时需要调用该接口
268
270
  # @return [void]
269
271
  def sync_messages
270
- query = {
272
+ url = api_url('webwxsync', {
271
273
  "sid" => store(:sid),
272
274
  "skey" => store(:skey),
273
275
  "pass_ticket" => store(:pass_ticket)
274
- }
275
- url = "#{store(:index_url)}/webwxsync?#{URI.encode_www_form(query)}"
276
+ })
276
277
  params = params_base_request.merge({
277
278
  "SyncKey" => store(:sync_key),
278
279
  "rr" => "-#{timestamp}"
@@ -313,12 +314,11 @@ module WeChat::Bot
313
314
  #
314
315
  # @return [Hash] 联系人列表
315
316
  def contacts
316
- query = {
317
+ url = api_url('webwxgetcontact', {
317
318
  "r" => timestamp,
318
319
  "pass_ticket" => store(:pass_ticket),
319
320
  "skey" => store(:skey)
320
- }
321
- url = "#{store(:index_url)}/webwxgetcontact?#{URI.encode_www_form(query)}"
321
+ })
322
322
 
323
323
  r = @session.post(url, json: {})
324
324
  data = r.parse(:json)
@@ -326,6 +326,8 @@ module WeChat::Bot
326
326
  @bot.contact_list.batch_sync(data["MemberList"])
327
327
  end
328
328
 
329
+ alias_method :_send, :send
330
+
329
331
  # 消息发送
330
332
  #
331
333
  # @param [Symbol] type 消息类型,未知类型默认走 :text
@@ -339,6 +341,8 @@ module WeChat::Bot
339
341
  case type
340
342
  when :emoticon
341
343
  send_emoticon(username, content)
344
+ when :image
345
+ send_image(username, content: content)
342
346
  else
343
347
  send_text(username, content)
344
348
  end
@@ -350,7 +354,7 @@ module WeChat::Bot
350
354
  # @param [String] text 消息内容
351
355
  # @return [Hash<Object,Object>] 发送结果状态
352
356
  def send_text(username, text)
353
- url = "#{store(:index_url)}/webwxsendmsg"
357
+ url = api_url('webwxsendmsg')
354
358
  params = params_base_request.merge({
355
359
  "Scene" => 0,
356
360
  "Msg" => {
@@ -367,34 +371,96 @@ module WeChat::Bot
367
371
  r.parse(:json)
368
372
  end
369
373
 
374
+ # FIXME: 上传图片出问题,未能解决
375
+ def upload_image(username, file)
376
+ url = "#{store(:file_url)}/webwxuploadmedia?f=json"
377
+
378
+ filename = File.basename(file.path)
379
+ content_type = {'png'=>'image/png', 'jpg'=>'image/jpeg', 'jpeg'=>'image/jpeg'}[filename.split('.').last.downcase] || 'application/octet-stream'
380
+ md5 = Digest::MD5.file(file.path).hexdigest
381
+
382
+ headers = {
383
+ 'Host' => 'file.wx.qq.com',
384
+ 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:42.0) Gecko/20100101 Firefox/42.0',
385
+ 'Accept' => '*/*',
386
+ 'Accept-Language' => 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
387
+ 'Accept-Encoding' => 'gzip, deflate, br',
388
+ 'Referer' => 'https://wx.qq.com/',
389
+ 'Origin' => 'https://wx.qq.com',
390
+ 'Connection' => 'Keep-Alive'
391
+ }
392
+
393
+ @media_cnt = 1 + (@media_cnt || -1)
394
+
395
+ params = {
396
+ 'id' => "WU_FILE_#{@media_cnt}",
397
+ 'name' => filename,
398
+ 'type' => content_type,
399
+ 'lastModifiedDate' => 'Tue Sep 09 2014 17:47:23 GMT+0800 (CST)',
400
+ 'size' => file.size,
401
+ 'mediatype' => 'pic', # pic/video/doc
402
+ 'uploadmediarequest' => JSON.generate(
403
+ params_base_request.merge({
404
+ 'UploadType' => 2,
405
+ 'ClientMediaId' => timestamp,
406
+ 'TotalLen' => file.size,
407
+ 'StartPos' => 0,
408
+ 'DataLen' => file.size,
409
+ 'MediaType' => 4,
410
+ 'FromUserName' => @bot.profile.username,
411
+ 'ToUserName' => username,
412
+ 'FileMd5' => md5
413
+ })
414
+ ),
415
+ 'webwx_data_ticket' => @session.cookie_of('webwx_data_ticket'),
416
+ 'pass_ticket' => store(:pass_ticket),
417
+ 'filename' => ::HTTP::FormData::File.new(file, content_type: content_type)
418
+ }
419
+
420
+ r = @session.post(url, form: params, headers: headers)
421
+
422
+ # @bot.logger.info "Response: #{r.inspect}"
423
+
424
+ r.parse(:json)
425
+ end
426
+
370
427
  # 发送图片
371
428
  #
372
429
  # @param [String] username 目标 UserName
373
430
  # @param [String, File] 图片名或图片文件
374
431
  # @param [Hash] 非文本消息的参数(可选)
375
432
  # @return [Boolean] 发送结果状态
376
- # def send_image(username, image, media_id = nil)
377
- # if media_id.nil?
378
- # media_id = upload_file(image)
379
- # end
380
-
381
- # url = "#{store(:index_url)}/webwxsendmsgimg?fun=async&f=json"
382
-
383
- # params = params_base_request.merge({
384
- # "Scene" => 0,
385
- # "Msg" => {
386
- # "Type" => type,
387
- # "FromUserName" => @bot.profile.username,
388
- # "ToUserName" => username,
389
- # "MediaId" => mediaId,
390
- # "LocalID" => timestamp,
391
- # "ClientMsgId" => timestamp,
392
- # },
393
- # })
394
-
395
- # r = @session.post(url, json: params)
396
- # r.parse(:json)
397
- # end
433
+ def send_image(username, **opts)
434
+ # if media_id.nil?
435
+ # media_id = upload_file(image)
436
+ # end
437
+ if opts[:media_id]
438
+ conf = {"MediaId" => opts[:media_id], "Content" => ""}
439
+ elsif opts[:image]
440
+ media_id = upload_image(username, opts[:image])
441
+ conf = {"MediaId" => media_id, "Content" => ""}
442
+ elsif opts[:content]
443
+ conf = {"MediaId" => "", "Content" => opts[:content]}
444
+ else
445
+ raise RuntimeException, "发送图片参数错误,须提供media_id或content"
446
+ end
447
+
448
+ url = "#{store(:index_url)}/webwxsendmsgimg?fun=async&f=json"
449
+
450
+ params = params_base_request.merge({
451
+ "Scene" => 0,
452
+ "Msg" => {
453
+ "Type" => 3,
454
+ "FromUserName" => @bot.profile.username,
455
+ "ToUserName" => username,
456
+ "LocalID" => timestamp,
457
+ "ClientMsgId" => timestamp,
458
+ }.merge(conf)
459
+ })
460
+
461
+ r = @session.post(url, json: params)
462
+ r.parse(:json)
463
+ end
398
464
 
399
465
  # 发送表情
400
466
  #
@@ -405,12 +471,11 @@ module WeChat::Bot
405
471
  #
406
472
  # @return [Hash<Object,Object>] 发送结果状态
407
473
  def send_emoticon(username, emoticon_id)
408
- query = {
474
+ url = api_url('webwxsendemoticon', {
409
475
  'fun' => 'sys',
410
476
  'pass_ticket' => store(:pass_ticket),
411
477
  'lang' => 'zh_CN'
412
- }
413
- url = "#{store(:index_url)}/webwxsendemoticon?#{URI.encode_www_form(query)}"
478
+ })
414
479
  params = params_base_request.merge({
415
480
  "Scene" => 0,
416
481
  "Msg" => {
@@ -435,7 +500,7 @@ module WeChat::Bot
435
500
  # @param [String] message_id
436
501
  # @return [TempFile]
437
502
  def download_image(message_id)
438
- url = "#{store(:index_url)}/webwxgetmsgimg"
503
+ url = api_url('webwxgetmsgimg')
439
504
  params = {
440
505
  "msgid" => message_id,
441
506
  "skey" => store(:skey)
@@ -460,7 +525,7 @@ module WeChat::Bot
460
525
  # @param [Array<String>] users
461
526
  # @return [Hash<Object, Object>]
462
527
  def create_group(*users)
463
- url = "#{store(:index_url)}/webwxcreatechatroom?r=#{timestamp}"
528
+ url = api_url('webwxcreatechatroom', r: timestamp, pass_ticket: store(:pass_ticket))
464
529
  params = params_base_request.merge({
465
530
  "Topic" => "",
466
531
  "MemberCount" => users.size,
@@ -471,11 +536,66 @@ module WeChat::Bot
471
536
  r.parse(:json)
472
537
  end
473
538
 
539
+ #####
540
+ # 以下接口都参考:https://github.com/littlecodersh/ItChat/blob/master/itchat/components/contact.py
541
+
542
+ # 更新群组
543
+ def update_group(username, fun, update_key, update_value)
544
+ url = api_url('webwxupdatechatroom', {fun: fun, pass_ticket: store(:pass_ticket)})
545
+ params = params_base_request.merge({
546
+ "ChatRoomName" => username,
547
+ update_key => update_value
548
+ })
549
+ r = @session.post(url, json: params)
550
+ r.parse(:json)
551
+ end
552
+
553
+ # 修改群组名称
554
+ def set_group_name(username, name)
555
+ update_group(username, 'modtopic', 'NewTopic', name)
556
+ end
557
+
558
+ # 删除群组成员
559
+ def delete_group_member(username, *users)
560
+ update_group(username, 'delmember', 'DelMemberList', users.join(","))
561
+ end
562
+
563
+ # 群组邀请
564
+ def invite_group_member(username, *users)
565
+ update_group(username, 'invitemember', 'InviteMemberList', users.join(","))
566
+ end
567
+
568
+ # 群组添加
569
+ def add_group_member(username, *users)
570
+ update_group(username, 'addmember', 'AddMemberList', users.join(","))
571
+ end
572
+
573
+ # 添加好友
574
+ #
575
+ # @param [Integer] status: 2-添加 3-接受
576
+ def add_friend(username, status = 2, verify_content='')
577
+ url = api_url('webwxverifyuser', {r: timestamp, pass_ticket: store(:pass_ticket)})
578
+ params = params_base_request.merge({
579
+ "Opcode" => status, # 3
580
+ "VerifyUserListSize" => 1,
581
+ "VerifyUserList" => [{
582
+ "Value" => username,
583
+ "VerifyUserTicket" => ''}],
584
+ "VerifyContent" => verify_content,
585
+ "SceneListCount" => 1,
586
+ "SceneList" => [33],
587
+ "skey" => store(:skey)
588
+ })
589
+ r = @session.post(url, json: params)
590
+ r.parse(:json)
591
+ end
592
+ #####
593
+
474
594
  # 登出
475
595
  #
476
596
  # @return [void]
477
597
  def logout
478
- url = "#{store(:index_url)}/webwxlogout"
598
+ url = api_url('webwxlogout')
479
599
  params = {
480
600
  "redirect" => 1,
481
601
  "type" => 1,
@@ -504,6 +624,10 @@ module WeChat::Bot
504
624
 
505
625
  private
506
626
 
627
+ def api_url(path, query = {})
628
+ "#{store(:index_url)}/#{path}#{query.empty? ? '' : '?'+URI.encode_www_form(query)}"
629
+ end
630
+
507
631
  # 保存和获取存储数据
508
632
  #
509
633
  # @return [Object] 获取数据返回该变量对应的值类型
@@ -128,6 +128,8 @@ module WeChat::Bot
128
128
  "#<#{self.class}:#{object_id.to_s(16)} username='#{username}' nickname='#{nickname}' kind='#{kind}' members=#{members.size}>"
129
129
  end
130
130
 
131
+ alias :inspect :to_s
132
+
131
133
  private
132
134
 
133
135
  # 更新或新写入变量值
@@ -1,3 +1,5 @@
1
+ require "wechat/bot/contact"
2
+
1
3
  module WeChat::Bot
2
4
  # 微信联系人列表
3
5
  class ContactList < CachedList
@@ -35,6 +37,21 @@ module WeChat::Bot
35
37
  end
36
38
  end
37
39
 
40
+ Contact::Kind.constants.each do |const|
41
+ val = Contact::Kind.const_get(const)
42
+
43
+ # 查找用户分类: find_user/group/mp/special
44
+ define_method "find_#{val}", ->(pattern = nil) do
45
+ @mutex.synchronize do
46
+ return @cache.values.select do |contact|
47
+ contact.kind == val && (
48
+ pattern.nil? || (pattern.is_a?(Regexp) && pattern.match?(contact.nickname.scrub)) || contact.nickname == pattern
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+
38
55
  # 查找用户
39
56
  #
40
57
  # @param [Hash] args 接受两个参数:
@@ -68,7 +68,7 @@ module WeChat::Bot
68
68
  # @param [Array<Object>] args
69
69
  # @yieldparam [Array<String>]
70
70
  # @return [Handler]
71
- def on(event, regexp = //, *args, &block)
71
+ def on(event, regexp = %r{}, *args, &block)
72
72
  event = event.to_s.to_sym
73
73
 
74
74
  pattern = case regexp
@@ -54,6 +54,13 @@ module WeChat::Bot
54
54
  response
55
55
  end
56
56
 
57
+ def cookie_of(name)
58
+ cookies.cookies.each do |cookie|
59
+ return cookie.value if name == cookie.name
60
+ end
61
+ nil
62
+ end
63
+
57
64
  private
58
65
 
59
66
  # 组装 request 基础请求参数
@@ -104,17 +104,17 @@ module WeChat::Bot
104
104
 
105
105
  if match = group_message(message)
106
106
  message = match[1]
107
- @from_user = @from.find_member(username: match[0])
107
+ @from_user = @from.find_member(username: match[0]) unless @from.nil?
108
108
  @at_message_names = match[2]
109
109
  else
110
110
  @from_user = @from
111
111
  end
112
112
 
113
113
  @message = message
114
-
115
- parse_emoticon if @kind == Message::Kind::Emoticon
116
114
 
117
115
  case @kind
116
+ when Message::Kind::Emoticon
117
+ parse_emoticon
118
118
  when Message::Kind::ShareCard
119
119
  @meta_data = MessageData::ShareCard.parse(@message)
120
120
  end
@@ -158,11 +158,6 @@ module WeChat::Bot
158
158
  end
159
159
  end
160
160
 
161
- # 私聊或者群聊被@
162
- def talked_to?
163
- (source == Contact::Kind::User) || at_members.any?{|member| !!member && (member.username == @raw['ToUserName']) }
164
- end
165
-
166
161
  private
167
162
 
168
163
  # 解析消息来源
@@ -1,5 +1,5 @@
1
1
  module WeChat
2
2
  module Bot
3
- VERSION = "0.1.3"
3
+ VERSION = "0.1.4"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wechat-bot2
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - icyleaf
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-08-02 00:00:00.000000000 Z
11
+ date: 2018-08-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler