wechat 0.14.0 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90aad3955d68b49a3eaf8774fdc247a6f0f8c5e7887b3abde7249fd4f4d6c64b
4
- data.tar.gz: 7c6ca0a6bd601a5527c8e99db9ddc687a4a057131dd3d6e1556ae0677781ddcd
3
+ metadata.gz: 1907505b47d02aed8ba3a23bc42f90b864928bb4516c6a08af562a7da26babcb
4
+ data.tar.gz: 615fe878b8b5fe6bfc176eb77c927957cad14b0fb71b65c6f891ddd850655ae0
5
5
  SHA512:
6
- metadata.gz: 8e83f65806db0a976da02ed4aa1bb74554af42ee0e1e9c5c9c484ec23f46a34d268dbe1396fcd2e0d6b25e70f0d99731a206ce6468a6889bc3ae523997947564
7
- data.tar.gz: a04c6ee54cf2280e2a45bc0eea3ba2d6a1d8e45e5d02e443e2c36b524da09ec96a5b4645b7e3ecfa3c9e1754ca31a40a8683c780d4e9fcd93f417be6da70e247
6
+ metadata.gz: 1aafb9ea5a0c1a1281810a0c28f7859de66851192d258939bd37e352dbe7ec5c70251418a41a70f0046a2da04230e8b925fef9bf6b3a7457d98feeb6de93d041
7
+ data.tar.gz: a7b7acc261ce1051be6c8afa93a8f6eb8da2f5f0fc932fd0240d2a63cebfa257864dad3d63b20d0c75ae4c2e98e74a27318cad6cba0a0f63eae2b144d2d75d42
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.16.0 (released at 2022-06-06)
4
+
5
+ * Support wechat draft. #305
6
+ * Add environment variable for configuring http proxy to ignore IP address changes everytime after app deployment, by @Awlter #312
7
+ * Soft drop support for Ruby 2.6, because EOL time 12 Apr 2022.
8
+
9
+ ## v0.15.1 (released at 2022-02-16)
10
+
11
+ * fix "Psych::BadAlias (Unknown alias: default)" in ruby 3.1.0 #309, reported by @otorain
12
+
13
+ ## v0.15.0 (released at 2021-12-21)
14
+
15
+ * Add wechat message json format support, by @younthu #306
16
+ * Support Rails 7 in this version.
17
+ * Fix wechat command-line 1st attempt bug #307
18
+
3
19
  ## v0.14.0 (released at 2021-09-15)
4
20
 
5
21
  * Add beta support for Conversation archive in WeCom, discuss at #303
data/README-CN.md CHANGED
@@ -1,4 +1,4 @@
1
- WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://app.travis-ci.com/Eric-Guo/wechat.svg?branch=main)](https://travis-ci.com/github/Eric-Guo/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
1
+ WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://mixtint.semaphoreci.com/badges/wechat/branches/main.svg?style=shields)](https://mixtint.semaphoreci.com/projects/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
2
2
  ======
3
3
 
4
4
  [![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Eric-Guo/wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -7,11 +7,11 @@ WeChat Gem 帮助开发者方便地在 Rails 环境中集成[微信公众平台]
7
7
 
8
8
  - 微信公众平台/企业微信[发送消息](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%8F%91%E9%80%81%E6%B6%88%E6%81%AF) API(命令行和 Web 环境都可以使用)
9
9
  - [接收消息](http://qydev.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E6%94%B6%E6%B6%88%E6%81%AF%E4%B8%8E%E4%BA%8B%E4%BB%B6)(必须运行 Web 服务器)
10
- - [微信JS-SDK](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS%E6%8E%A5%E5%8F%A3) config 接口注入权限验证
10
+ - [微信 JS-SDK](http://qydev.weixin.qq.com/wiki/index.php?title=%E5%BE%AE%E4%BF%A1JS%E6%8E%A5%E5%8F%A3) config 接口注入权限验证
11
11
  - OAuth 2.0 认证机制
12
12
  - 接收消息会话 ( Session ) 记录机制(可选)
13
13
 
14
- 命令行工具 `wechat` 可以调用各种无需 Web 环境的 API,同时也提供了 Rails Controller 的 Responder DSL 。可以帮助开发者方便地在Rails 应用中集成微信的消息处理,包括主动推送的和被动响应的消息。
14
+ 命令行工具 `wechat` 可以调用各种无需 Web 环境的 API,同时也提供了 Rails Controller 的 Responder DSL 。可以帮助开发者方便地在 Rails 应用中集成微信的消息处理,包括主动推送的和被动响应的消息。
15
15
 
16
16
  如果您的 App 还需要集成微信 OAuth2.0 除了简便的 `wechat_oauth2` 指令,也可以考虑 [omniauth-wechat-oauth2](https://github.com/skinnyworm/omniauth-wechat-oauth2) 以便和 [devise](https://github.com/heartcombo/devise) 集成提供完整的用户认证。
17
17
 
@@ -28,7 +28,7 @@ WeChat Gem 帮助开发者方便地在 Rails 环境中集成[微信公众平台]
28
28
  ```
29
29
  gem install "wechat"
30
30
  # 如果使用 Ruby 版本小于 2.6
31
- # gem install wechat -v 0.12.2
31
+ # gem install wechat -v 0.12.4
32
32
  ```
33
33
 
34
34
  或者添加下面这行到 `Gemfile`:
@@ -36,16 +36,16 @@ gem install "wechat"
36
36
  ```
37
37
  gem 'wechat'
38
38
  # 如果使用 Rails 版本小于 6
39
- # gem 'wechat', '~> 0.12.2'
39
+ # gem 'wechat', '~> 0.12.4'
40
40
  ```
41
41
 
42
- 运行下面这行代码来安装:
42
+ 运行下面这行代码来安装:
43
43
 
44
44
  ```console
45
45
  bundle install
46
46
  ```
47
47
 
48
- 运行下面这行代码来生成必要文件:
48
+ 运行下面这行代码来生成必要文件:
49
49
 
50
50
  ```console
51
51
  rails generate wechat:install
@@ -53,7 +53,7 @@ rails generate wechat:install
53
53
 
54
54
  运行 `rails g wechat:install` 后会自动生成 wechat.yml 配置,还有 wechats_controller 及相关路由配置到当前 Rails 项目。
55
55
 
56
- 启用 Session 会话记录:
56
+ 启用 Session 会话记录:
57
57
 
58
58
  ```console
59
59
  rails g wechat:session
@@ -70,7 +70,7 @@ rails g wechat:redis_store
70
70
 
71
71
  Redis 存贮相比默认的文件存贮,可以允许 Rails 应用运行在多台服务器中。如果只有一台服务器,仍然推荐使用默认的文件存贮,另外命令行不会读取 Redis 存贮的 Token 或者 Ticket。
72
72
 
73
- 启用数据库配置微信账户:
73
+ 启用数据库配置微信账户:
74
74
 
75
75
  ```console
76
76
  rails g wechat:config
@@ -196,7 +196,7 @@ test:
196
196
  # secret: "my_secret"
197
197
  ```
198
198
 
199
- 支持微信公众平台 / 企业微信多账号的注意点 ( 例如: 增加账号 `wx2` ):
199
+ 支持微信公众平台 / 企业微信多账号的注意点 ( 例如:增加账号 `wx2` ):
200
200
 
201
201
  * 配置文件可增加多个微信公众平台 ( 企业微信 ) 配置,用法类似 Rails 中 `config/database.yml` 多数据库配置的处理。 `development`, `test`, `production` 是默认账号的配置段,要想增加账号 `wx2`,你需要增加配置段 `wx2_development`, `wx2_test`, `wx2_production`。
202
202
 
@@ -219,7 +219,7 @@ test:
219
219
  environment | 字串 | 必填。配置对应的运行环境,一般有:`production`、`development`、`test`。比如 `production` 配置仅在生产环境有效。默认为 `development`。
220
220
  account | 字串 | 必填。自定义的微信账户名称。同一 `environment` 下,账户名称不允许重复。
221
221
  enabled | 布尔 | 必填。配置是否生效。默认 `true`。
222
- appid | 字串 | 公众号 id , 此字段和 `corpid` 两者必填其一。
222
+ appid | 字串 | 公众号 id ,此字段和 `corpid` 两者必填其一。
223
223
  secret | 字串 | 公众号相关配置。当公众号 `appid` 存在时必填。
224
224
  corpid | 字串 | 企业号 id。此字段和 `appid` 两者必填其一。
225
225
  corpsecret | 字串 | 企业号相关配置。当企业号 `corpid` 存在时必填。
@@ -229,7 +229,7 @@ encoding_aes_key | 字串 | 当 `encrypt_mode` 为 `true` 时必填。
229
229
  token | 字串 | 必填。
230
230
  access_token | 字串 | 必填。存储 `access token` 文件的路径。
231
231
  jsapi_ticket | 字串 | 必填。存储 `jsapi ticket` 文件的路径。
232
- skip_verify_ssl | 布尔|
232
+ skip_verify_ssl | 布尔 |
233
233
  timeout | 整数 | 默认值是 20。
234
234
  trusted_domain_fullname | 字串 |
235
235
 
@@ -237,7 +237,7 @@ trusted_domain_fullname | 字串 |
237
237
 
238
238
  ##### 配置优先级
239
239
 
240
- 注意在Rails项目根目录下运行 `wechat` 命令行工具会优先使用 `config/wechat.yml `中的 `default`配置,如果失败则使用 `~\.wechat.yml` 中的配置,以便于在生产环境下管理多个微信账号应用。
240
+ 注意在 Rails 项目根目录下运行 `wechat` 命令行工具会优先使用 `config/wechat.yml `中的 `default`配置,如果失败则使用 `~\.wechat.yml` 中的配置,以便于在生产环境下管理多个微信账号应用。
241
241
 
242
242
  如果启用数据库账户配置,数据库中的账户信息在读入 `wechat.yml` 或环境变量之后被载入。当存在同名账户时,数据库中的配置会覆盖前两者。
243
243
 
@@ -245,9 +245,9 @@ trusted_domain_fullname | 字串 |
245
245
 
246
246
  微信服务器有时请求会花很长时间,如果不配置默认为 20 秒,可视情况配置。
247
247
 
248
- ##### 配置跳过SSL认证
248
+ ##### 配置跳过 SSL 认证
249
249
 
250
- Wechat 服务器有报道曾出现 [RestClient::SSLCertificateNotVerified](http://qydev.weixin.qq.com/qa/index.php?qa=11037) 错误,此时可以选择关闭SSL验证。`skip_verify_ssl: true`
250
+ Wechat 服务器有报道曾出现 [RestClient::SSLCertificateNotVerified](http://qydev.weixin.qq.com/qa/index.php?qa=11037) 错误,此时可以选择关闭 SSL 验证。`skip_verify_ssl: true`
251
251
 
252
252
  #### 为每个 Responder 配置不同的 appid 和 secret
253
253
 
@@ -292,7 +292,7 @@ end
292
292
  </body>
293
293
  ```
294
294
 
295
- 在开发模式下,由于程序往往通过微信调试工具的服务器端调试工具反向代理被访问,此时需要配置 `trusted_domain_fullname` 以便wechat gem 可以使用正确的域名做 JS-SDK 的权限签名。
295
+ 在开发模式下,由于程序往往通过微信调试工具的服务器端调试工具反向代理被访问,此时需要配置 `trusted_domain_fullname` 以便 wechat gem 可以使用正确的域名做 JS-SDK 的权限签名。
296
296
 
297
297
  #### OAuth2.0 验证接口支持
298
298
 
@@ -333,13 +333,13 @@ end
333
333
 
334
334
  `wechat_oauth2 `封装了 OAuth2.0 验证接口和 Cookies 处理逻辑,用户仅需提供业务代码块即可。userid 指的是微信企业成员 userid,openid 是关注该公众号的用户 openid。
335
335
 
336
- 注意:
337
- * 如果使用 `wechat_responder`, 请不要在 Controller 里定义 `show` 和 `create` 方法, 否则会报错。
338
- * 如果遇到“ redirect_uri 参数错误”的错误信息,请登录服务号管理后台,查看“开发者中心/网页服务/网页授权获取用户基本信息”的授权回调页面域名已正确配置。
336
+ 注意:
337
+ * 如果使用 `wechat_responder`,请不要在 Controller 里定义 `show` 和 `create` 方法,否则会报错。
338
+ * 如果遇到 redirect_uri 参数错误” 的错误信息,请登录服务号管理后台,查看 “开发者中心/网页服务/网页授权获取用户基本信息” 的授权回调页面域名已正确配置。
339
339
 
340
340
  ## 关于接口权限
341
341
 
342
- Wechat Gem 内部不会检查权限, 但因公众号类型不同和微信服务器端通讯时,可能会被拒绝详细权限控制可参考[官方文档](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433401084)。
342
+ Wechat Gem 内部不会检查权限,但因公众号类型不同和微信服务器端通讯时,可能会被拒绝详细权限控制可参考[官方文档](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1433401084)。
343
343
 
344
344
  ## 使用命令行
345
345
 
@@ -387,7 +387,7 @@ Wechat Public Account commands:
387
387
  wechat qrcode_create_limit_scene [SCENE_ID_OR_STR] # 请求永久二维码
388
388
  wechat qrcode_create_scene [SCENE_ID_OR_STR, EXPIRE_SECONDS] # 请求临时二维码
389
389
  wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过 ticket 下载二维码
390
- wechat queryrecoresultfortext [VOICE_ID] # AI开放接口-获取语音识别结果
390
+ wechat queryrecoresultfortext [VOICE_ID] # AI 开放接口 - 获取语音识别结果
391
391
  wechat shorturl [LONG_URL] # 长链接转短链接
392
392
  wechat tag [TAGID] # 获取标签下粉丝列表
393
393
  wechat tag_add_user [TAG_ID, OPEN_IDS] # 批量为用户打标签
@@ -397,7 +397,7 @@ Wechat Public Account commands:
397
397
  wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
398
398
  wechat tags # 获取所有标签
399
399
  wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
400
- wechat translatecontent [CONTENT] # AI开放接口-微信翻译
400
+ wechat translatecontent [CONTENT] # AI 开放接口 - 微信翻译
401
401
  wechat user [OPEN_ID] # 获取用户基本信息
402
402
  wechat user_batchget [OPEN_ID_LIST] # 批量获取用户基本信息
403
403
  wechat user_change_group [OPEN_ID, TO_GROUP_ID] # 移动用户分组
@@ -451,7 +451,7 @@ Wechat Enterprise Account commands:
451
451
  wechat menu_delete # 删除菜单
452
452
  wechat menu_trymatch [USER_ID] # 测试个性化菜单匹配结果
453
453
  wechat message_send [OPENID, TEXT_MESSAGE] # 发送文字消息
454
- wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
454
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过 ticket 下载二维码
455
455
  wechat tag [TAG_ID] # 获取标签成员
456
456
  wechat tag_add_department [TAG_ID, PARTY_IDS] # 增加标签部门
457
457
  wechat tag_add_user [TAG_ID, USER_IDS] # 增加标签成员
@@ -587,7 +587,7 @@ template:
587
587
  value: "您好,您已报名成功"
588
588
  color: "#0A0A0A"
589
589
  keynote1:
590
- value: "XX活动"
590
+ value: "XX 活动"
591
591
  color: "#CCCCCC"
592
592
  keynote2:
593
593
  value: "2014 年 9 月 16 日"
@@ -668,14 +668,14 @@ class WechatsController < ActionController::Base
668
668
  request.reply.text "echo: #{content}" #Just echo
669
669
  end
670
670
 
671
- # 当请求的文字信息内容为 'help' 时, 使用这个 responder 处理
671
+ # 当请求的文字信息内容为 'help' 时,使用这个 responder 处理
672
672
  on :text, with: 'help' do |request|
673
673
  request.reply.text 'help content' #回复帮助信息
674
674
  end
675
675
 
676
- # 当请求的文字信息内容为'<n>条新闻'时, 使用这个responder处理, 并将n作为第二个参数
677
- on :text, with: /^(\d+)条新闻$/ do |request, count|
678
- # 微信最多显示8条新闻,大于8条将只取前8条
676
+ # 当请求的文字信息内容为'<n>条新闻'时,使用这个 responder 处理,并将 n 作为第二个参数
677
+ on :text, with: /^(\d+) 条新闻$/ do |request, count|
678
+ # 微信最多显示 8 条新闻,大于 8 条将只取前 8
679
679
  news = (1..count.to_i).each_with_object([]) { |n, memo| memo << { title: '新闻标题', content: "第#{n}条新闻的内容#{n.hash}" } }
680
680
  request.reply.news(news) do |article, n, index| # 回复"articles"
681
681
  article.item title: "#{index} #{n[:title]}", description: n[:content], pic_url: 'http://www.baidu.com/img/bdlogo.gif', url: 'http://www.baidu.com/'
@@ -737,7 +737,7 @@ class WechatsController < ActionController::Base
737
737
  # request.reply.voice(request[:MediaId])
738
738
 
739
739
  voice_id = request[:MediaId]
740
- # 开通语音识别后,用户每次发送语音给服务号时,微信会在推送的语音消息XML数据包中,增加一个 Recognition 字段
740
+ # 开通语音识别后,用户每次发送语音给服务号时,微信会在推送的语音消息 XML 数据包中,增加一个 Recognition 字段
741
741
  recognition = request[:Recognition]
742
742
  request.reply.text "#{voice_id} #{recognition}"
743
743
  end
@@ -806,12 +806,12 @@ class WechatsController < ActionController::Base
806
806
  request.reply.success # request is XML result hash.
807
807
  end
808
808
 
809
- # 当无任何 responder 处理用户信息时,使用这个 responder 处理
809
+ # 当无任何 responder 处理用户信息时,使用这个 responder 处理
810
810
  on :fallback, respond: 'fallback message'
811
811
  end
812
812
  ```
813
813
 
814
- 在 controller 中使用 `wechat_responder` 引入 Responder DSL, 之后可以用
814
+ 在 controller 中使用 `wechat_responder` 引入 Responder DSL,之后可以用
815
815
 
816
816
  ```
817
817
  on <message_type> do |message|
@@ -823,26 +823,26 @@ end
823
823
 
824
824
  目前支持的 message_type 有如下几种
825
825
 
826
- - :text 响应文字消息,可以用 `:with` 参数来匹配文本内容 `on(:text, with:'help'){|message, content| ...}`
826
+ - :text 响应文字消息,可以用 `:with` 参数来匹配文本内容 `on(:text, with:'help'){|message, content| ...}`
827
827
  - :image 响应图片消息
828
828
  - :voice 响应语音消息
829
829
  - :shortvideo 响应短视频消息
830
830
  - :video 响应视频消息
831
831
  - :label_location 响应地理位置消息
832
832
  - :link 响应链接消息
833
- - :event 响应事件消息, 可以用 `:with` 参数来匹配事件类型,同文字消息类似,支持正则表达式匹配
834
- - :click 虚拟响应事件消息, 微信传入:event,但 gem 内部会单独处理
835
- - :view 虚拟响应事件消息, 微信传入:event,但 gem 内部会单独处理
833
+ - :event 响应事件消息,可以用 `:with` 参数来匹配事件类型,同文字消息类似,支持正则表达式匹配
834
+ - :click 虚拟响应事件消息,微信传入:event,但 gem 内部会单独处理
835
+ - :view 虚拟响应事件消息,微信传入:event,但 gem 内部会单独处理
836
836
  - :scan 虚拟响应事件消息
837
837
  - :batch_job 虚拟响应事件消息
838
838
  - :location 虚拟响应上报地理位置事件消息
839
- - :fallback 默认响应,当收到的消息无法被其他responder响应时,会使用这个responder.
839
+ - :fallback 默认响应,当收到的消息无法被其他 responder 响应时,会使用这个 responder.
840
840
 
841
841
  ### 多客服消息转发
842
842
 
843
843
  ```ruby
844
844
  class WechatsController < ActionController::Base
845
- # 当无任何responder处理用户信息时,转发至客服处理。
845
+ # 当无任何 responder 处理用户信息时,转发至客服处理。
846
846
  on :fallback do |message|
847
847
  message.reply.transfer_customer_service
848
848
  end
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://app.travis-ci.com/Eric-Guo/wechat.svg?branch=main)](https://travis-ci.com/github/Eric-Guo/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
1
+ WeChat [![Gem Version](https://badge.fury.io/rb/wechat.svg)](https://rubygems.org/gems/wechat) [![Build Status](https://mixtint.semaphoreci.com/badges/wechat/branches/main.svg?style=shields)](https://mixtint.semaphoreci.com/projects/wechat) [![Maintainability](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/maintainability)](https://codeclimate.com/github/Eric-Guo/wechat/maintainability) [![Test Coverage](https://api.codeclimate.com/v1/badges/12885358487c13e91e00/test_coverage)](https://codeclimate.com/github/Eric-Guo/wechat/test_coverage)
2
2
  ======
3
3
 
4
4
  [![Join the chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Eric-Guo/wechat?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -35,7 +35,7 @@ Use `gem install`
35
35
  ```
36
36
  gem install "wechat"
37
37
  # If your ruby version < 2.6
38
- # gem install wechat -v 0.12.2
38
+ # gem install wechat -v 0.12.4
39
39
  ```
40
40
 
41
41
  Or add it to your app's `Gemfile`:
@@ -43,7 +43,7 @@ Or add it to your app's `Gemfile`:
43
43
  ```
44
44
  gem 'wechat'
45
45
  # If your rails version < 6.0
46
- # gem 'wechat', '~> 0.12.2'
46
+ # gem 'wechat', '~> 0.12.4'
47
47
  ```
48
48
 
49
49
  Run the following command to install it:
@@ -381,8 +381,8 @@ Feel safe if you can not read Chinese in the comments, it's kept there in order
381
381
  ```
382
382
  $ wechat
383
383
  Wechat Public Account commands:
384
- wechat addvoicetorecofortext [VOICE_ID] # AI开放接口-提交语音
385
- wechat callbackip # 获取微信服务器IP地址
384
+ wechat addvoicetorecofortext [VOICE_ID] # AI 开放接口 - 提交语音
385
+ wechat callbackip # 获取微信服务器 IP 地址
386
386
  wechat clear_quota # 接口调用次数清零
387
387
  wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
388
388
  wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
@@ -417,8 +417,8 @@ Wechat Public Account commands:
417
417
  wechat message_mass_preview [WX_NAME, MPNEWS_MEDIA_ID] # 预览图文消息素材
418
418
  wechat qrcode_create_limit_scene [SCENE_ID_OR_STR] # 请求永久二维码
419
419
  wechat qrcode_create_scene [SCENE_ID_OR_STR, EXPIRE_SECONDS] # 请求临时二维码
420
- wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
421
- wechat queryrecoresultfortext [VOICE_ID] # AI开放接口-获取语音识别结果
420
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过 ticket 下载二维码
421
+ wechat queryrecoresultfortext [VOICE_ID] # AI 开放接口 - 获取语音识别结果
422
422
  wechat shorturl [LONG_URL] # 长链接转短链接
423
423
  wechat tag [TAGID] # 获取标签下粉丝列表
424
424
  wechat tag_add_user [TAG_ID, OPEN_IDS] # 批量为用户打标签
@@ -428,7 +428,7 @@ Wechat Public Account commands:
428
428
  wechat tag_update [TAG_ID, TAGNAME] # 更新标签名字
429
429
  wechat tags # 获取所有标签
430
430
  wechat template_message [OPENID, TEMPLATE_YAML_PATH] # 模板消息接口
431
- wechat translatecontent [CONTENT] # AI开放接口-微信翻译
431
+ wechat translatecontent [CONTENT] # AI 开放接口 - 微信翻译
432
432
  wechat user [OPEN_ID] # 获取用户基本信息
433
433
  wechat user_batchget [OPEN_ID_LIST] # 批量获取用户基本信息
434
434
  wechat user_change_group [OPEN_ID, TO_GROUP_ID] # 移动用户分组
@@ -450,10 +450,10 @@ Wechat Enterprise Account commands:
450
450
  wechat batch_replaceparty [BATCH_PARTY_CSV_MEDIA_ID] # 全量覆盖部门
451
451
  wechat batch_replaceuser [BATCH_USER_CSV_MEDIA_ID] # 全量覆盖成员
452
452
  wechat batch_syncuser [SYNC_USER_CSV_MEDIA_ID] # 增量更新成员
453
- wechat callbackip # 获取微信服务器IP地址
453
+ wechat callbackip # 获取微信服务器 IP 地址
454
454
  wechat clear_quota # 接口调用次数清零
455
- wechat convert_to_openid [USER_ID] # userid转换成openid
456
- wechat convert_to_userid [OPENID] # openid转换成userid
455
+ wechat convert_to_openid [USER_ID] # userid 转换成 openid
456
+ wechat convert_to_userid [OPENID] # openid 转换成 userid
457
457
  wechat custom_image [OPENID, IMAGE_PATH] # 发送图片客服消息
458
458
  wechat custom_music [OPENID, THUMBNAIL_PATH, MUSIC_URL] # 发送音乐客服消息
459
459
  wechat custom_news [OPENID, NEWS_YAML_PATH] # 发送图文客服消息
@@ -483,7 +483,7 @@ Wechat Enterprise Account commands:
483
483
  wechat menu_delete # 删除菜单
484
484
  wechat menu_trymatch [USER_ID] # 测试个性化菜单匹配结果
485
485
  wechat message_send [OPENID, TEXT_MESSAGE] # 发送文字消息
486
- wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过ticket下载二维码
486
+ wechat qrcode_download [TICKET, QR_CODE_PIC_PATH] # 通过 ticket 下载二维码
487
487
  wechat tag [TAG_ID] # 获取标签成员
488
488
  wechat tag_add_department [TAG_ID, PARTY_IDS] # 增加标签部门
489
489
  wechat tag_add_user [TAG_ID, USER_IDS] # 增加标签成员
@@ -592,7 +592,7 @@ Sending custom_news should also be defined as a yaml file, like `articles.yml`
592
592
  articles:
593
593
  -
594
594
  title: "习近平在布鲁日欧洲学院演讲"
595
- description: "新华网比利时布鲁日4月1日电 国家主席习近平1日在比利时布鲁日欧洲学院发表重要演讲"
595
+ description: "新华网比利时布鲁日 4 1 日电 国家主席习近平 1 日在比利时布鲁日欧洲学院发表重要演讲"
596
596
  url: "http://news.sina.com.cn/c/2014-04-01/232629843387.shtml"
597
597
  pic_url: "http://i3.sinaimg.cn/dy/c/2014-04-01/1396366518_bYays1.jpg"
598
598
  ```
data/bin/wechat CHANGED
@@ -50,8 +50,8 @@ class App < Thor
50
50
  end
51
51
 
52
52
  desc 'department_update [DEPARTMENT_ID, NAME]', '更新部门'
53
- method_option :parentid, aliases: '-p', desc: '父亲部门id。根部门id为1', default: nil
54
- method_option :order, aliases: '-o', desc: '在父部门中的次序值。order值小的排序靠前。', default: nil
53
+ method_option :parentid, aliases: '-p', desc: '父亲部门id。根部门id为1', default: nil, check_default_type: false
54
+ method_option :order, aliases: '-o', desc: '在父部门中的次序值。order值小的排序靠前。', default: nil, check_default_type: false
55
55
  def department_update(departmentid, name)
56
56
  api_opts = options.slice(:parentid, :order)
57
57
  puts wechat_api.department_update(departmentid, name, api_opts[:parentid], api_opts[:order])
@@ -97,8 +97,8 @@ class App < Thor
97
97
  end
98
98
 
99
99
  desc 'user_simplelist [DEPARTMENT_ID]', '获取部门成员'
100
- method_option :fetch_child, aliases: '-c', desc: '是否递归获取子部门下面的成员', default: 1
101
- method_option :status, aliases: '-s', desc: '0 获取全部成员,1 获取已关注成员列表,2 获取禁用成员列表,4 获取未关注成员列表。status可叠加', default: 0
100
+ method_option :fetch_child, aliases: '-c', desc: '是否递归获取子部门下面的成员', default: 1, check_default_type: false
101
+ method_option :status, aliases: '-s', desc: '0 获取全部成员,1 获取已关注成员列表,2 获取禁用成员列表,4 获取未关注成员列表。status可叠加', default: 0, check_default_type: false
102
102
  def user_simplelist(departmentid = 1)
103
103
  api_opts = options.slice(:fetch_child, :status)
104
104
 
@@ -111,8 +111,8 @@ class App < Thor
111
111
  end
112
112
 
113
113
  desc 'user_list [DEPARTMENT_ID]', '获取部门成员详情'
114
- method_option :fetch_child, aliases: '-c', desc: '是否递归获取子部门下面的成员', default: 0
115
- method_option :status, aliases: '-s', desc: '0 获取全部成员,1 获取已关注成员列表,2 获取禁用成员列表,4 获取未关注成员列表。status可叠加', default: 0
114
+ method_option :fetch_child, aliases: '-c', desc: '是否递归获取子部门下面的成员', default: 0, check_default_type: false
115
+ method_option :status, aliases: '-s', desc: '0 获取全部成员,1 获取已关注成员列表,2 获取禁用成员列表,4 获取未关注成员列表。status可叠加', default: 0, check_default_type: false
116
116
  def user_list(departmentid = 1)
117
117
  api_opts = options.slice(:fetch_child, :status)
118
118
 
@@ -232,10 +232,45 @@ class App < Thor
232
232
 
233
233
  desc 'media_uploadnews [MPNEWS_YAML_PATH]', '上传图文消息素材'
234
234
  def media_uploadnews(mpnews_yaml_path)
235
- mpnew = YAML.load(File.read(mpnews_yaml_path))
235
+ mpnew = Wechat::ApiLoader.load_yaml(File.read(mpnews_yaml_path))
236
236
  puts wechat_api.media_uploadnews(Wechat::Message.new(MsgType: 'uploadnews').mpnews(mpnew[:articles]))
237
237
  end
238
238
 
239
+ desc 'draft_add [ARTICLE_YAML_PATH]', '新建草稿'
240
+ def draft_add(article_yaml_path)
241
+ yml_hash = Wechat::ApiLoader.load_yaml(File.read(article_yaml_path))
242
+ puts wechat_api.draft_add(Wechat::Message.new(MsgType: 'draft_news').draft_news(yml_hash[:articles]))
243
+ end
244
+
245
+ desc 'draft_get [MEDIA_ID]', '获取草稿'
246
+ def draft_get(media_id)
247
+ puts wechat_api.draft_get(media_id)
248
+ end
249
+
250
+ desc 'draft_delete [MEDIA_ID]', '删除草稿'
251
+ def draft_delete(media_id)
252
+ puts wechat_api.draft_delete(media_id)
253
+ end
254
+
255
+ desc 'draft_count', '获取草稿总数'
256
+ def draft_count
257
+ puts wechat_api.draft_count
258
+ end
259
+
260
+ desc 'draft_batchget [OFFSET, COUNT]', '获取草稿列表'
261
+ method_option :no_content, aliases: '-no_content', desc: '不要返回 content 字段', default: true, check_default_type: false
262
+ def draft_batchget(offset, count)
263
+ api_opts = options.slice(:no_content)
264
+ wechat_api.draft_batchget(offset, count, no_content: api_opts[:no_content])
265
+ end
266
+
267
+ desc 'draft_switch', '检查草稿箱和发布功能开关状态'
268
+ method_option :enable_draft, aliases: '-enable_draft', desc: '立刻开启草稿箱和发布功能', default: false, check_default_type: false
269
+ def draft_switch
270
+ api_opts = options.slice(:enable_draft)
271
+ puts wechat_api.draft_switch(checkonly: api_opts[:enable_draft] || false)
272
+ end
273
+
239
274
  desc 'message_mass_delete [MSG_ID]', '删除群发消息'
240
275
  def message_mass_delete(msg_id)
241
276
  puts wechat_api.message_mass_delete(msg_id)
@@ -349,13 +384,13 @@ class App < Thor
349
384
 
350
385
  desc 'menu_create [MENU_YAML_PATH]', '创建菜单'
351
386
  def menu_create(menu_yaml_path)
352
- menu = YAML.load(File.read(menu_yaml_path))
387
+ menu = Wechat::ApiLoader.load_yaml(File.read(menu_yaml_path))
353
388
  puts 'Menu created' if wechat_api.menu_create(menu)
354
389
  end
355
390
 
356
391
  desc 'menu_addconditional [CONDITIONAL_MENU_YAML_PATH]', '创建个性化菜单'
357
392
  def menu_addconditional(conditional_menu_yaml_path)
358
- conditional_menu = YAML.load(File.read(conditional_menu_yaml_path))
393
+ conditional_menu = Wechat::ApiLoader.load_yaml(File.read(conditional_menu_yaml_path))
359
394
  add_result = wechat_api.menu_addconditional(conditional_menu)
360
395
  puts "Conditional menu created: #{add_result}" if add_result
361
396
  end
@@ -408,7 +443,7 @@ class App < Thor
408
443
 
409
444
  desc 'material_add_news [MPNEWS_YAML_PATH]', '永久图文素材上传'
410
445
  def material_add_news(mpnews_yaml_path)
411
- new = YAML.load(File.read(mpnews_yaml_path))
446
+ new = Wechat::ApiLoader.load_yaml(File.read(mpnews_yaml_path))
412
447
  puts wechat_api.material_add_news(Wechat::Message.new(MsgType: 'mpnews').mpnews(new['articles']))
413
448
  end
414
449
 
@@ -479,13 +514,13 @@ class App < Thor
479
514
 
480
515
  desc 'custom_news [OPENID, NEWS_YAML_PATH]', '发送图文客服消息'
481
516
  def custom_news(openid, news_yaml_path)
482
- articles = YAML.load(File.read(news_yaml_path))
517
+ articles = Wechat::ApiLoader.load_yaml(File.read(news_yaml_path))
483
518
  puts wechat_api.custom_message_send Wechat::Message.to(openid).news(articles['articles'])
484
519
  end
485
520
 
486
521
  desc 'template_message [OPENID, TEMPLATE_YAML_PATH]', '模板消息接口'
487
522
  def template_message(openid, template_yaml_path)
488
- template = YAML.load(File.read(template_yaml_path))
523
+ template = Wechat::ApiLoader.load_yaml(File.read(template_yaml_path))
489
524
  puts wechat_api.template_message_send Wechat::Message.to(openid).template(template['template'])
490
525
  end
491
526
 
@@ -556,6 +591,10 @@ class App < Thor
556
591
  def clear_quota
557
592
  puts wechat_api.clear_quota
558
593
  end
594
+
595
+ def self.exit_on_failure?
596
+ true
597
+ end
559
598
  end
560
599
 
561
600
  App.start
@@ -38,29 +38,36 @@ module ActionController
38
38
  self.trusted_domain_fullname = opts[:trusted_domain_fullname] || cfg.trusted_domain_fullname
39
39
  self.oauth2_cookie_duration = opts[:oauth2_cookie_duration] || cfg.oauth2_cookie_duration.to_i.seconds
40
40
  self.timeout = opts[:timeout] || cfg.timeout
41
- self.qcloud_token_lifespan = opts[:qcloud_token_lifespan] || cfg.qcloud_token_lifespan
42
41
  self.skip_verify_ssl = opts.key?(:skip_verify_ssl) ? opts[:skip_verify_ssl] : cfg.skip_verify_ssl
43
42
 
43
+ proxy_url = opts.key?(:proxy_url) ? opts[:proxy_url] : cfg.proxy_url
44
+ proxy_port = opts.key?(:proxy_port) ? opts[:proxy_port] : cfg.proxy_port
45
+ proxy_username = opts.key?(:proxy_username) ? opts[:proxy_username] : cfg.proxy_username
46
+ proxy_password = opts.key?(:proxy_password) ? opts[:proxy_password] : cfg.proxy_password
47
+
44
48
  return Wechat.api if account == :default && opts.empty?
45
49
 
46
50
  access_token = opts[:access_token] || cfg.access_token
47
51
  jsapi_ticket = opts[:jsapi_ticket] || cfg.jsapi_ticket
48
52
  qcloud_env = opts[:qcloud_env] || cfg.qcloud_env
49
53
  qcloud_token = opts[:qcloud_token] || cfg.qcloud_token
54
+ qcloud_token_lifespan = opts[:qcloud_token_lifespan] || cfg.qcloud_token_lifespan
50
55
 
51
56
  api_type = opts[:type] || cfg.type
52
57
  secret = corpid.present? ? opts[:corpsecret] || cfg.corpsecret : opts[:secret] || cfg.secret
53
58
 
54
- get_wechat_api(api_type, corpid, appid, secret, access_token, agentid, timeout, skip_verify_ssl, jsapi_ticket, qcloud_env, qcloud_token, qcloud_token_lifespan)
59
+ network_setting = Wechat::NetworkSetting.new(timeout, skip_verify_ssl, proxy_url, proxy_port, proxy_username, proxy_password)
60
+ qcloud_setting = Wechat::Qcloud::Setting.new(qcloud_env, qcloud_token, qcloud_token_lifespan)
61
+ get_wechat_api(api_type, corpid, appid, secret, access_token, agentid, network_setting, jsapi_ticket, qcloud_setting)
55
62
  end
56
63
 
57
- def get_wechat_api(api_type, corpid, appid, secret, access_token, agentid, timeout, skip_verify_ssl, jsapi_ticket, qcloud_env, qcloud_token, qcloud_token_lifespan)
64
+ def get_wechat_api(api_type, corpid, appid, secret, access_token, agentid, network_setting, jsapi_ticket, qcloud_setting)
58
65
  if api_type && api_type.to_sym == :mp
59
- Wechat::MpApi.new(appid, secret, access_token, timeout, skip_verify_ssl, jsapi_ticket, qcloud_env, qcloud_token, qcloud_token_lifespan)
66
+ Wechat::MpApi.new(appid, secret, access_token, network_setting, jsapi_ticket, qcloud_setting)
60
67
  elsif corpid.present?
61
- Wechat::CorpApi.new(corpid, secret, access_token, agentid, timeout, skip_verify_ssl, jsapi_ticket)
68
+ Wechat::CorpApi.new(corpid, secret, access_token, agentid, network_setting, jsapi_ticket)
62
69
  else
63
- Wechat::Api.new(appid, secret, access_token, timeout, skip_verify_ssl, jsapi_ticket)
70
+ Wechat::Api.new(appid, secret, access_token, network_setting, jsapi_ticket)
64
71
  end
65
72
  end
66
73
  end
data/lib/wechat/api.rb CHANGED
@@ -2,15 +2,16 @@
2
2
 
3
3
  module Wechat
4
4
  class Api < ApiBase
5
- def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file)
5
+ def initialize(appid, secret, token_file, network_setting, jsapi_ticket_file)
6
6
  super()
7
- @client = HttpClient.new(Wechat::Api::API_BASE, timeout, skip_verify_ssl)
7
+ @client = HttpClient.new(Wechat::Api::API_BASE, network_setting)
8
8
  @access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
9
9
  @jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
10
10
  @qcloud = nil
11
11
  end
12
12
 
13
13
  include Concern::Common
14
+ include Concern::Draft
14
15
 
15
16
  def template_message_send(message)
16
17
  post 'message/template/send', message.to_json, content_type: :json
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_support/core_ext/object/blank'
4
+
3
5
  module Wechat
4
6
  module ApiLoader
5
7
  def self.with(options)
@@ -10,17 +12,19 @@ module Wechat
10
12
  js_token_file = options[:js_token_file] || c.jsapi_ticket.presence || '/var/tmp/wechat_jsapi_ticket'
11
13
  type = options[:type] || c.type
12
14
 
15
+ network_setting = Wechat::NetworkSetting.new(c.timeout, c.skip_verify_ssl, c.proxy_url, c.proxy_port, c.proxy_username, c.proxy_password)
13
16
  if c.appid && c.secret && token_file.present?
14
17
  if type == 'mp'
15
18
  qcloud_env = options[:qcloud_env] || c.qcloud_env
16
19
  qcloud_token_file = options[:qcloud_token_file] || c.qcloud_token_file.presence || '/var/tmp/qcloud_access_token'
17
20
  qcloud_token_lifespan = options[:qcloud_token_lifespan] || c.qcloud_token_lifespan
18
- Wechat::MpApi.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file, qcloud_env, qcloud_token_file, qcloud_token_lifespan)
21
+ qcloud_setting = Wechat::Qcloud::Setting.new(qcloud_env, qcloud_token_file, qcloud_token_lifespan)
22
+ Wechat::MpApi.new(c.appid, c.secret, token_file, network_setting, js_token_file, qcloud_setting)
19
23
  else
20
- Wechat::Api.new(c.appid, c.secret, token_file, c.timeout, c.skip_verify_ssl, js_token_file)
24
+ Wechat::Api.new(c.appid, c.secret, token_file, network_setting, js_token_file)
21
25
  end
22
26
  elsif c.corpid && c.corpsecret && token_file.present?
23
- Wechat::CorpApi.new(c.corpid, c.corpsecret, token_file, c.agentid, c.timeout, c.skip_verify_ssl, js_token_file)
27
+ Wechat::CorpApi.new(c.corpid, c.corpsecret, token_file, c.agentid, network_setting, js_token_file)
24
28
  else
25
29
  raise 'Need create ~/.wechat.yml with wechat appid and secret or running at rails root folder so wechat can read config/wechat.yml'
26
30
  end
@@ -38,15 +42,27 @@ module Wechat
38
42
  @configs = loading_config!
39
43
  end
40
44
 
45
+ def self.load_yaml(result)
46
+ YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(result) : YAML.safe_load(result)
47
+ end
48
+
41
49
  private_class_method def self.loading_config!
42
50
  configs = config_from_file || config_from_environment
43
51
  configs.merge!(config_from_db)
44
52
 
45
- configs.symbolize_keys!
53
+ configs.transform_keys! do |key|
54
+ key.to_sym
55
+ rescue StandardError
56
+ key
57
+ end
46
58
  configs.each do |key, cfg|
47
59
  raise "wrong wechat configuration format for #{key}" unless cfg.is_a?(Hash)
48
60
 
49
- cfg.symbolize_keys!
61
+ cfg.transform_keys! do |sub_key|
62
+ sub_key.to_sym
63
+ rescue StandardError
64
+ sub_key
65
+ end
50
66
  end
51
67
 
52
68
  if defined?(::Rails)
@@ -60,7 +76,7 @@ module Wechat
60
76
  configs.each do |_, cfg|
61
77
  cfg[:timeout] ||= 20
62
78
  cfg[:qcloud_token_lifespan] ||= 7200
63
- cfg[:have_session_class] = class_exists?('WechatSession')
79
+ cfg[:have_session_class] ||= class_exists?('WechatSession')
64
80
  cfg[:oauth2_cookie_duration] ||= 3600 # 1 hour
65
81
  end
66
82
 
@@ -75,27 +91,28 @@ module Wechat
75
91
  private_class_method def self.config_from_db
76
92
  return {} unless class_exists?('WechatConfig')
77
93
 
78
- environment = defined?(::Rails) ? Rails.env.to_s : ENV['RAILS_ENV'] || 'development'
94
+ environment = defined?(::Rails) ? Rails.env.to_s : ENV.fetch('RAILS_ENV', 'development')
79
95
  WechatConfig.get_all_configs(environment)
80
96
  end
81
97
 
82
98
  private_class_method def self.config_from_file
83
99
  if defined?(::Rails)
84
- config_file = ENV['WECHAT_CONF_FILE'] || Rails.root.join('config', 'wechat.yml')
100
+ config_file = ENV.fetch('WECHAT_CONF_FILE') { Rails.root.join('config', 'wechat.yml') }
85
101
  resolve_config_file(config_file, Rails.env.to_s)
86
102
  else
87
- rails_config_file = ENV['WECHAT_CONF_FILE'] || File.join(Dir.getwd, 'config', 'wechat.yml')
103
+ require 'erb'
104
+ rails_config_file = ENV.fetch('WECHAT_CONF_FILE') { File.join(Dir.getwd, 'config', 'wechat.yml') }
88
105
  application_config_file = File.join(Dir.getwd, 'config', 'application.yml')
89
106
  home_config_file = File.join(Dir.home, '.wechat.yml')
90
107
  if File.exist?(rails_config_file)
91
- rails_env = ENV['RAILS_ENV'] || 'development'
108
+ rails_env = ENV.fetch('RAILS_ENV', 'development')
92
109
  if File.exist?(application_config_file) && !defined?(::Figaro)
93
110
  require 'figaro'
94
111
  Figaro::Application.new(path: application_config_file, environment: rails_env).load
95
112
  end
96
113
  config = resolve_config_file(rails_config_file, rails_env)
97
114
  if config.present? && (default = config[:default]) && (default['appid'] || default['corpid'])
98
- puts "Using rails project #{ENV['WECHAT_CONF_FILE'] || 'config/wechat.yml'} #{rails_env} setting..."
115
+ puts "Using rails project #{ENV.fetch('WECHAT_CONF_FILE', 'config/wechat.yml')} #{rails_env} setting..."
99
116
  return config
100
117
  end
101
118
  end
@@ -107,9 +124,7 @@ module Wechat
107
124
  return unless File.exist?(config_file)
108
125
 
109
126
  begin
110
- # rubocop:disable Security/YAMLLoad
111
- raw_data = YAML.load(ERB.new(File.read(config_file)).result)
112
- # rubocop:enable Security/YAMLLoad
127
+ raw_data = load_yaml(ERB.new(File.read(config_file)).result)
113
128
  rescue NameError
114
129
  puts "WARNING: If using 'Rails.application.credentials.wechat_secret!' in wechat.yml, you need run in 'rails c' and access via 'Wechat.api' or gem 'figaro' instead."
115
130
  end
@@ -132,22 +147,26 @@ module Wechat
132
147
  end
133
148
 
134
149
  private_class_method def self.config_from_environment
135
- value = { appid: ENV['WECHAT_APPID'],
136
- secret: ENV['WECHAT_SECRET'],
137
- corpid: ENV['WECHAT_CORPID'],
138
- corpsecret: ENV['WECHAT_CORPSECRET'],
139
- agentid: ENV['WECHAT_AGENTID'],
140
- token: ENV['WECHAT_TOKEN'],
141
- access_token: ENV['WECHAT_ACCESS_TOKEN'],
142
- encrypt_mode: ENV['WECHAT_ENCRYPT_MODE'],
143
- timeout: ENV['WECHAT_TIMEOUT'],
144
- skip_verify_ssl: ENV['WECHAT_SKIP_VERIFY_SSL'],
145
- encoding_aes_key: ENV['WECHAT_ENCODING_AES_KEY'],
146
- jsapi_ticket: ENV['WECHAT_JSAPI_TICKET'],
147
- qcloud_env: ENV['WECHAT_QCLOUD_ENV'],
148
- qcloud_token_file: ENV['WECHAT_QCLOUD_TOKEN'],
149
- qcloud_token_lifespan: ENV['WECHAT_QCLOUD_TOKEN_LIFESPAN'],
150
- trusted_domain_fullname: ENV['WECHAT_TRUSTED_DOMAIN_FULLNAME'] }
150
+ value = { appid: ENV.fetch('WECHAT_APPID', nil),
151
+ secret: ENV.fetch('WECHAT_SECRET', nil),
152
+ corpid: ENV.fetch('WECHAT_CORPID', nil),
153
+ corpsecret: ENV.fetch('WECHAT_CORPSECRET', nil),
154
+ agentid: ENV.fetch('WECHAT_AGENTID', nil),
155
+ token: ENV.fetch('WECHAT_TOKEN', nil),
156
+ access_token: ENV.fetch('WECHAT_ACCESS_TOKEN', nil),
157
+ encrypt_mode: ENV.fetch('WECHAT_ENCRYPT_MODE', nil),
158
+ timeout: ENV.fetch('WECHAT_TIMEOUT', nil),
159
+ skip_verify_ssl: ENV.fetch('WECHAT_SKIP_VERIFY_SSL', nil),
160
+ proxy_url: ENV.fetch('WECHAT_PROXY_URL', nil),
161
+ proxy_port: ENV.fetch('WECHAT_PROXY_PORT', nil),
162
+ proxy_username: ENV.fetch('WECHAT_PROXY_USERNAME', nil),
163
+ proxy_password: ENV.fetch('WECHAT_PROXY_PASSWORD', nil),
164
+ encoding_aes_key: ENV.fetch('WECHAT_ENCODING_AES_KEY', nil),
165
+ jsapi_ticket: ENV.fetch('WECHAT_JSAPI_TICKET', nil),
166
+ qcloud_env: ENV.fetch('WECHAT_QCLOUD_ENV', nil),
167
+ qcloud_token_file: ENV.fetch('WECHAT_QCLOUD_TOKEN', nil),
168
+ qcloud_token_lifespan: ENV.fetch('WECHAT_QCLOUD_TOKEN_LIFESPAN', nil),
169
+ trusted_domain_fullname: ENV.fetch('WECHAT_TRUSTED_DOMAIN_FULLNAME', nil) }
151
170
  { default: value }
152
171
  end
153
172
 
@@ -113,7 +113,7 @@ module Wechat
113
113
  end
114
114
 
115
115
  def menu_create(menu)
116
- # 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
116
+ # 微信不接受 7bit escaped json(eg \uxxxx),中文必须 UTF-8 编码,这可能是个安全漏洞
117
117
  post 'menu/create', JSON.generate(menu)
118
118
  end
119
119
 
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wechat
4
+ module Concern
5
+ module Draft
6
+ def draft_add(mpnews_articles)
7
+ draft_add_params_hash = { articles: mpnews_articles }
8
+ post 'draft/add', JSON.generate(draft_add_params_hash)
9
+ end
10
+
11
+ def draft_get(media_id)
12
+ post 'draft/get', JSON.generate(media_id: media_id)
13
+ end
14
+
15
+ def draft_delete(media_id)
16
+ post 'draft/delete', JSON.generate(media_id: media_id)
17
+ end
18
+
19
+ def draft_update(media_id, mpnews_articles, index: 0)
20
+ draft_update_params_hash = { media_id: media_id,
21
+ index: index,
22
+ articles: mpnews_articles }
23
+ post 'draft/update', JSON.generate(draft_update_params_hash)
24
+ end
25
+
26
+ def draft_count
27
+ get 'draft/count'
28
+ end
29
+
30
+ def draft_batchget(offset, count, no_content: false)
31
+ draft_batchget_params_hash = { offset: offset,
32
+ count: count,
33
+ no_content: (no_content ? 1 : 0) }
34
+ post 'draft/batchget', JSON.generate(draft_batchget_params_hash)
35
+ end
36
+
37
+ def draft_switch(checkonly: true)
38
+ post 'draft/switch', nil, params: { checkonly: (checkonly ? 1 : 0) }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -5,7 +5,7 @@ module Wechat
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  module ClassMethods
8
- attr_accessor :wechat_api_client, :wechat_cfg_account, :token, :appid, :corpid, :agentid, :encrypt_mode, :timeout, :qcloud_token_lifespan,
8
+ attr_accessor :wechat_api_client, :wechat_cfg_account, :token, :appid, :corpid, :agentid, :encrypt_mode, :timeout,
9
9
  :skip_verify_ssl, :encoding_aes_key, :trusted_domain_fullname, :oauth2_cookie_duration
10
10
  end
11
11
 
@@ -4,9 +4,9 @@ module Wechat
4
4
  class CorpApi < ApiBase
5
5
  attr_reader :agentid
6
6
 
7
- def initialize(appid, secret, token_file, agentid, timeout, skip_verify_ssl, jsapi_ticket_file)
7
+ def initialize(appid, secret, token_file, agentid, network_setting, jsapi_ticket_file)
8
8
  super()
9
- @client = HttpClient.new(QYAPI_BASE, timeout, skip_verify_ssl)
9
+ @client = HttpClient.new(QYAPI_BASE, network_setting)
10
10
  @access_token = Token::CorpAccessToken.new(@client, appid, secret, token_file)
11
11
  @agentid = agentid
12
12
  @jsapi_ticket = Ticket::CorpJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
@@ -143,7 +143,7 @@ module Wechat
143
143
  end
144
144
 
145
145
  def menu_create(menu)
146
- # 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
146
+ # 微信不接受 7bit escaped json(eg \uxxxx),中文必须 UTF-8 编码,这可能是个安全漏洞
147
147
  post 'menu/create', JSON.generate(menu), params: { agentid: agentid }
148
148
  end
149
149
 
@@ -6,16 +6,21 @@ module Wechat
6
6
  class HttpClient
7
7
  attr_reader :base, :ssl_context, :httprb
8
8
 
9
- def initialize(base, timeout, skip_verify_ssl)
9
+ def initialize(base, network_setting)
10
10
  @base = base
11
11
  @httprb = if HTTP::VERSION.to_i >= 4
12
- HTTP.timeout(write: timeout, connect: timeout, read: timeout)
12
+ HTTP.timeout(write: network_setting.timeout, connect: network_setting.timeout, read: network_setting.timeout)
13
13
  else
14
- HTTP.timeout(:global, write: timeout, connect: timeout, read: timeout)
14
+ HTTP.timeout(:global, write: network_setting.timeout, connect: network_setting.timeout, read: network_setting.timeout)
15
15
  end
16
+
17
+ unless network_setting.proxy_url.nil?
18
+ @httprb = @httprb.via(network_setting.proxy_url, network_setting.proxy_port.to_i, network_setting.proxy_username, network_setting.proxy_password)
19
+ end
20
+
16
21
  @ssl_context = OpenSSL::SSL::SSLContext.new
17
22
  @ssl_context.ssl_version = 'TLSv1_2'
18
- @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE if skip_verify_ssl
23
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE if network_setting.skip_verify_ssl
19
24
  end
20
25
 
21
26
  def get(path, get_header = {})
@@ -33,7 +33,6 @@ module Wechat
33
33
  class ArticleBuilder
34
34
  attr_reader :items
35
35
 
36
- delegate :count, to: :items
37
36
  def initialize
38
37
  @items = []
39
38
  end
@@ -203,6 +202,10 @@ module Wechat
203
202
  update(MsgType: 'template', Template: template_fields)
204
203
  end
205
204
 
205
+ def draft_news(collection)
206
+ update(MsgType: 'draft_news', Articles: collection)
207
+ end
208
+
206
209
  def to_xml
207
210
  ws = message_hash.delete(:WechatSession)
208
211
  xml = message_hash.to_xml(root: 'xml', children: 'item', skip_instruct: true, skip_types: true)
@@ -244,6 +247,8 @@ module Wechat
244
247
  json_hash['news'] = { 'articles' => json_hash.delete('articles') }
245
248
  when 'mpnews'
246
249
  json_hash = { 'articles' => json_hash['articles'] }
250
+ when 'draft_news'
251
+ json_hash = json_hash['articles']
247
252
  when 'ref_mpnews'
248
253
  json_hash['msgtype'] = 'mpnews'
249
254
  json_hash.delete('articles')
data/lib/wechat/mp_api.rb CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  module Wechat
4
4
  class MpApi < ApiBase
5
- def initialize(appid, secret, token_file, timeout, skip_verify_ssl, jsapi_ticket_file, qcloud_env, qcloud_token_file, qcloud_token_lifespan)
5
+ def initialize(appid, secret, token_file, network_setting, jsapi_ticket_file, qcloud_setting)
6
6
  super()
7
- @client = HttpClient.new(Wechat::Api::API_BASE, timeout, skip_verify_ssl)
7
+ @client = HttpClient.new(Wechat::Api::API_BASE, network_setting)
8
8
  @access_token = Token::PublicAccessToken.new(@client, appid, secret, token_file)
9
9
  @jsapi_ticket = Ticket::PublicJsapiTicket.new(@client, @access_token, jsapi_ticket_file)
10
- @qcloud = Qcloud::Token.new(@client, @access_token, qcloud_env, qcloud_token_file, qcloud_token_lifespan)
10
+ @qcloud = Qcloud::Token.new(@client, @access_token, qcloud_setting)
11
11
  end
12
12
 
13
13
  include Concern::Common
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wechat
4
+ class NetworkSetting
5
+ attr_reader :timeout, :skip_verify_ssl, :proxy_url, :proxy_port, :proxy_username, :proxy_password
6
+
7
+ def initialize(timeout, skip_verify_ssl, proxy_url, proxy_port, proxy_username, proxy_password)
8
+ @timeout = timeout
9
+ @skip_verify_ssl = skip_verify_ssl
10
+ @proxy_url = proxy_url
11
+ @proxy_port = proxy_port
12
+ @proxy_username = proxy_username
13
+ @proxy_password = proxy_password
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Wechat
4
+ module Qcloud
5
+ class Setting
6
+ attr_reader :qcloud_env, :qcloud_token, :qcloud_token_lifespan
7
+
8
+ def initialize(qcloud_env, qcloud_token, qcloud_token_lifespan)
9
+ @qcloud_env = qcloud_env
10
+ @qcloud_token = qcloud_token
11
+ @qcloud_token_lifespan = qcloud_token_lifespan
12
+ end
13
+ end
14
+ end
15
+ end
@@ -10,12 +10,12 @@ module Wechat
10
10
  class Token
11
11
  attr_reader :client, :access_token, :qcloud_env, :qcloud_token_file, :qcloud_token_lifespan, :qcloud_token, :qcloud_token_expired_time
12
12
 
13
- def initialize(client, access_token, qcloud_env, qcloud_token_file, lifespan)
13
+ def initialize(client, access_token, qcloud_setting)
14
14
  @client = client
15
15
  @access_token = access_token
16
- @qcloud_env = qcloud_env
17
- @qcloud_token_file = qcloud_token_file
18
- @qcloud_token_lifespan = lifespan
16
+ @qcloud_env = qcloud_setting.qcloud_env
17
+ @qcloud_token_file = qcloud_setting.qcloud_token
18
+ @qcloud_token_lifespan = qcloud_setting.qcloud_token_lifespan
19
19
  @random_generator = Random.new
20
20
  end
21
21
 
@@ -187,7 +187,7 @@ module Wechat
187
187
  end
188
188
 
189
189
  def create
190
- request_msg = Wechat::Message.from_hash(post_xml)
190
+ request_msg = Wechat::Message.from_hash(post_body)
191
191
  response_msg = run_responder(request_msg)
192
192
 
193
193
  if response_msg.respond_to? :to_xml
@@ -229,6 +229,24 @@ module Wechat
229
229
  msg_encrypt)
230
230
  end
231
231
 
232
+ def post_body
233
+ if request.media_type == 'application/json'
234
+ data_hash = params
235
+
236
+ if @we_encrypt_mode && data['Encrypt'].present?
237
+ content, @we_app_id = unpack(decrypt(Base64.decode64(data['Encrypt']), @we_encoding_aes_key))
238
+ data_hash = content
239
+ end
240
+
241
+ data_hash = data_hash.to_unsafe_hash if data_hash.instance_of?(ActionController::Parameters)
242
+ HashWithIndifferentAccess.new(data_hash).tap do |msg|
243
+ msg[:Event]&.downcase!
244
+ end
245
+ else
246
+ post_xml
247
+ end
248
+ end
249
+
232
250
  def post_xml
233
251
  data = request_content
234
252
 
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wechat
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Skinnyworm
@@ -35,7 +35,7 @@ cert_chain:
35
35
  ZM9IDtdMg8E/4ujwilV8HKmgU77vVN6vSMvxx8zQFSz9a6GbdpB4egPZ++peSk/Q
36
36
  uaIJtOX6M4VC6u7eZfotARKyUy6EcoN2zNqEAQ==
37
37
  -----END CERTIFICATE-----
38
- date: 2021-09-15 00:00:00.000000000 Z
38
+ date: 2022-06-06 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: activesupport
@@ -44,9 +44,6 @@ dependencies:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
46
  version: '6.0'
47
- - - "<"
48
- - !ruby/object:Gem::Version
49
- version: '7'
50
47
  type: :runtime
51
48
  prerelease: false
52
49
  version_requirements: !ruby/object:Gem::Requirement
@@ -54,9 +51,6 @@ dependencies:
54
51
  - - ">="
55
52
  - !ruby/object:Gem::Version
56
53
  version: '6.0'
57
- - - "<"
58
- - !ruby/object:Gem::Version
59
- version: '7'
60
54
  - !ruby/object:Gem::Dependency
61
55
  name: http
62
56
  requirement: !ruby/object:Gem::Requirement
@@ -91,6 +85,20 @@ dependencies:
91
85
  - - ">="
92
86
  - !ruby/object:Gem::Version
93
87
  version: 1.6.0
88
+ - !ruby/object:Gem::Dependency
89
+ name: psych
90
+ requirement: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: 3.3.2
95
+ type: :runtime
96
+ prerelease: false
97
+ version_requirements: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 3.3.2
94
102
  - !ruby/object:Gem::Dependency
95
103
  name: thor
96
104
  requirement: !ruby/object:Gem::Requirement
@@ -153,28 +161,42 @@ dependencies:
153
161
  requirements:
154
162
  - - ">="
155
163
  - !ruby/object:Gem::Version
156
- version: '6.0'
164
+ version: 7.0.0
157
165
  type: :development
158
166
  prerelease: false
159
167
  version_requirements: !ruby/object:Gem::Requirement
160
168
  requirements:
161
169
  - - ">="
162
170
  - !ruby/object:Gem::Version
163
- version: '6.0'
171
+ version: 7.0.0
164
172
  - !ruby/object:Gem::Dependency
165
173
  name: rspec-rails
166
174
  requirement: !ruby/object:Gem::Requirement
167
175
  requirements:
168
176
  - - "~>"
169
177
  - !ruby/object:Gem::Version
170
- version: '5.0'
178
+ version: '5.1'
171
179
  type: :development
172
180
  prerelease: false
173
181
  version_requirements: !ruby/object:Gem::Requirement
174
182
  requirements:
175
183
  - - "~>"
176
184
  - !ruby/object:Gem::Version
177
- version: '5.0'
185
+ version: '5.1'
186
+ - !ruby/object:Gem::Dependency
187
+ name: rspec-mocks
188
+ requirement: !ruby/object:Gem::Requirement
189
+ requirements:
190
+ - - '='
191
+ - !ruby/object:Gem::Version
192
+ version: 3.10.2
193
+ type: :development
194
+ prerelease: false
195
+ version_requirements: !ruby/object:Gem::Requirement
196
+ requirements:
197
+ - - '='
198
+ - !ruby/object:Gem::Version
199
+ version: 3.10.2
178
200
  - !ruby/object:Gem::Dependency
179
201
  name: sqlite3
180
202
  requirement: !ruby/object:Gem::Requirement
@@ -223,6 +245,7 @@ files:
223
245
  - lib/wechat/api_loader.rb
224
246
  - lib/wechat/cipher.rb
225
247
  - lib/wechat/concern/common.rb
248
+ - lib/wechat/concern/draft.rb
226
249
  - lib/wechat/concern/qcloud.rb
227
250
  - lib/wechat/controller_api.rb
228
251
  - lib/wechat/corp_api.rb
@@ -230,6 +253,8 @@ files:
230
253
  - lib/wechat/http_client.rb
231
254
  - lib/wechat/message.rb
232
255
  - lib/wechat/mp_api.rb
256
+ - lib/wechat/network_setting.rb
257
+ - lib/wechat/qcloud/setting.rb
233
258
  - lib/wechat/qcloud/token.rb
234
259
  - lib/wechat/responder.rb
235
260
  - lib/wechat/signature.rb
@@ -242,8 +267,18 @@ files:
242
267
  homepage: https://github.com/Eric-Guo/wechat
243
268
  licenses:
244
269
  - MIT
245
- metadata: {}
246
- post_install_message:
270
+ metadata:
271
+ bug_tracker_uri: https://github.com/Eric-Guo/wechat/issues
272
+ changelog_uri: https://github.com/Eric-Guo/wechat/releases
273
+ documentation_uri: https://github.com/Eric-Guo/wechat/tree/v0.16.0#readme
274
+ source_code_uri: https://github.com/Eric-Guo/wechat/tree/v0.16.0
275
+ rubygems_mfa_required: 'true'
276
+ post_install_message: |-
277
+ *****WECHAT BREAK CHANGE*****
278
+ Including correct version of `psych` after upgrade wechat, if not sure, using v3.3.2.
279
+ Ruby 3.1’s incompatible changes to its YAML module (Psych 4), detail see:
280
+ https://www.ctrl.blog/entry/ruby-psych4.html
281
+ *****************************
247
282
  rdoc_options: []
248
283
  require_paths:
249
284
  - lib
@@ -256,9 +291,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
256
291
  requirements:
257
292
  - - ">="
258
293
  - !ruby/object:Gem::Version
259
- version: '0'
294
+ version: 1.8.11
260
295
  requirements: []
261
- rubygems_version: 3.2.27
296
+ rubygems_version: 3.3.15
262
297
  signing_key:
263
298
  specification_version: 4
264
299
  summary: DSL for wechat message handling and API
metadata.gz.sig CHANGED
Binary file