wechat-gate-updater 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '05826f541ea10d64326fdf81f707288381a50bbc07c9c5ff33aa9bccc91eea12'
4
+ data.tar.gz: e87102aa758d4c6f7e55dac92ca31f18a0231e7320613186c1e6f9be83eac747
5
+ SHA512:
6
+ metadata.gz: 6031f7e8729571efcfde0c7814c896c9193626d25bb3d8704588b2c7967f80bb0fd91fb1759bf48d2eb03f2e1db313d533113d77e57b75a713b7112dc2303823
7
+ data.tar.gz: 62a136dd45c5cdfcc4140c40c6f5d3d850f3d55c4689a28fab398544afd2e6007313df530089fb8d164c1c26e16f741ec919e7b26d58a1824c5744363c624a88
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /data/APP-*
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,49 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ wechat-gate (0.1.6)
5
+ activesupport (>= 5.0.1)
6
+ rest-client (>= 1.8)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ activesupport (5.1.4)
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ i18n (~> 0.7)
14
+ minitest (~> 5.1)
15
+ tzinfo (~> 1.1)
16
+ concurrent-ruby (1.0.5)
17
+ domain_name (0.5.20170404)
18
+ unf (>= 0.0.5, < 1.0.0)
19
+ http-cookie (1.0.3)
20
+ domain_name (~> 0.5)
21
+ i18n (0.9.1)
22
+ concurrent-ruby (~> 1.0)
23
+ mime-types (3.1)
24
+ mime-types-data (~> 3.2015)
25
+ mime-types-data (3.2016.0521)
26
+ minitest (5.10.3)
27
+ netrc (0.11.0)
28
+ rake (10.5.0)
29
+ rest-client (2.0.2)
30
+ http-cookie (>= 1.0.2, < 2.0)
31
+ mime-types (>= 1.16, < 4.0)
32
+ netrc (~> 0.8)
33
+ thread_safe (0.3.6)
34
+ tzinfo (1.2.4)
35
+ thread_safe (~> 0.1)
36
+ unf (0.1.4)
37
+ unf_ext
38
+ unf_ext (0.0.7.4)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ bundler (>= 1.10)
45
+ rake (>= 10.0)
46
+ wechat-gate!
47
+
48
+ BUNDLED WITH
49
+ 1.15.3
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Lei Lee
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,193 @@
1
+ # WechatGate
2
+
3
+ > 搬运 from [隔壁老李的微信开发Ruby Gem](https://github.com/eggmantv/wechat_gate),原著未支持 Application::API,这里添加
4
+
5
+ **微信公众平台开发库**
6
+
7
+ 支持的接口:
8
+
9
+ - access_token(后端API使用)
10
+ - 用户授权信息获取(OAuth2)
11
+ - JS-SDK
12
+ - 回复消息封装
13
+ - 菜单接口
14
+ - 素材接口
15
+
16
+ 功能特点:
17
+
18
+ - 自动管理access_token和JS-SDK的ticket刷新和过期
19
+ - 多微信公众号支持
20
+ - 多环境支持(development, production),方便本地测试
21
+ - Controler和helper方法(微信session管理等等)
22
+ - 接口简单,方便定制
23
+
24
+ 使用视频教程: [微信公众号开发](https://eggman.tv/c/s-wechat-development-using-ruby-on-rails)
25
+
26
+ ## Installation
27
+
28
+ Add this line to your application's Gemfile:
29
+
30
+ ```ruby
31
+ gem 'wechat-gate'
32
+ ```
33
+
34
+ And then execute:
35
+
36
+ $ bundle
37
+
38
+ Or install it yourself as:
39
+
40
+ $ gem install wechat-gate
41
+
42
+ ## 公众号
43
+
44
+ 在开工之前你需要在微信公众账号平台做以下配置:
45
+
46
+ 1. 开通你的公众号(服务号),并开通微信认证(300元认证服务费)
47
+ 2. 在公众号后台“公众号设置” - “功能设置”中设置你的JS接口安全域名,就是你的公众号调用的网站的域名
48
+ 3. 在“接口权限” - “网页授权获取用户基本信息”中设置你的授权回调页面域名,这个用于OAuth2的回调域名认证
49
+ 4. 在“基本配置”中查看并配置你的AppID和AppSecret
50
+
51
+ ## 配置
52
+
53
+ 在Rails项目config目录下建立文件wechat.yml,并配置你的公众号信息.
54
+
55
+ ```
56
+ # 区分不同的环境
57
+ eggman:
58
+ development:
59
+ host: http://wechat-test1.eggman.tv
60
+
61
+ wechat_id: xxxxxxxxxx
62
+ app_id: xxxxxxxxxx
63
+ app_secret: xxxxxxxxxx
64
+
65
+ oauth2_redirect_uri: "http://wechat-test1.eggman.tv/wechat/users/callback"
66
+
67
+ push_url: "http://wechat-test1.eggman.tv/wechat/push"
68
+ push_token: xxxxxxxxxxxxxxxxxxxx
69
+ production:
70
+ host: https://eggman.tv
71
+
72
+ wechat_id: xxxxxxxxxx
73
+ app_id: xxxxxxxxxxxxxxxxxxxx
74
+ app_secret: xxxxxxxxxxxxxxxxxxxxxxxxxx
75
+
76
+ # 如果不需要多环境支持,也可以这样
77
+ app_name:
78
+ app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
79
+ app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
80
+ oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
81
+ ```
82
+
83
+ 然后在ApplicationController中指定当前要读取的公众号名称:
84
+
85
+ ```
86
+ self.wechat_gate_app_name = 'eggman'
87
+ ```
88
+
89
+ ## 后端调用
90
+
91
+ 后台API操作(比如微信用户信息获取等等操作)。
92
+
93
+ 默认情况下在controller中已经初始化了配置,方法为**wechat_gate_config**,直接使用就行。
94
+
95
+
96
+ ```ruby
97
+ wechat_gate_config.users # 获取用户列表
98
+ wechat_gate_config.user('ONE_OPEN_ID') # 获取一个用户的详细信息
99
+ wechat_gate_config.access_token # 获取当前access_token
100
+
101
+ # OAuth 2
102
+ wechat_gate_config.oauth2_entrance_url(scope: "snsapi_userinfo", state: "CURENT_STATE") # 获取当前OAuth2授权入口URL
103
+ wechat_gate_config.oauth2_access_token("TOKEN") # 根据OAuth2返回的TOKEN获取access_token
104
+ wechat_gate_config.oauth2_user("ACCESS_TOKEN", "ONE_OPEN_ID") # 获取一个用户的信息
105
+
106
+ wechat_gate_config.medias # 获取素材列表, 参数type: image | video | voice | news (图文)
107
+
108
+ wechat_gate_config.menu_get # 获取菜单
109
+ wechat_gate_config.menu_create(MENU_HASH) # 创建菜单
110
+
111
+ wechat_gate_config.generate_js_request_params(REFERER_URL) # 返回JS-SDK的验证参数,供前端JS-SDK使用
112
+ ```
113
+
114
+ 当然你也可以手工来初始化配置,甚至指定配置文件的路径:
115
+
116
+ ```
117
+ config = WechatGate::Config.new('eggman', '/path/to/what/ever/you/want.yml')
118
+ ```
119
+
120
+ access_token和JS_SDK中ticket都有过期时间和刷新次数限制,这里已经考虑了,你可以不用管,如果你想手工刷新,可以这样:
121
+
122
+ ```
123
+ config.refresh_access_token
124
+ config.refresh_jsapi_ticket
125
+ ```
126
+
127
+ **配置文件支持erb**
128
+
129
+ > 更多接口和文档请直接看源码,写的很详细
130
+
131
+ ## JS-SDK
132
+
133
+ ```ruby
134
+ def ticket
135
+ url = CGI.unescape(params[:url]) # 微信中用户访问的页面
136
+ @data = wechat_gate_config.generate_js_request_params(url) # 生成微信JS-SDK所需的jsapi_ticket,signature等等参数供前段js使用
137
+ render content_type: "application/javascript"
138
+ end
139
+ ```
140
+
141
+ ticket.js.erb:
142
+
143
+ ```
144
+ var wxServerConfig = <%= @data.to_json.html_safe %>;
145
+ <%= params[:callback] %>();
146
+ ```
147
+
148
+ 然后在微信端页面引入以下代码:
149
+
150
+ ```js
151
+ (function() {
152
+ var ticket = document.createElement("script");
153
+ ticket.src = "http://localhost/api/wechat_ticket/ticket.js?url=" + encodeURIComponent(window.location.href.split('#')[0]) + "&callback=wxCallback";
154
+ var s = document.getElementsByTagName("script")[0];
155
+ s.parentNode.insertBefore(ticket, s);
156
+ })();
157
+ ```
158
+
159
+ ## 其他功能
160
+
161
+ ### 自定义菜单
162
+
163
+ 首先设置菜单配置文件,config/wechat_menu.yml,支持erb,格式请参考[微信自定义菜单文档](https://mp.weixin.qq.com/wiki):
164
+
165
+ ```
166
+ button:
167
+ - type: view
168
+ name: 我的2
169
+ url: <%= @config.oauth2_entrance_url(scope: 'snsapi_userinfo', state: 'profile') %>
170
+ - type: view
171
+ name: 课程
172
+ sub_button:
173
+ - type: view
174
+ name: 免费课程
175
+ url: <%= @config.oauth2_entrance_url(scope: 'snsapi_userinfo', state: 'free') %>
176
+ - type: view
177
+ name: 付费课程
178
+ url: <%= @config.oauth2_entrance_url(scope: 'snsapi_userinfo', state: 'paid') %>
179
+ ```
180
+
181
+ > 其中的**@config**变量为当前微信公众号实例,请不要修改,直接使用
182
+
183
+ 然后执行rake任务:
184
+
185
+ ```shell
186
+ $rails wechat_gate:create_menu APP_NAME=eggman CONFIG=/path/to/wechat.yml MENU=/path/to/wechat_menu.yml
187
+ ```
188
+
189
+ 其中,CONFIG默认为config/wechat.yml,MENU默认为config/wechat_menu.yml,APP_NAME必须指定
190
+
191
+ ## TODO
192
+
193
+ 添加测试
data/RELEASE.md ADDED
@@ -0,0 +1,11 @@
1
+ # v0.1.5
2
+
3
+ - add app_id to js-sdk API
4
+
5
+ # v0.1.4
6
+
7
+ - add create_menu rake task
8
+
9
+ # v0.1.3
10
+
11
+ # v0.1.2
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "wechat-gate"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "wechat-gate"
5
+
6
+ # usage:
7
+ # output_type: "/file/to/write/js/variable" | ruby | js
8
+ # refresh_token_and_ticket app_name page_url output_type
9
+ #
10
+
11
+ WechatGate::Config.new(ARGV[0]) do |config|
12
+ config.write_token_to_file ARGV[1], ARGV[2]
13
+ end
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
data/config/wechat.yml ADDED
@@ -0,0 +1,18 @@
1
+ # 请参考这个配置文件
2
+ #
3
+ app_name:
4
+ development:
5
+ app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
6
+ app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
7
+ oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
8
+
9
+ production:
10
+ app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
11
+ app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
12
+ oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
13
+
14
+ # 如果不需要多环境支持,也可以这样
15
+ app_name:
16
+ app_id: <%= ENV['WECHAT_APP_NAME_APP_ID'] %>
17
+ app_secret: <%= ENV['WECHAT_APP_NAME_APP_SECRET'] %>
18
+ oauth2_redirect_uri: <%= ENV['WECHAT_APP_NAME_OAUTH2_REDIRECT_URI'] %>
@@ -0,0 +1,22 @@
1
+ require 'wechat_gate/exception'
2
+
3
+ namespace :wechat_gate do
4
+
5
+ def validate_envs
6
+ raise WechatGate::Exception::ConfigException, 'need specify APP_NAME!' unless ENV['APP_NAME']
7
+ end
8
+
9
+ desc "create menu, APP_NAME=app_name, CONFIG=/path/to/config/file.yml, MENU=/path/to/menu/config/file.yml"
10
+ task :create_menu => :environment do
11
+ validate_envs
12
+
13
+ @config = WechatGate::Config.new(ENV['APP_NAME'], ENV['CONFIG'])
14
+
15
+ menu_file = ENV['MENU']
16
+ menu_file = "#{Dir.pwd}/config/wechat_menu.yml" unless menu_file
17
+ raise WechatGate::Exception::ConfigException, "MENU #{menu_file} not found!" unless File.exists?(menu_file)
18
+
19
+ menu = YAML.load(ERB.new(File.read(menu_file)).result(binding))
20
+ @config.menu_create(JSON.generate(menu))
21
+ end
22
+ end
@@ -0,0 +1,8 @@
1
+ require "wechat_gate/version"
2
+ require "wechat_gate/config"
3
+ require "wechat_gate/railtie" if defined?(Rails)
4
+
5
+ if defined?(ActionController)
6
+ ActionController::Base.send(:include, WechatGate::Controller)
7
+ ActionController::API.send(:include, WechatGate::Controller)
8
+ end
@@ -0,0 +1,64 @@
1
+ require 'yaml'
2
+ require 'erb'
3
+ require 'wechat_gate/tokens/access_token'
4
+ require 'wechat_gate/tokens/jsapi_ticket'
5
+ require 'wechat_gate/tokens/ext'
6
+ require 'wechat_gate/oauth'
7
+ require 'wechat_gate/user'
8
+ require 'wechat_gate/menu'
9
+ require 'wechat_gate/media'
10
+ require 'wechat_gate/message'
11
+ require 'wechat_gate/send_message'
12
+ require 'wechat_gate/exception'
13
+ require 'wechat_gate/controller'
14
+
15
+ module WechatGate
16
+ class Config
17
+
18
+ attr_reader :app_name
19
+ attr_reader :config
20
+ attr_reader :output_type
21
+
22
+ include WechatGate::Tokens::AccessToken
23
+ include WechatGate::Tokens::JsapiTicket
24
+ include WechatGate::Tokens::Ext
25
+ include WechatGate::Oauth
26
+ include WechatGate::User
27
+ include WechatGate::Menu
28
+ include WechatGate::Media
29
+ include WechatGate::Message
30
+ include WechatGate::SendMessage
31
+
32
+ def initialize app_name, config_file = nil
33
+ unless config_file
34
+ if defined?(Rails)
35
+ config_file = "#{Rails.root}/config/wechat.yml"
36
+ end
37
+ end
38
+
39
+ raise Exception::ConfigException, "no wechat configuration file found!" unless config_file
40
+ unless File.exists?(config_file)
41
+ raise Exception::ConfigException, "configuration file does not exist!"
42
+ end
43
+
44
+ config_text = ERB.new(File.read(config_file)).result
45
+ configs = YAML.load(config_text)
46
+ unless configs[app_name]
47
+ raise Exception::ConfigException, "no configuration found for app: #{app_name}!"
48
+ end
49
+
50
+ @config = if defined?(Rails)
51
+ configs[app_name][Rails.env] || configs[app_name]
52
+ else
53
+ configs[app_name]
54
+ end
55
+
56
+ @app_name = app_name
57
+
58
+ yield(self) if block_given?
59
+ end
60
+
61
+ end
62
+
63
+
64
+ end
@@ -0,0 +1,73 @@
1
+ require 'wechat_gate/exception'
2
+
3
+ module WechatGate
4
+ module Controller
5
+
6
+ def self.included base
7
+ base.send :include, InstanceMethods
8
+
9
+ base.class_eval do
10
+ helper_method :is_wechat_logged_in?
11
+ helper_method :current_open_id
12
+
13
+ class_attribute :wechat_gate_app_name
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ protected
19
+ def wechat_gate_setup
20
+ unless self.class.wechat_gate_app_name
21
+ raise Exception::ConfigException, "please specify wechat_gate_app_name!"
22
+ end
23
+
24
+ @wechat_gate_config = WechatGate::Config.new(self.class.wechat_gate_app_name)
25
+ end
26
+
27
+ def wechat_gate_config
28
+ @wechat_gate_config ||= wechat_gate_setup
29
+ end
30
+
31
+ def is_wechat_logged_in?
32
+ !!session[:user_open_id]
33
+ end
34
+
35
+ def current_open_id
36
+ session[:user_open_id]
37
+ end
38
+
39
+ def wechat_user_signin(user_open_id)
40
+ session[:user_open_id] = user_open_id
41
+ end
42
+
43
+ def wechat_user_auth
44
+ unless is_wechat_logged_in?
45
+ redirect_to wechat_gate_config.oauth2_entrance_url(scope: "snsapi_base")
46
+ end
47
+ end
48
+
49
+ def bind_user_with_open_id user_model
50
+ if is_wechat_logged_in? and user_model.open_id.blank?
51
+ user_model.update_attribute :open_id, current_open_id
52
+ end
53
+ end
54
+
55
+ def is_legal_from_wechat_server?
56
+ data = [
57
+ wechat_gate_config.config["push_token"],
58
+ params[:timestamp],
59
+ params[:nonce]
60
+ ]
61
+
62
+ Digest::SHA1.hexdigest(data.sort.join('')) == params[:signature]
63
+ end
64
+
65
+ def check_wechat_server
66
+ unless is_legal_from_wechat_server?
67
+ render text: "illegal signature!"
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,7 @@
1
+ module WechatGate
2
+ module Exception
3
+
4
+ class ConfigException < StandardError; end
5
+
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ require 'wechat_gate/request'
2
+
3
+ module WechatGate
4
+ module Media
5
+ #
6
+ # http://mp.weixin.qq.com/wiki/15/8386c11b7bc4cdd1499c572bfe2e95b3.html
7
+ #
8
+
9
+ # type: image | video | voice | news (图文)
10
+ def medias(type = 'news', offset = 0, count = 20)
11
+ WechatGate::Request.send(
12
+ "https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=#{self.access_token}",
13
+ :post,
14
+ {
15
+ "type": type,
16
+ "offset": offset,
17
+ "count": count
18
+ }.to_json
19
+ )
20
+ end
21
+ end
22
+
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ require 'wechat_gate/request'
2
+
3
+ module WechatGate
4
+ module Menu
5
+ def menu_get
6
+ WechatGate::Request.send(
7
+ "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=#{self.access_token}"
8
+ )
9
+ end
10
+
11
+ def menu_create(menu_hash)
12
+ WechatGate::Request.send(
13
+ "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=#{self.access_token}",
14
+ :post,
15
+ menu_hash
16
+ )
17
+ end
18
+ end
19
+
20
+
21
+ end
@@ -0,0 +1,53 @@
1
+ module WechatGate
2
+ module Message
3
+ #
4
+ # http://mp.weixin.qq.com/wiki/17/f298879f8fb29ab98b2f2971d42552fd.html
5
+ #
6
+ # 消息发送只能是被动的,就是微信会把用户的聊天数据推送到服务器端,然后服务器利用返回值作出相应
7
+ #
8
+
9
+ def message_body(type, to, body)
10
+ content = case type.to_sym
11
+ when :text
12
+ %Q{
13
+ <Content><![CDATA[#{body}]]></Content>
14
+ }
15
+ when :image
16
+ # body: media_id
17
+ %Q{
18
+ <Image>
19
+ <MediaId><![CDATA[#{body}]]></MediaId>
20
+ </Image>
21
+ }
22
+ when :voice
23
+ %Q{
24
+ <Voice>
25
+ <MediaId><![CDATA[#{body}]]></MediaId>
26
+ </Voice>
27
+ }
28
+ when :video
29
+ # body: { media_id: MEDIA_ID, title: TITLE, description: DESCRIPTION }
30
+ %Q{
31
+ <Video>
32
+ <MediaId><![CDATA[#{body[:media_id]}]]></MediaId>
33
+ <Title><![CDATA[#{body[:title]}]]></Title>
34
+ <Description><![CDATA[#{body[:description]}]]></Description>
35
+ </Video>
36
+ }
37
+ end
38
+
39
+ %Q{
40
+ <xml>
41
+ <ToUserName><![CDATA[#{to}]]></ToUserName>
42
+ <FromUserName><![CDATA[#{self.config['wechat_id']}]]></FromUserName>
43
+ <CreateTime>#{Time.now.to_i}</CreateTime>
44
+ <MsgType><![CDATA[#{type}]]></MsgType>
45
+ #{content}
46
+ </xml>
47
+ }
48
+ end
49
+
50
+ end
51
+
52
+
53
+ end
@@ -0,0 +1,80 @@
1
+ require 'rest-client'
2
+ require 'wechat_gate/request'
3
+
4
+ module WechatGate
5
+ module Oauth
6
+ #
7
+ #
8
+ # 此module中的access_token是微信网页授权OAuth2.0的专用access_token,和其他modules中所需需要的access_token
9
+ # 不是一个概念,这里的access_token主要就是用户非公众号的关注用户来进行网页授权访问的,其中:
10
+ # scope:
11
+ # snsapi_base 用于公众号已关注用户,已关注的用户默认已经得到了授权,这里只是为了取得当前用户的openid,
12
+ # 此时对用户的所有操作 (ex: WechatGate::User模块) 可以直接利用系统基本的access_token (WechatGate::Tokens::AccessToken模块) 来进行。
13
+ # snsapi_userinfo 用户非关注用户取得授权,这里遵循标准的OAuth2.0授权流程,取得用户信息需要利用本模块中的方法来进行。
14
+ # 这里的access_token是和用户绑定的。
15
+ #
16
+ #
17
+
18
+ #
19
+ # 用户点击授权入口页面
20
+ #
21
+ def oauth2_entrance_url(ops = {})
22
+ ops = {
23
+ state: 'empty', # 自定义参数值
24
+ redirect_uri: self.config["oauth2_redirect_uri"],
25
+ scope: 'snsapi_base' # snsapi_base | snsapi_userinfo
26
+ }.merge(ops)
27
+
28
+ "https://open.weixin.qq.com/connect/oauth2/authorize?appid=#{self.config['app_id']}&redirect_uri=#{CGI.escape(ops[:redirect_uri])}&response_type=code&scope=#{ops[:scope]}&state=#{ops[:state]}#wechat_redirect"
29
+ end
30
+
31
+ # TODO
32
+ # 这里目前需要调用该gem的应用自身来保存用户的access_token, refresh_token, openid等参数以及判断有效期
33
+ # 不过这个地方也可以不做缓存,微信在这个地方没有限制API的调用次数。
34
+ #
35
+ # code:
36
+ # code来源于网页端redirect_uri页面得到的微信端返回的参数,专门用户取得下一步的access_token
37
+ # 该接口会返回:
38
+ # {
39
+ # "access_token" => "access_token",
40
+ # "expires_in"=>7200,
41
+ # "refresh_token"=>"refresh_token",
42
+ # "openid"=>"MZkwG5sAx-d4PMQ6Lq1xisE",
43
+ # "scope"=>"snsapi_base"
44
+ # }
45
+ # 此时已经获得了用户的openid,如果用户为公众号的订阅用户,就可以直接利用Tokens::AccessToken的token来对改用户调用业务接口了,
46
+ # 此时这里的access_token意义就不大了,这里的access_token和Tokens::AccessToken的token是完全不一样的。
47
+ #
48
+ def oauth2_access_token(code)
49
+ WechatGate::Request.send("https://api.weixin.qq.com/sns/oauth2/access_token?appid=#{self.config['app_id']}&secret=#{self.config['app_secret']}&code=#{code}&grant_type=authorization_code")
50
+ end
51
+
52
+ # access_token拥有较短的有效期,当access_token超时后,可以使用refresh_token进行刷新,
53
+ # refresh_token拥有较长的有效期(7天、30天、60天、90天),当refresh_token失效的后,需要用户重新授权。
54
+ #
55
+ def oauth2_access_token_valid?(access_token, openid)
56
+ WechatGate::Request.send("https://api.weixin.qq.com/sns/auth?access_token=#{access_token}&openid=#{openid}")
57
+ end
58
+
59
+ # 利用refresh_token刷新access_token
60
+ #
61
+ # response:
62
+ # {
63
+ # "access_token":"ACCESS_TOKEN",
64
+ # "expires_in":7200,
65
+ # "refresh_token":"REFRESH_TOKEN",
66
+ # "openid":"OPENID",
67
+ # "scope":"SCOPE"
68
+ # }
69
+ def oauth2_refresh_access_token(refresh_token)
70
+ WechatGate::Request.send("https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=#{self.config['app_id']}&grant_type=refresh_token&refresh_token=#{refresh_token}")
71
+ end
72
+
73
+ # 获取用户信息
74
+ #
75
+ def oauth2_user(access_token, openid)
76
+ WechatGate::Request.send("https://api.weixin.qq.com/sns/userinfo?access_token=#{access_token}&openid=#{openid}&lang=zh_CN")
77
+ end
78
+ end
79
+
80
+ end
@@ -0,0 +1,8 @@
1
+ module WechatGate
2
+ require 'rails'
3
+
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks { load "tasks/wechat_gate.rake" }
6
+ end
7
+
8
+ end
@@ -0,0 +1,34 @@
1
+ require 'rest-client'
2
+
3
+ module WechatGate
4
+ module Request
5
+ def self.send(url, method = :get, payload = nil, headers = nil, &block)
6
+ method = method.to_sym
7
+
8
+ opts = {
9
+ method: method,
10
+ url: url,
11
+ verify_ssl: false
12
+ }
13
+ if method == :post and payload
14
+ opts.merge! payload: payload
15
+ end
16
+
17
+ if headers
18
+ opts.merge! headers: headers
19
+ end
20
+
21
+ response = RestClient::Request.execute(opts)
22
+ response = JSON.parse(response)
23
+ raise response.to_s if response['errmsg'] and response['errmsg'] != 'ok'
24
+
25
+ if block_given?
26
+ yield(response)
27
+ else
28
+ response
29
+ end
30
+ end
31
+ end
32
+
33
+
34
+ end
@@ -0,0 +1,138 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+ require 'wechat_gate/request'
3
+
4
+ module WechatGate
5
+ module SendMessage
6
+
7
+ #
8
+ # https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421140549&token=&lang=zh_CN
9
+ # 这个接口有发送限制,服务号每月只能发送4次,订阅号每天一次
10
+ #
11
+ def mass_send open_ids, msg_options = {}
12
+ payload = {
13
+ "touser": [open_ids].flatten,
14
+ "msgtype": "text",
15
+ "text": { "content": "hello from boxer."}
16
+ }.merge(msg_options)
17
+
18
+ WechatGate::Request.send(
19
+ "https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=#{self.access_token}",
20
+ :post,
21
+ payload.to_json
22
+ )
23
+ end
24
+
25
+ #
26
+ # 这个是微信的私有接口,在微信网页端才能使用,给单个用户发消息,而且没有发送数量限制,
27
+ # 这里是模拟公众号登录,然后给用户发送消息(在“用户管理”页面点击单个用户)
28
+ #
29
+ # **这个接口要求48小时内只能发送20条**
30
+ # **用户在48小时内与公众账号只要发生互动(点击公众号菜单或者发过消息),阀值就会刷新**
31
+ #
32
+ # Request URL:https://mp.weixin.qq.com/cgi-bin/singlesend?t=ajax-response&f=json&token=1927434739&lang=zh_CN
33
+ # Request Method:POST
34
+ # Query String:
35
+ # t:ajax-response
36
+ # f:json
37
+ # token:1927434739
38
+ # lang:zh_CN
39
+ # Form Data
40
+ # token:1927434739
41
+ # lang:zh_CN
42
+ # f:json
43
+ # ajax:1
44
+ # random:0.9569772763087352
45
+ # type:1
46
+ # content:ll
47
+ # tofakeid:oMZZkwziWkGiXq3-RjGHcgXn6v70
48
+ # imgcode:
49
+ #
50
+ mattr_accessor :single_send_cookies
51
+ mattr_accessor :single_send_token
52
+ mattr_accessor :single_send_cookies_refreshed_at
53
+ mattr_accessor :single_send_cookies_expired_in
54
+
55
+ mattr_accessor :single_send_ua
56
+
57
+ #
58
+ # 在公众号端没有cookie过期的概念,这里把登录后的cookie和token缓存起来,为了不频繁的登录公众号
59
+ #
60
+ # **这个功能只能在生产环境的真实公众账号中测试,不支持在沙盒中测试**
61
+ #
62
+ self.single_send_cookies_refreshed_at = Time.now.to_i
63
+ self.single_send_cookies_expired_in = 3600
64
+
65
+ self.single_send_ua = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.110 Safari/537.36"
66
+
67
+ def single_send open_id, content
68
+ single_send_if_need_refresh_cookie
69
+
70
+ opts = {
71
+ method: :post,
72
+ url: "https://mp.weixin.qq.com/cgi-bin/singlesend?t=ajax-response&f=json&token=#{self.single_send_token}&lang=zh_CN",
73
+ verify_ssl: false,
74
+ payload: {
75
+ token: self.single_send_token,
76
+ lang: "zh_CN",
77
+ f: "json",
78
+ ajax: 1,
79
+ random: Random.rand,
80
+ type: 1, # 文本消息
81
+ content: content,
82
+ tofakeid: open_id,
83
+ imgcode: ""
84
+ }.to_query,
85
+ headers: {
86
+ 'User-Agent': self.single_send_ua,
87
+ 'Cookie': self.single_send_cookies,
88
+ 'Referer': "https://mp.weixin.qq.com/cgi-bin/singlesendpage?t=message/send&action=index&tofakeid=#{open_id}&token=#{self.single_send_token}&lang=zh_CN"
89
+ }
90
+ }
91
+
92
+ response = RestClient::Request.execute(opts)
93
+ data = JSON.parse(response)
94
+ raise response.to_s if data['errmsg'] and data['errmsg'] != 'ok'
95
+ data
96
+ end
97
+
98
+ private
99
+ def single_send_if_need_refresh_cookie
100
+ if self.single_send_cookies.nil? ||
101
+ (Time.now.to_i - self.single_send_cookies_refreshed_at > self.single_send_cookies_expired_in)
102
+ single_send_login_and_refresh_cookies_w_token
103
+ end
104
+ end
105
+
106
+ #
107
+ # 登录公众号,缓存登录后的cookie和token
108
+ #
109
+ def single_send_login_and_refresh_cookies_w_token
110
+ opts = {
111
+ method: :post,
112
+ url: "https://mp.weixin.qq.com/cgi-bin/login?lang=zh_CN",
113
+ verify_ssl: false,
114
+ payload: {
115
+ username: self.config['wechat_login_username'],
116
+ pwd: Digest::MD5.hexdigest(self.config['wechat_login_password']),
117
+ imgcode: "",
118
+ f: "json"
119
+ }.to_query,
120
+ headers: {
121
+ 'User-Agent': self.single_send_ua,
122
+ 'Referer': 'https://mp.weixin.qq.com/'
123
+ }
124
+ }
125
+
126
+ response = RestClient::Request.execute(opts)
127
+ data = JSON.parse(response)
128
+ raise response.to_s if data['errmsg'] and data['errmsg'] != 'ok'
129
+
130
+ self.single_send_cookies = response.cookies.map {|k, v| k + "=" + v}.join("; ")
131
+ data["redirect_url"].gsub(/token=(\d+)/) { |x| self.single_send_token = $1 }
132
+ self.single_send_cookies_refreshed_at = Time.now.to_i
133
+ end
134
+
135
+ end
136
+
137
+
138
+ end
@@ -0,0 +1,30 @@
1
+ require 'wechat_gate/tokens/base'
2
+
3
+ module WechatGate
4
+ module Tokens
5
+ module AccessToken
6
+ def self.included base
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ def access_token
12
+ token = WechatGate::Tokens::AccessToken::Get.refresh(self)
13
+ end
14
+ end
15
+
16
+ class Get < WechatGate::Tokens::Base
17
+ def url
18
+ "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=#{@config.config['app_id']}&secret=#{@config.config['app_secret']}"
19
+ end
20
+
21
+ def save response
22
+ File.open(saved_file, 'w') do |f|
23
+ f.puts "#{Time.now.to_i} #{response['access_token']}"
24
+ end
25
+ end
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ require 'rest-client'
2
+ require 'wechat_gate/request'
3
+
4
+ module WechatGate
5
+ module Tokens
6
+ class Base
7
+
8
+ def self.refresh config
9
+ handler = new(config)
10
+ handler.run
11
+ end
12
+
13
+ def initialize config
14
+ @config = config
15
+ end
16
+
17
+ def run
18
+ fetch if is_expired?
19
+ File.readlines(saved_file).first.chomp.split(' ').last
20
+ end
21
+
22
+ def expired_in
23
+ 7100
24
+ end
25
+
26
+ protected
27
+ def url
28
+ raise "need to implement #ur method in sub-class"
29
+ end
30
+
31
+ def save response
32
+ raise "need to implement #save method in sub-class"
33
+ end
34
+
35
+ private
36
+ def fetch
37
+ WechatGate::Request.send(url) do |response|
38
+ save response
39
+ response
40
+ end
41
+ end
42
+
43
+ def saved_file
44
+ # File.expand_path("../../../../data/APP-#{@config.app_name}-#{self.class.name}", __FILE__)
45
+ "/tmp/APP-#{@config.app_name}-#{self.class.name}-#{@config.config['app_id']}"
46
+ end
47
+
48
+ def is_expired?
49
+ if File.exists?(saved_file)
50
+ line = File.readlines(saved_file).first
51
+ unless line
52
+ return true
53
+ else
54
+ line = line.chomp
55
+ return Time.now.to_i - line.split(' ').first.to_i >= expired_in
56
+ end
57
+ else
58
+ FileUtils.touch(saved_file)
59
+ return true
60
+ end
61
+ end
62
+
63
+ end
64
+
65
+ end
66
+ end
@@ -0,0 +1,50 @@
1
+ module WechatGate
2
+ module Tokens
3
+ module Ext
4
+ # please refer
5
+ # http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
6
+ #
7
+ def generate_js_request_params current_page_url
8
+ current_page_url = current_page_url.gsub(/#.*/, '')
9
+
10
+ letters = ('a'..'z').to_a + (0..9).to_a
11
+ word_creator = proc { letters.sample }
12
+ noncestr = []
13
+ 16.times { noncestr << word_creator.call }
14
+
15
+ params = {
16
+ "jsapi_ticket" => self.jsapi_ticket,
17
+ "noncestr" => noncestr.join,
18
+ "timestamp" => Time.now.to_i,
19
+ "url" => current_page_url
20
+ }
21
+
22
+ sign_string = params.keys.sort.inject([]) { |m, n| m << "#{n}=#{params[n]}" }.join('&')
23
+ sign = Digest::SHA1.hexdigest(sign_string)
24
+ params["signature"] = sign
25
+ params["app_id"] = self.config['app_id']
26
+
27
+ params
28
+ end
29
+
30
+ def write_token_to_file current_page_url, output_type
31
+ params = generate_js_request_params(current_page_url)
32
+ case output_type
33
+ when /\// # write to file
34
+ f = File.open(output_type, 'w')
35
+ f.write %Q{<script>var wxServerConfig = #{params.to_json}</script>}
36
+ f.close
37
+ when 'ruby'
38
+ params
39
+ when 'js'
40
+ params.to_json
41
+ else
42
+ params
43
+ end
44
+ end
45
+
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ require 'wechat_gate/tokens/base'
2
+
3
+ module WechatGate
4
+ module Tokens
5
+ module JsapiTicket
6
+ def self.included base
7
+ base.send :include, InstanceMethods
8
+ end
9
+
10
+ module InstanceMethods
11
+ def jsapi_ticket
12
+ token = WechatGate::Tokens::JsapiTicket::Get.refresh(self)
13
+ end
14
+ end
15
+
16
+ class Get < WechatGate::Tokens::Base
17
+ def url
18
+ token = WechatGate::Tokens::AccessToken::Get.refresh(@config)
19
+ "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=#{token}&type=jsapi"
20
+ end
21
+
22
+ def save response
23
+ File.open(saved_file, 'w') do |f|
24
+ f.puts "#{Time.now.to_i} #{response['ticket']}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -0,0 +1,16 @@
1
+ require 'wechat_gate/request'
2
+
3
+ module WechatGate
4
+ module User
5
+ def users(next_openid = nil)
6
+ WechatGate::Request.send("https://api.weixin.qq.com/cgi-bin/user/get?access_token=#{self.access_token}&next_openid=#{next_openid}")
7
+ end
8
+
9
+ def user(openid)
10
+ WechatGate::Request.send("https://api.weixin.qq.com/cgi-bin/user/info?access_token=#{self.access_token}&openid=#{openid}&lang=zh_CN")
11
+ end
12
+
13
+ end
14
+
15
+
16
+ end
@@ -0,0 +1,3 @@
1
+ module WechatGate
2
+ VERSION = "0.1.6"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'wechat_gate/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "wechat-gate-updater"
8
+ spec.version = WechatGate::VERSION
9
+ spec.authors = ["Lei Lee"]
10
+ spec.email = ["qinyuanmao.live@gmail.com"]
11
+
12
+ spec.summary = %q{隔壁家老李的微信开发Ruby Gem}
13
+ spec.description = %q{接口简单易用,实在是微信开发必备之好Gem}
14
+ spec.homepage = "https://github.com/qinyuanmao/wechat_gate"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "rest-client", '>= 1.8'
23
+ spec.add_dependency "activesupport", '>= 5.0.1'
24
+
25
+ spec.add_development_dependency "bundler", ">= 1.10"
26
+ spec.add_development_dependency "rake", ">= 10.0"
27
+
28
+ end
metadata ADDED
@@ -0,0 +1,130 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wechat-gate-updater
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ platform: ruby
6
+ authors:
7
+ - Lei Lee
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2021-02-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activesupport
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 5.0.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 5.0.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '1.10'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '1.10'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ description: 接口简单易用,实在是微信开发必备之好Gem
70
+ email:
71
+ - qinyuanmao.live@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - CODE_OF_CONDUCT.md
78
+ - Gemfile
79
+ - Gemfile.lock
80
+ - LICENSE.txt
81
+ - README.md
82
+ - RELEASE.md
83
+ - Rakefile
84
+ - bin/console
85
+ - bin/refresh_token_and_ticket
86
+ - bin/setup
87
+ - config/wechat.yml
88
+ - lib/tasks/wechat_gate.rake
89
+ - lib/wechat-gate.rb
90
+ - lib/wechat_gate/config.rb
91
+ - lib/wechat_gate/controller.rb
92
+ - lib/wechat_gate/exception.rb
93
+ - lib/wechat_gate/media.rb
94
+ - lib/wechat_gate/menu.rb
95
+ - lib/wechat_gate/message.rb
96
+ - lib/wechat_gate/oauth.rb
97
+ - lib/wechat_gate/railtie.rb
98
+ - lib/wechat_gate/request.rb
99
+ - lib/wechat_gate/send_message.rb
100
+ - lib/wechat_gate/tokens/access_token.rb
101
+ - lib/wechat_gate/tokens/base.rb
102
+ - lib/wechat_gate/tokens/ext.rb
103
+ - lib/wechat_gate/tokens/jsapi_ticket.rb
104
+ - lib/wechat_gate/user.rb
105
+ - lib/wechat_gate/version.rb
106
+ - wechat-gate.gemspec
107
+ homepage: https://github.com/qinyuanmao/wechat_gate
108
+ licenses:
109
+ - MIT
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubygems_version: 3.1.4
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: 隔壁家老李的微信开发Ruby Gem
130
+ test_files: []