wechat_client 0.1.0

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
+ SHA1:
3
+ metadata.gz: ff41ee062a5bf71013ed7080adffd679d7cf0cbd
4
+ data.tar.gz: 45d27e3a68794e7528559ea7252d82fa9e54647a
5
+ SHA512:
6
+ metadata.gz: 39cec4d97951fda68bdda47f63d3ca1bf245f190bda34635bad3378fc4ede14b9beaf140728804f9f4a85c8baad24e3ae941d217f128f4718071ca0e3f3d2aa9
7
+ data.tar.gz: 8515a09255f876ec5f6ab59a886ba6e6eb403bfa02f684cadec0e12e9c1a26a85ebf37fd22ff13c616ed8c17a5ccb99ac56730a7fe151cc45433aed426cb09a2
data/.gitignore ADDED
@@ -0,0 +1,26 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
23
+ /.idea
24
+ spec/spec_helper.rb
25
+ *.*.log
26
+ *.log
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation --color spec --drb
2
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Scott1743
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # WechatClient
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'wechat_client'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install wechat_client
16
+
17
+ ## Usage
18
+
19
+ TODO: Write usage instructions here
20
+
21
+ ## Contributing
22
+
23
+ 1. Fork it ( https://github.com/[my-github-username]/wechat_client/fork )
24
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
25
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
26
+ 4. Push to the branch (`git push origin my-new-feature`)
27
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,21 @@
1
+ require 'wechat_client/apis/getting'
2
+ require 'wechat_client/apis/posting'
3
+
4
+ module WechatClient
5
+ class Api
6
+ attr_reader :options
7
+ attr_writer :access_token
8
+ def initialize options
9
+ @options = options
10
+ end
11
+
12
+ include WechatClient::Apis::Getting
13
+ include WechatClient::Apis::Posting
14
+
15
+ private
16
+
17
+ def _access_token_
18
+ @access_token || WechatClient.client.get(:access_token){|json| json['access_token']}
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ module WechatClient
2
+ module Apis
3
+ module Getting
4
+
5
+ #获取接口调用凭据
6
+
7
+ def server_ip
8
+ "https://api.weixin.qq.com/cgi-bin/getcallbackip?access_token=#{_access_token_}"
9
+ end
10
+
11
+ def access_token
12
+ "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=#{@options.app_id}&secret=#{@options.app_secret}"
13
+ end
14
+
15
+ def __access_token__
16
+ # for debug
17
+ "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=#{@options.app_id}&secret=#{@options.app_secret}"
18
+ end
19
+
20
+ #发送消息
21
+
22
+ def send_message
23
+ "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=#{_access_token_}"
24
+ end
25
+
26
+ #素材管理
27
+
28
+ #用户管理
29
+
30
+ def auth_access_token
31
+ "https://api.weixin.qq.com/sns/oauth2/access_token?appid=#{@options.app_id}&secret=#{@options.app_secret}&grant_type=authorization_code&"
32
+ end
33
+
34
+ def base_info
35
+ "https://api.weixin.qq.com/cgi-bin/user/info?access_token=#{_access_token_}&lang=zh_CN&"
36
+ end
37
+
38
+ def auth_info
39
+ "https://api.weixin.qq.com/sns/userinfo?lang=zh_CN&"
40
+ end
41
+
42
+ def auth_access_token_validation
43
+ "https://api.weixin.qq.com/sns/auth?"
44
+ end
45
+
46
+ def refresh_auth_access_token
47
+ "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=#{@options.app_id}&grant_type=refresh_token&"
48
+ end
49
+
50
+ #菜单管理
51
+
52
+ def menus
53
+ "https://api.weixin.qq.com/cgi-bin/menu/get?access_token=#{_access_token_}"
54
+ end
55
+
56
+ def delete_menus
57
+ "https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=#{_access_token_}"
58
+ end
59
+
60
+ #账户管理
61
+
62
+ #jsapi
63
+
64
+ def jsapi_ticket
65
+ "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=#{_access_token_}&type=jsapi"
66
+ end
67
+
68
+ def __jsapi_ticket__
69
+ "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=#{_access_token_}&type=jsapi"
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,12 @@
1
+ module WechatClient
2
+ module Apis
3
+ module Posting
4
+
5
+ #菜单管理
6
+
7
+ def create_menus
8
+ "https://api.weixin.qq.com/cgi-bin/menu/create?access_token=#{_access_token_}"
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,28 @@
1
+ module WechatClient
2
+ module CacheStore
3
+ class BaseStore
4
+ def initialize client
5
+ @client = client
6
+ end
7
+
8
+ def write key, json
9
+ mar_obj = Marshal.dump(json)
10
+ cache_write(key, mar_obj)
11
+ end
12
+
13
+ def read key
14
+ mar_obj = cache_read(key)
15
+ Marshal.load(mar_obj)
16
+ end
17
+
18
+ # def exists key; end
19
+ # def cache_write key, mar; end
20
+ # def cache_read mar; end
21
+ # def delete key; end
22
+
23
+ # def method_missing m, *args
24
+ # @client.send(m, *args)
25
+ # end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ require 'wechat_client/cache_store/base_store'
2
+
3
+ module WechatClient
4
+ module CacheStore
5
+ class RedisStore < BaseStore
6
+ def exists key
7
+ @client.exists(key)
8
+ end
9
+
10
+ def delete key
11
+ @client.del(key)
12
+ end
13
+
14
+ def cache_write key, mar
15
+ @client.multi do
16
+ @client.set(key, mar)
17
+ @client.expire(key, 60*60)
18
+ end
19
+ end
20
+
21
+ def cache_read key
22
+ @client.get(key)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,142 @@
1
+ require 'wechat_client/api'
2
+ require 'rest-client'
3
+ require 'monitor'
4
+
5
+ module WechatClient
6
+ class WechatApiNotFoundError < NoMethodError
7
+ require 'fuzzy_match'
8
+ @@methods = WechatClient::Api.public_instance_methods(false)
9
+ Finder = FuzzyMatch.new(@@methods)
10
+
11
+ def initialize(name)
12
+ fuzzy_method_name = Finder.find(name)
13
+ error_msg = "undefined wechat api: #{name}"
14
+ error_msg << ". Do you mean #{fuzzy_method_name}?" unless fuzzy_method_name.empty?
15
+ super(error_msg)
16
+ end
17
+ end
18
+
19
+ class ResponseCodeError < StandardError
20
+ def initialize(error_json)
21
+ super "#{error_json['errcode']}:#{error_json['errmsg']}"
22
+ end
23
+ end
24
+
25
+ class Client
26
+ extend MonitorMixin
27
+ attr_reader :error, :url
28
+
29
+ DEFAULT = {
30
+ app_id: nil,
31
+ app_secret: nil,
32
+ serial_no: nil,
33
+ raise_flag: false, # 微信返回错误代码时raise
34
+ verify_ssl: true, # RestClient::Request.execute 参数
35
+ auto_cache: true # 是否使用自带的缓存方式
36
+ }
37
+
38
+ NEED_AUTO_CACHE = [:access_token, :jsapi_ticket]
39
+
40
+ def initialize args
41
+ @options = DEFAULT.merge(args.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo})
42
+ @api = Api.new(options)
43
+ @error = {}
44
+ end
45
+
46
+ GETTING_APIS = WechatClient::Apis::Getting.public_instance_methods(false)
47
+ POSTING_APIS = WechatClient::Apis::Posting.public_instance_methods(false)
48
+
49
+ GETTING_APIS.each do |api|
50
+ define_method api do |params = {}, access_token = nil, &block|
51
+ get(api, params, access_token, &block)
52
+ end
53
+ end
54
+
55
+ POSTING_APIS.each do |api|
56
+ define_method api do |post_data = {}, access_token = nil, &block|
57
+ post(api, post_data, access_token, &block)
58
+ end
59
+ end
60
+
61
+ def get(api_name, params = {}, at = nil, &block)
62
+ Client.synchronize do
63
+ @api.access_token = at
64
+ response = request_handle(api_name, params) do |params|
65
+ @url += params.to_query if params
66
+ RestClient::Request.execute(method: :get, url: @url, verify_ssl: options.verify_ssl)
67
+ end
68
+ response_handle(api_name, response, options.raise_flag, &block)
69
+ end
70
+ end
71
+
72
+ def post(api_name, post_data = {}, at = nil, &block)
73
+ Client.synchronize do
74
+ @api.access_token = at
75
+ response = request_handle(api_name, post_data) do |post_data|
76
+ RestClient::Request.execute(method: :post, url: @url, payload: post_data.to_json, verify_ssl: options.verify_ssl)
77
+ end
78
+ response_handle(api_name, response, options.raise_flag, &block)
79
+ end
80
+ end
81
+
82
+ def options
83
+ OpenStruct.new(@options)
84
+ end
85
+
86
+ private
87
+
88
+ def cache_read api_name
89
+ WechatClient.cache.exists("__wechat_client_#{api_name}__") ? \
90
+ WechatClient.cache.read("__wechat_client_#{api_name}__") : yield(nil)
91
+ end
92
+
93
+ def cache_write api_name, json
94
+ WechatClient.cache.write("__wechat_client_#{api_name}__", json)
95
+ end
96
+
97
+ def request_handle api_name, params, &block
98
+ if @api.respond_to?(api_name)
99
+ @url = @api.send(api_name)
100
+ else
101
+ raise WechatApiNotFoundError, api_name
102
+ end
103
+
104
+ return cache_read(api_name, &block) if options.auto_cache && NEED_AUTO_CACHE.include?(api_name.to_sym)
105
+ yield params
106
+ end
107
+
108
+ def response_handle(api_name, response, raise_flag)
109
+ # 重置 @error
110
+ @error.clear
111
+ content_type = response.respond_to?(:headers) ? response.headers[:content_type] : ""
112
+
113
+ # 判断返回类型, 已知 json 和 image 两种
114
+ data = if content_type.include?("json") || content_type.include?("text")
115
+ json = JSON.parse(response.body.gsub(/[\u0000-\u001f]+/, ''))
116
+ if json["errcode"] && json["errcode"].to_i != 0
117
+ WechatClient.logger.error(json)
118
+ raise(ResponseCodeError, json) if raise_flag
119
+ @error = json
120
+ else
121
+ WechatClient.logger.info("WechatClientLog: api-#{api_name} return #{json}")
122
+ cache_write(api_name, json)if options.auto_cache && NEED_AUTO_CACHE.include?(api_name.to_sym)
123
+ end
124
+ json
125
+ elsif content_type.include? "image"
126
+ tmp = Tempfile.new('tmp')
127
+ tmp.binmode
128
+ tmp.write(response)
129
+ tmp.close
130
+ tmp
131
+ else
132
+ response
133
+ end
134
+
135
+ if block_given? && @error.empty?
136
+ yield(data)
137
+ else
138
+ data
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,50 @@
1
+ module WechatClient
2
+ class OAuth
3
+ attr_reader :code, :scope, :expires_in, :unionid, :access_token, :refresh_token, :openid
4
+
5
+ def initialize options
6
+ set_valuables options[:code]
7
+ end
8
+
9
+ def auth
10
+ if @scope == "snsapi_base"
11
+ WechatClient.client.base_info openid: @openid
12
+ else
13
+ WechatClient.client.auth_info access_token: @access_token, openid: @openid
14
+ end
15
+ end
16
+
17
+ def refresh_auth
18
+ WechatClient.client.refresh_auth_access_token refresh_token: @refresh_token
19
+ end
20
+
21
+ def valid?
22
+ WechatClient.client.auth_access_token_validation access_token: @access_token,\
23
+ openid: @openid do |json|
24
+ json["errmsg"].downcase == "ok"
25
+ end
26
+ end
27
+
28
+ def self.generate_auth_link url, state = "", scope = "snsapi_userinfo"
29
+ params = {
30
+ appid: WechatClient.client.options.app_id,
31
+ redirect_uri: url,
32
+ response_type: "code",
33
+ scope: scope,
34
+ state: state
35
+ }
36
+ "https://open.weixin.qq.com/connect/oauth2/authorize?#{params.to_query}#wechat_redirect"
37
+ end
38
+
39
+ private
40
+
41
+ def set_valuables code
42
+ @code = code
43
+ WechatClient.client.auth_access_token code: @code do |json|
44
+ json.each do |k, v|
45
+ instance_variable_set "@#{k}", v
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module WechatClient
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ require 'redis'
3
+ require 'yell'
4
+ require 'nokogiri'
5
+ require 'pry'
6
+ require 'pry-nav'
7
+
8
+ unless defined? ActiveSupport
9
+ class String
10
+ def to_query(key)
11
+ require 'cgi' unless defined?(CGI) && defined?(CGI::escape)
12
+ "#{CGI.escape(key.to_s)}=#{CGI.escape(to_s)}"
13
+ end
14
+ end
15
+
16
+ class Hash
17
+ def to_query(namespace = nil)
18
+ collect do |key, value|
19
+ value.to_s.to_query(namespace ? "#{namespace}[#{key}]" : key)
20
+ end.compact.sort! * '&'
21
+ end
22
+ end
23
+ end
24
+
25
+ require 'wechat_client/cache_store/redis_store'
26
+
27
+ module WechatClient
28
+ class << self
29
+ attr_reader :options, :logger, :client
30
+ end
31
+
32
+ @options = {}
33
+ @logger = Yell.new :datefile, 'wechat_client.log'
34
+ @cache_client = Redis.new(host: '127.0.0.1', port: '6379', db: '0')
35
+
36
+ def self.cache
37
+ if @cache_client && client.options.auto_cache
38
+ klass = "WechatClient::CacheStore::" + @cache_client.class.name + "Store"
39
+ @_cache_ ||= const_get(klass).new(@cache_client)
40
+ end
41
+ end
42
+
43
+ def self.client
44
+ @client ||= WechatClient::Client.new @options
45
+ end
46
+ end
47
+
48
+ require 'wechat_client/client'
49
+ require 'wechat_client/o_auth'
50
+
@@ -0,0 +1,75 @@
1
+ describe WechatClient::Client do
2
+ it "get client" do
3
+ client = WechatClient.client
4
+ expect(client.options).to be_a(OpenStruct)
5
+ expect(client.options.app_id.empty?).to eq false
6
+ expect(client.options.verify_ssl).to eq false
7
+ end
8
+
9
+ it "get cached access_token" do
10
+ at = WechatClient.client.get(:access_token){|json| json['access_token']}
11
+ at_ag = WechatClient.client.access_token{|json| json['access_token']}
12
+ expect(at.is_a?(String)).to eq true
13
+ expect(at).to eq at_ag
14
+ end
15
+
16
+ it "get uncached access_token" do
17
+ options = WechatClient.options.merge(auto_cache: false)
18
+ client = WechatClient::Client.new options
19
+ at = client.get(:access_token){|json| json['access_token']}
20
+ at_ag = client.access_token{|json| json['access_token']}
21
+ expect(at == at_ag).to eq false
22
+ end
23
+
24
+ it "force get new access_token" do
25
+ at_ag = WechatClient.client.get(:__access_token__){|json| json['access_token']}
26
+ at = WechatClient.client.get(:access_token){|json| json['access_token']}
27
+ WechatClient.cache.delete("__wechat_client_access_token__")
28
+ expect(at == at_ag).to eq false
29
+ end
30
+
31
+ # it "post message" do
32
+ # message = {
33
+ # touser: 'o7c6JwXe9EqzIXyMZDWAFHn_Dzps',
34
+ # msgtype: 'text',
35
+ # text: {content: 'hello world'}
36
+ # }
37
+ # errcode = WechatClient.client.post(:send_message, message) {|json| json["errcode"]}
38
+ # expect(errcode).to eq 0
39
+ # end
40
+
41
+ it "mulit threads" do
42
+ options = WechatClient.options.merge(auto_cache: true)
43
+ client = WechatClient::Client.new options
44
+ threads = []
45
+ result = ThreadSafe::Array.new
46
+
47
+ RestClient::Request.stub(:execute).and_return("test")
48
+ 10.times do
49
+ 100.times do
50
+ threads << Thread.new do
51
+ client.get(:access_token)
52
+ result << client.url
53
+ end
54
+ end
55
+ 100.times do
56
+ threads << Thread.new do
57
+ client.get(:jsapi_ticket)
58
+ result << client.url
59
+ end
60
+ end
61
+ threads.each(&:join)
62
+ count = result.select{|url| url == WechatClient::Api.new(client.options).access_token}.count
63
+ expect(count).to eq 100
64
+ result.clear
65
+ end
66
+ end
67
+
68
+ it "get base info" do
69
+ openid = "oCo5Tt6gj2kghPQ4_avdQd4nwlMA"
70
+ unionid = WechatClient.client.base_info openid: openid do |json|
71
+ json["unionid"]
72
+ end
73
+ expect(unionid).to eq "oHv8ewZPGaZokH2ScUkWxiSGKY7s"
74
+ end
75
+ end
@@ -0,0 +1,17 @@
1
+ require "rspec"
2
+ require "wechat_client"
3
+ require "pry"
4
+ require "pry-nav"
5
+
6
+ module WechatClient
7
+ @@options = {
8
+ "app_id" => "app_id",
9
+ "app_secret" => "app_secret",
10
+ "serial_no" => "gh_xxx",
11
+ "token" => "token",
12
+ "verify_ssl" => false
13
+ }
14
+ end
15
+
16
+ RSpec.configure do |config|
17
+ end
@@ -0,0 +1,33 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'wechat_client/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "wechat_client"
8
+ spec.version = WechatClient::VERSION
9
+ spec.authors = ["Scott1743 nobrick"]
10
+ spec.email = ["512981271@qq.com qucool@gmail.com"]
11
+ spec.summary = %q{ A WeChat client }
12
+ spec.description = %q{ A WeChat client }
13
+ spec.homepage = "https://github.com/nobrick/wechat_client"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'rest-client', '~> 1.6'
22
+ spec.add_dependency 'redis', '~> 2.0'
23
+ spec.add_dependency 'yell', '~> 1.2'
24
+ spec.add_dependency 'nokogiri', '~> 1.4'
25
+ spec.add_runtime_dependency 'fuzzy_match', '~> 2.0'
26
+
27
+ spec.add_development_dependency "bundler", "~> 1.6"
28
+ spec.add_development_dependency "rake", "~> 12.0"
29
+ spec.add_development_dependency "rspec", "~> 3.5"
30
+ spec.add_development_dependency "pry", "~> 0.10"
31
+ spec.add_development_dependency "pry-nav", "~> 0.2"
32
+ spec.add_development_dependency "thread_safe", "~> 0.3"
33
+ end
metadata ADDED
@@ -0,0 +1,219 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wechat_client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Scott1743 nobrick
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-05-04 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.6'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: yell
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: nokogiri
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.4'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.4'
69
+ - !ruby/object:Gem::Dependency
70
+ name: fuzzy_match
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.6'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.6'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '12.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '12.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rspec
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '3.5'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '3.5'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '0.10'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '0.10'
139
+ - !ruby/object:Gem::Dependency
140
+ name: pry-nav
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '0.2'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '0.2'
153
+ - !ruby/object:Gem::Dependency
154
+ name: thread_safe
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - "~>"
158
+ - !ruby/object:Gem::Version
159
+ version: '0.3'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - "~>"
165
+ - !ruby/object:Gem::Version
166
+ version: '0.3'
167
+ description: " A WeChat client "
168
+ email:
169
+ - 512981271@qq.com qucool@gmail.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - ".gitignore"
175
+ - ".rspec"
176
+ - Gemfile
177
+ - LICENSE.txt
178
+ - README.md
179
+ - Rakefile
180
+ - lib/wechat_client.rb
181
+ - lib/wechat_client/api.rb
182
+ - lib/wechat_client/apis/getting.rb
183
+ - lib/wechat_client/apis/posting.rb
184
+ - lib/wechat_client/cache_store/base_store.rb
185
+ - lib/wechat_client/cache_store/redis_store.rb
186
+ - lib/wechat_client/client.rb
187
+ - lib/wechat_client/o_auth.rb
188
+ - lib/wechat_client/version.rb
189
+ - spec/client_spec.rb
190
+ - spec/spec_helper.rb.example
191
+ - wechat_client.gemspec
192
+ homepage: https://github.com/nobrick/wechat_client
193
+ licenses:
194
+ - MIT
195
+ metadata: {}
196
+ post_install_message:
197
+ rdoc_options: []
198
+ require_paths:
199
+ - lib
200
+ required_ruby_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - ">="
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ required_rubygems_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ requirements: []
211
+ rubyforge_project:
212
+ rubygems_version: 2.4.5.1
213
+ signing_key:
214
+ specification_version: 4
215
+ summary: A WeChat client
216
+ test_files:
217
+ - spec/client_spec.rb
218
+ - spec/spec_helper.rb.example
219
+ has_rdoc: