wechat_public 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/wechat/access_token.rb +35 -0
- data/lib/wechat/api.rb +72 -0
- data/lib/wechat/client.rb +74 -0
- data/lib/wechat/message.rb +171 -0
- data/lib/wechat/responder.rb +124 -0
- data/lib/wechat/test.rb +7 -0
- data/lib/wechat_public/test.rb +7 -0
- data/lib/wechat_public/version.rb +1 -1
- data/lib/wechat_public.rb +5 -4
- data/wechat_public.gemspec +5 -0
- metadata +37 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 744beadd7a9d6788a4ba9c10749824fd3f075fd2
|
4
|
+
data.tar.gz: 9185439b87d650df02b85728b84614ad070a0196
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d2b1b1d158d4b177363c2036de94987fe9d83851ae9ad7f08ac85a31ff38e1cc15cd83ae29c92707f3b234f252c1c5af73d7eb78ad2b4080fd3a824717fc3a5b
|
7
|
+
data.tar.gz: 7dbcff4a3af30a9ff00689db86fdabf3f5542a50e4f348fff1df285fb267912e798c5b9e63750252fb9b672c111739b2ceb91756a38beb3cad055df16cf39798
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module WechatPublic
|
2
|
+
class AccessToken
|
3
|
+
attr_reader :client, :appid, :secret, :token_file, :token_data
|
4
|
+
|
5
|
+
def initialize(client, appid, secret, token_file)
|
6
|
+
@appid = appid
|
7
|
+
@secret = secret
|
8
|
+
@client = client
|
9
|
+
@token_file = token_file
|
10
|
+
end
|
11
|
+
|
12
|
+
def token
|
13
|
+
begin
|
14
|
+
@token_data ||= JSON.parse(File.read(token_file))
|
15
|
+
rescue
|
16
|
+
self.refresh
|
17
|
+
end
|
18
|
+
return valid_token(@token_data)
|
19
|
+
end
|
20
|
+
|
21
|
+
def refresh
|
22
|
+
data = client.get("token", params:{grant_type: "client_credential", appid: appid, secret: secret})
|
23
|
+
File.open(token_file, 'w'){|f| f.write(data.to_s)} if valid_token(data)
|
24
|
+
return @token_data = data
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
def valid_token token_data
|
29
|
+
access_token = token_data["access_token"]
|
30
|
+
raise "Response didn't have access_token" if access_token.blank?
|
31
|
+
return access_token
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/wechat/api.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'wechat/client'
|
2
|
+
require 'wechat/access_token'
|
3
|
+
|
4
|
+
class WechatPublic::Api
|
5
|
+
attr_reader :access_token, :client
|
6
|
+
|
7
|
+
API_BASE = "https://api.weixin.qq.com/cgi-bin/"
|
8
|
+
FILE_BASE = "http://file.api.weixin.qq.com/cgi-bin/"
|
9
|
+
|
10
|
+
def initialize appid, secret, token_file
|
11
|
+
@client = WechatPublic::Client.new(API_BASE)
|
12
|
+
@access_token = WechatPublic::AccessToken.new(@client, appid, secret, token_file)
|
13
|
+
end
|
14
|
+
|
15
|
+
def users nextid = nil
|
16
|
+
params = {params: {next_openid: nextid}} if nextid.present?
|
17
|
+
get('user/get', params||{})
|
18
|
+
end
|
19
|
+
|
20
|
+
def user openid
|
21
|
+
get("user/info", params:{openid: openid})
|
22
|
+
end
|
23
|
+
|
24
|
+
def menu
|
25
|
+
get("menu/get")
|
26
|
+
end
|
27
|
+
|
28
|
+
def menu_delete
|
29
|
+
get("menu/delete")
|
30
|
+
end
|
31
|
+
|
32
|
+
def menu_create menu
|
33
|
+
# 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
|
34
|
+
post("menu/create", JSON.generate(menu))
|
35
|
+
end
|
36
|
+
|
37
|
+
def media media_id
|
38
|
+
response = get "media/get", params:{media_id: media_id}, base: FILE_BASE, as: :file
|
39
|
+
end
|
40
|
+
|
41
|
+
def media_create type, file
|
42
|
+
post "media/upload", {upload:{media: file}}, params:{type: type}, base: FILE_BASE
|
43
|
+
end
|
44
|
+
|
45
|
+
def custom_message_send message
|
46
|
+
post "message/custom/send", message.to_json, content_type: :json
|
47
|
+
end
|
48
|
+
|
49
|
+
def template_message_send message
|
50
|
+
post "message/template/send", message.to_json, content_type: :json
|
51
|
+
end
|
52
|
+
|
53
|
+
protected
|
54
|
+
def get path, headers={}
|
55
|
+
with_access_token(headers[:params]){|params| client.get path, headers.merge(params: params)}
|
56
|
+
end
|
57
|
+
|
58
|
+
def post path, payload, headers = {}
|
59
|
+
with_access_token(headers[:params]){|params| client.post path, payload, headers.merge(params: params)}
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_access_token params={}, tries=2
|
63
|
+
begin
|
64
|
+
params ||= {}
|
65
|
+
yield(params.merge(access_token: access_token.token))
|
66
|
+
rescue WechatPublic::AccessTokenExpiredError => ex
|
67
|
+
access_token.refresh
|
68
|
+
retry unless (tries -= 1).zero?
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
module WechatPublic
|
4
|
+
class Client
|
5
|
+
|
6
|
+
attr_reader :base
|
7
|
+
|
8
|
+
def initialize(base)
|
9
|
+
@base = base
|
10
|
+
end
|
11
|
+
|
12
|
+
def get path, header={}
|
13
|
+
request(path, header) do |url, header|
|
14
|
+
RestClient.get(url, header)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def post path, payload, header = {}
|
19
|
+
request(path, header) do |url, header|
|
20
|
+
RestClient.post(url, payload, header)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def request path, header={}, &block
|
25
|
+
url = "#{header.delete(:base) || self.base}#{path}"
|
26
|
+
as = header.delete(:as)
|
27
|
+
header.merge!(:accept => :json)
|
28
|
+
response = yield(url, header)
|
29
|
+
|
30
|
+
raise "Request not OK, response code #{response.code}" if response.code != 200
|
31
|
+
parse_response(response, as || :json) do |parse_as, data|
|
32
|
+
break data unless (parse_as == :json && data["errcode"].present?)
|
33
|
+
|
34
|
+
case data["errcode"]
|
35
|
+
when 0 # for request didn't expect results
|
36
|
+
true
|
37
|
+
|
38
|
+
when 42001, 40014 #42001: access_token超时, 40014:不合法的access_token
|
39
|
+
raise AccessTokenExpiredError
|
40
|
+
|
41
|
+
else
|
42
|
+
raise ResponseError.new(data['errcode'], data['errmsg'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
def parse_response response, as
|
49
|
+
content_type = response.headers[:content_type]
|
50
|
+
parse_as = {
|
51
|
+
/^application\/json/ => :json,
|
52
|
+
/^image\/.*/ => :file
|
53
|
+
}.inject([]){|memo, match| memo<<match[1] if content_type =~ match[0]; memo}.first || as || :text
|
54
|
+
|
55
|
+
case parse_as
|
56
|
+
when :file
|
57
|
+
file = Tempfile.new("tmp")
|
58
|
+
file.binmode
|
59
|
+
file.write(response.body)
|
60
|
+
file.close
|
61
|
+
data = file
|
62
|
+
|
63
|
+
when :json
|
64
|
+
data = JSON.parse(response.body.gsub /[\u0000-\u001f]+/, '')
|
65
|
+
|
66
|
+
else
|
67
|
+
data = response.body
|
68
|
+
end
|
69
|
+
|
70
|
+
return yield(parse_as, data)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module WechatPublic
|
2
|
+
class Message
|
3
|
+
|
4
|
+
JSON_KEY_MAP = {
|
5
|
+
"ToUserName" => "touser",
|
6
|
+
"MediaId" => "media_id",
|
7
|
+
"ThumbMediaId" => "thumb_media_id",
|
8
|
+
"TemplateId"=>"template_id"
|
9
|
+
}
|
10
|
+
|
11
|
+
class << self
|
12
|
+
def from_hash message_hash
|
13
|
+
self.new(message_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
def to to_user
|
17
|
+
self.new(:ToUserName=>to_user, :CreateTime=>Time.now.to_i)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ArticleBuilder
|
22
|
+
attr_reader :items
|
23
|
+
delegate :count, to: :items
|
24
|
+
def initialize
|
25
|
+
@items=Array.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def item title: "title", description: nil, pic_url: nil, url: nil
|
29
|
+
items << {:Title=> title, :Description=> description, :PicUrl=> pic_url, :Url=> url}.reject{|k,v| v.nil? }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :message_hash
|
34
|
+
|
35
|
+
def initialize(message_hash)
|
36
|
+
@message_hash = message_hash || {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def [](key)
|
40
|
+
message_hash[key]
|
41
|
+
end
|
42
|
+
|
43
|
+
def reply
|
44
|
+
Message.new(
|
45
|
+
:ToUserName=>message_hash[:FromUserName],
|
46
|
+
:FromUserName=>message_hash[:ToUserName],
|
47
|
+
:CreateTime=>Time.now.to_i
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
def as type
|
52
|
+
case type
|
53
|
+
when :text
|
54
|
+
message_hash[:Content]
|
55
|
+
|
56
|
+
when :image, :voice, :video
|
57
|
+
WechatPublic.api.media(message_hash[:MediaId])
|
58
|
+
|
59
|
+
when :location
|
60
|
+
message_hash.slice(:Location_X, :Location_Y, :Scale, :Label).inject({}){|results, value|
|
61
|
+
results[value[0].to_s.underscore.to_sym] = value[1]; results}
|
62
|
+
else
|
63
|
+
raise "Don't know how to parse message as #{type}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def to openid
|
68
|
+
update(:ToUserName=>openid)
|
69
|
+
end
|
70
|
+
|
71
|
+
def text content
|
72
|
+
update(:MsgType=>"text", :Content=>content)
|
73
|
+
end
|
74
|
+
|
75
|
+
def image media_id
|
76
|
+
update(:MsgType=>"image", :Image=>{:MediaId=>media_id})
|
77
|
+
end
|
78
|
+
|
79
|
+
def voice media_id
|
80
|
+
update(:MsgType=>"voice", :Voice=>{:MediaId=>media_id})
|
81
|
+
end
|
82
|
+
|
83
|
+
def video media_id, opts={}
|
84
|
+
video_fields = camelize_hash_keys({media_id: media_id}.merge(opts.slice(:title, :description)))
|
85
|
+
update(:MsgType=>"video", :Video=>video_fields)
|
86
|
+
end
|
87
|
+
|
88
|
+
def music thumb_media_id, music_url, opts={}
|
89
|
+
music_fields = camelize_hash_keys(opts.slice(:title, :description, :HQ_music_url).merge(music_url: music_url, thumb_media_id: thumb_media_id))
|
90
|
+
update(:MsgType=>"music", :Music=>music_fields)
|
91
|
+
end
|
92
|
+
|
93
|
+
def news collection, &block
|
94
|
+
if block_given?
|
95
|
+
article = ArticleBuilder.new
|
96
|
+
collection.each{|item| yield(article, item)}
|
97
|
+
items = article.items
|
98
|
+
else
|
99
|
+
items = collection.collect do |item|
|
100
|
+
camelize_hash_keys(item.symbolize_keys.slice(:title, :description, :pic_url, :url).reject{|k,v| v.nil? })
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
update(:MsgType=>"news", :ArticleCount=> items.count,
|
105
|
+
:Articles=> items.collect{|item| camelize_hash_keys(item)})
|
106
|
+
end
|
107
|
+
|
108
|
+
def template opts={}
|
109
|
+
template_fields = camelize_hash_keys(opts.symbolize_keys.slice(:template_id, :topcolor, :url, :data))
|
110
|
+
update(:MsgType=>"template",:Template=> template_fields)
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_xml
|
114
|
+
message_hash.to_xml(root: "xml", children: "item", skip_instruct: true, skip_types: true)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_json
|
118
|
+
json_hash = deep_recursive(message_hash) do |key, value|
|
119
|
+
key = key.to_s
|
120
|
+
[(JSON_KEY_MAP[key] || key.downcase), value]
|
121
|
+
end
|
122
|
+
|
123
|
+
json_hash.slice!("touser", "msgtype", "content", "image", "voice", "video", "music", "news", "articles","template").to_hash
|
124
|
+
case json_hash["msgtype"]
|
125
|
+
when "text"
|
126
|
+
json_hash["text"] = {"content" => json_hash.delete("content")}
|
127
|
+
when "news"
|
128
|
+
json_hash["news"] = {"articles" => json_hash.delete("articles")}
|
129
|
+
when "template"
|
130
|
+
json_hash.merge! json_hash['template']
|
131
|
+
end
|
132
|
+
JSON.generate(json_hash)
|
133
|
+
end
|
134
|
+
|
135
|
+
def save_to! model_class
|
136
|
+
model = model_class.new(underscore_hash_keys(message_hash))
|
137
|
+
model.save!
|
138
|
+
return self
|
139
|
+
end
|
140
|
+
|
141
|
+
private
|
142
|
+
def camelize_hash_keys hash
|
143
|
+
deep_recursive(hash){|key, value| [key.to_s.camelize.to_sym, value]}
|
144
|
+
end
|
145
|
+
|
146
|
+
def underscore_hash_keys hash
|
147
|
+
deep_recursive(hash){|key, value| [key.to_s.underscore.to_sym, value]}
|
148
|
+
end
|
149
|
+
|
150
|
+
def update fields={}
|
151
|
+
message_hash.merge!(fields)
|
152
|
+
return self
|
153
|
+
end
|
154
|
+
|
155
|
+
def deep_recursive hash, &block
|
156
|
+
hash.inject({}) do |memo, val|
|
157
|
+
key,value = *val
|
158
|
+
case value.class.name
|
159
|
+
when "Hash"
|
160
|
+
value = deep_recursive(value, &block)
|
161
|
+
when "Array"
|
162
|
+
value = value.collect{|item| item.is_a?(Hash) ? deep_recursive(item, &block) : item}
|
163
|
+
end
|
164
|
+
|
165
|
+
key,value = yield(key, value)
|
166
|
+
memo.merge!(key => value)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
end
|
171
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module WechatPublic
|
2
|
+
module Responder
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
self.skip_before_filter :verify_authenticity_token
|
7
|
+
self.before_filter :verify_signature, only: [:show, :create]
|
8
|
+
#delegate :wehcat, to: :class
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
attr_accessor :wechat, :token
|
14
|
+
|
15
|
+
def on message_type, with: nil, respond: nil, &block
|
16
|
+
raise "Unknow message type" unless message_type.in? [:text, :image, :voice, :video, :location, :link, :event, :login]
|
17
|
+
config=respond.nil? ? {} : {:respond=>respond}
|
18
|
+
config.merge!(:proc=>block) if block_given?
|
19
|
+
|
20
|
+
if (with.present? && !message_type.in?([:text, :event]))
|
21
|
+
raise "Only text and event message can take :with parameters"
|
22
|
+
else
|
23
|
+
config.merge!(:with=>with) if with.present?
|
24
|
+
end
|
25
|
+
|
26
|
+
responders(message_type) << config
|
27
|
+
return config
|
28
|
+
end
|
29
|
+
|
30
|
+
def responders type
|
31
|
+
@responders ||= Hash.new
|
32
|
+
@responders[type] ||= Array.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def responder_for message, &block
|
36
|
+
message_type = message[:MsgType].to_sym
|
37
|
+
responders = responders(message_type)
|
38
|
+
case message_type
|
39
|
+
when :text
|
40
|
+
yield(* match_responders(responders, message[:Content]))
|
41
|
+
|
42
|
+
when :event
|
43
|
+
if message[:Event] == 'CLICK'
|
44
|
+
yield(* match_responders(responders, message[:EventKey]))
|
45
|
+
else
|
46
|
+
yield(* match_responders(responders, message[:Event]))
|
47
|
+
end
|
48
|
+
else
|
49
|
+
yield(responders.first)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def match_responders responders, value
|
56
|
+
matched = responders.inject({scoped:nil, general:nil}) do |matched, responder|
|
57
|
+
condition = responder[:with]
|
58
|
+
|
59
|
+
if condition.nil?
|
60
|
+
matched[:general] ||= [responder, value]
|
61
|
+
next matched
|
62
|
+
end
|
63
|
+
|
64
|
+
if condition.is_a? Regexp
|
65
|
+
matched[:scoped] ||= [responder] + $~.captures if(value =~ condition)
|
66
|
+
else
|
67
|
+
matched[:scoped] ||= [responder, value] if(value == condition)
|
68
|
+
end
|
69
|
+
matched
|
70
|
+
end
|
71
|
+
return matched[:scoped] || matched[:general]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# def show
|
77
|
+
# render :text => params[:echostr]
|
78
|
+
# end
|
79
|
+
|
80
|
+
# def create
|
81
|
+
# request = Wechat::Message.from_hash(params[:xml] || post_xml)
|
82
|
+
# response = self.class.responder_for(request) do |responder, *args|
|
83
|
+
# responder ||= self.class.responders(:fallback).first
|
84
|
+
|
85
|
+
# next if responder.nil?
|
86
|
+
# next request.reply.text responder[:respond] if (responder[:respond])
|
87
|
+
# next responder[:proc].call(*args.unshift(request)) if (responder[:proc])
|
88
|
+
# end
|
89
|
+
|
90
|
+
# if response.respond_to? :to_xml
|
91
|
+
# render xml: response.to_xml
|
92
|
+
# else
|
93
|
+
# render :nothing => true, :status => 200, :content_type => 'text/html'
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
|
97
|
+
def get_uid params
|
98
|
+
request = WechatPublic::Message.from_hash(params[:xml] || post_xml)
|
99
|
+
response = self.class.responder_for(request) do |responder, *args|
|
100
|
+
responder ||= self.class.responders(:login).first
|
101
|
+
|
102
|
+
next if responder.nil?
|
103
|
+
next request.reply.text responder[:respond] if (responder[:respond])
|
104
|
+
next responder[:proc].call(*args.unshift(request)) if (responder[:proc])
|
105
|
+
end
|
106
|
+
{
|
107
|
+
request: request,
|
108
|
+
response: response
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def verify_signature
|
114
|
+
array = [self.class.token, params[:timestamp], params[:nonce]].compact.collect(&:to_s).sort
|
115
|
+
render :text => "Forbidden", :status => 403 if params[:signature] != Digest::SHA1.hexdigest(array.join)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
def post_xml
|
120
|
+
data = Hash.from_xml(request.raw_post)
|
121
|
+
HashWithIndifferentAccess.new_from_hash_copying_default data.fetch('xml', {})
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
data/lib/wechat/test.rb
ADDED
data/lib/wechat_public.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
require "wechat_public/version"
|
2
|
-
require "wechat/
|
2
|
+
require "wechat/api"
|
3
3
|
module WechatPublic
|
4
4
|
autoload :Message, "wechat/message"
|
5
5
|
autoload :Responder, "wechat/responder"
|
6
6
|
autoload :Response, "wechat/response"
|
7
|
-
|
8
|
-
|
7
|
+
autoload :Test, 'wechat/test'
|
8
|
+
#WechatPublic::Test.print
|
9
9
|
class AccessTokenExpiredError < StandardError; end
|
10
10
|
class ResponseError < StandardError
|
11
11
|
attr_reader :error_code
|
@@ -31,7 +31,8 @@ module WechatPublic
|
|
31
31
|
puts config
|
32
32
|
OpenStruct.new(config)
|
33
33
|
puts "判断 ActionController::Base...加载了么",ActionController::Base
|
34
|
-
|
34
|
+
puts "判断 WechatPublic::Test 。。加载了么",WechatPublic::Test
|
35
|
+
WechatPublic::Test.print
|
35
36
|
end
|
36
37
|
end
|
37
38
|
|
data/wechat_public.gemspec
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
lib = File.expand_path('../lib', __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
4
|
require 'wechat_public/version'
|
5
|
+
require 'wechat_public/test'
|
5
6
|
|
6
7
|
Gem::Specification.new do |spec|
|
7
8
|
spec.name = "wechat_public"
|
@@ -20,4 +21,8 @@ Gem::Specification.new do |spec|
|
|
20
21
|
|
21
22
|
spec.add_development_dependency "bundler", "~> 1.6"
|
22
23
|
spec.add_development_dependency 'rake', '~> 0'
|
24
|
+
# spec.add_development_dependency 'rest_client', '~> 1.8'
|
25
|
+
# spec.add_development_dependency 'rails', '~> 4.2'
|
26
|
+
spec.add_runtime_dependency 'rest_client', '>= 1.8'
|
27
|
+
spec.add_runtime_dependency 'rails', ">= 4.2"
|
23
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: wechat_public
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Will
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-04-
|
11
|
+
date: 2015-04-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -38,6 +38,34 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rest_client
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.8'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.8'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rails
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.2'
|
41
69
|
description: one coding programmer.
|
42
70
|
email:
|
43
71
|
- yh@fir.im
|
@@ -50,7 +78,14 @@ files:
|
|
50
78
|
- LICENSE.txt
|
51
79
|
- README.md
|
52
80
|
- Rakefile
|
81
|
+
- lib/wechat/access_token.rb
|
82
|
+
- lib/wechat/api.rb
|
83
|
+
- lib/wechat/client.rb
|
84
|
+
- lib/wechat/message.rb
|
85
|
+
- lib/wechat/responder.rb
|
86
|
+
- lib/wechat/test.rb
|
53
87
|
- lib/wechat_public.rb
|
88
|
+
- lib/wechat_public/test.rb
|
54
89
|
- lib/wechat_public/version.rb
|
55
90
|
- wechat_public.gemspec
|
56
91
|
homepage: ''
|