weichat_rails 0.0.1
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +30 -0
- data/Rakefile +1 -0
- data/lib/weichat_rails/access_token.rb +34 -0
- data/lib/weichat_rails/api.rb +103 -0
- data/lib/weichat_rails/auto_generate_secret_key.rb +25 -0
- data/lib/weichat_rails/client.rb +81 -0
- data/lib/weichat_rails/message.rb +170 -0
- data/lib/weichat_rails/responder.rb +167 -0
- data/lib/weichat_rails/version.rb +3 -0
- data/lib/weichat_rails.rb +47 -0
- data/weichat_rails.gemspec +29 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: dbde8b2fd7ae43678424029ec5a2bde7b8ee99df
|
4
|
+
data.tar.gz: 4d380976e2671b6e8b547543efb9428d356b5f15
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae0761e2533164dfd36d6dd1fee6ff174a7fe57febeb82ef363ab0fc690a75119d23bbba3f8e464511639924ab6ac1c29b8d02ebc80b88be585bcde25d07becb
|
7
|
+
data.tar.gz: cb58481410216c5d1b4885b10ee00ed08589b97f943d06cc9e5ef22a6f0c0c0f63688060b2cda088812bf6a7b989c0546db7152ee791a2863272701444a65510
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 qmliu
|
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,30 @@
|
|
1
|
+
# WeichatRails
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'weichat_rails'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install weichat_rails
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( http://github.com/<my-github-username>/weichat_rails/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
30
|
+
touch readme
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module WeichatRails
|
2
|
+
class AccessToken
|
3
|
+
attr_reader :client, :appid, :secret
|
4
|
+
|
5
|
+
CacheScope = "#{Rails.application.class.parent_name}_access_token"
|
6
|
+
def initialize(client, appid, secret)
|
7
|
+
@appid = appid
|
8
|
+
@secret = secret
|
9
|
+
@client = client
|
10
|
+
end
|
11
|
+
|
12
|
+
#store token in rails.cache
|
13
|
+
def token
|
14
|
+
Rails.cache.fetch("#{CacheScope}#{appid}",expires_in: 7200) do
|
15
|
+
data = client.get("token", params:{grant_type: "client_credential", appid: appid, secret: secret})
|
16
|
+
valid_token(data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#delete the cache
|
21
|
+
def refresh
|
22
|
+
Rails.cache.delete("#{CacheScope}#{appid}")
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def valid_token token_data
|
27
|
+
access_token = token_data["access_token"]
|
28
|
+
raise "Response didn't have access_token" if access_token.blank?
|
29
|
+
return access_token
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'weichat_rails/client'
|
2
|
+
require 'weichat_rails/access_token'
|
3
|
+
|
4
|
+
class WeichatRails::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
|
+
KEFU_BASE = "https://api.weixin.qq.com/customservice/"
|
10
|
+
#https://api.weixin.qq.com/cgi-bin/customservice/getkflist?access_token=ACCESS_TOKEN
|
11
|
+
|
12
|
+
def initialize appid, secret
|
13
|
+
@client = WeichatRails::Client.new(API_BASE)
|
14
|
+
@access_token = WeichatRails::AccessToken.new(@client, appid, secret)
|
15
|
+
end
|
16
|
+
|
17
|
+
def users
|
18
|
+
get("user/get")
|
19
|
+
end
|
20
|
+
|
21
|
+
def user openid
|
22
|
+
get("user/info", params:{openid: openid})
|
23
|
+
end
|
24
|
+
|
25
|
+
def menu
|
26
|
+
get("menu/get")
|
27
|
+
end
|
28
|
+
|
29
|
+
def menu_delete
|
30
|
+
get("menu/delete")
|
31
|
+
end
|
32
|
+
|
33
|
+
def kefulist
|
34
|
+
get("customservice/getkflist")
|
35
|
+
end
|
36
|
+
|
37
|
+
def group_get
|
38
|
+
get("groups/get")
|
39
|
+
end
|
40
|
+
|
41
|
+
def group_user_id openid
|
42
|
+
json_str = JSON.generate({openid: openid})
|
43
|
+
post("groups/getid", json_str)
|
44
|
+
end
|
45
|
+
|
46
|
+
def group_update openid, to_groupid
|
47
|
+
json_str = JSON.generate({openid: openid,to_groupid: to_groupid})
|
48
|
+
post("groups/members/update", json_str)
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_duokefu_records time,pageindex
|
52
|
+
json_str = JSON.generate({starttime: time.beginning_of_day.to_i,endtime: time.end_of_day.to_i,pagesize: 50,pageindex: pageindex})
|
53
|
+
post("msgrecord/getrecord",json_str,base: KEFU_BASE)
|
54
|
+
end
|
55
|
+
|
56
|
+
def menu_create menu
|
57
|
+
# 微信不接受7bit escaped json(eg \uxxxx), 中文必须UTF-8编码, 这可能是个安全漏洞
|
58
|
+
# 如果是rails4.0以上使用 to_json 即可,否则使用 JSON.generate(menu,:ascii_only => false)
|
59
|
+
# 但以上试过之后仍是不行,在rails c中却是可以的,所以因该是被其它旧版本的json gem给重写了,所以采用以下方法
|
60
|
+
# 原因找到了是:
|
61
|
+
# 如果传的hash为ActiveSupport::HashWithIndifferentAccess 对像,因为用的是3.2.19,所以其编码可能不对
|
62
|
+
# 直接用 Hash对像的话,用的是ruby 2.0 的方法,做JSON.generate的时候对中文不会再编码成 \uxxxx的形式
|
63
|
+
#
|
64
|
+
json_str = JSON.generate(menu)#.gsub!(/\\u([0-9a-z]{4})/){|u| [$1.to_i(16)].pack('U')}
|
65
|
+
|
66
|
+
post("menu/create", json_str)
|
67
|
+
end
|
68
|
+
|
69
|
+
#返回媒体文件
|
70
|
+
def media media_id
|
71
|
+
get "media/get", params:{media_id: media_id}, base: FILE_BASE, as: :file
|
72
|
+
end
|
73
|
+
|
74
|
+
def media_create type, file
|
75
|
+
post "media/upload", {upload:{media: file}}, params:{type: type}, base: FILE_BASE
|
76
|
+
end
|
77
|
+
|
78
|
+
def custom_message_send message
|
79
|
+
post "message/custom/send", message.to_json, content_type: :json
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
protected
|
84
|
+
def get path, headers={}
|
85
|
+
with_access_token(headers[:params]){|params| client.get path, headers.merge(params: params)}
|
86
|
+
end
|
87
|
+
|
88
|
+
def post path, payload, headers = {}
|
89
|
+
with_access_token(headers[:params]){|params| client.post path, payload, headers.merge(params: params)}
|
90
|
+
end
|
91
|
+
|
92
|
+
def with_access_token params={}, tries=2
|
93
|
+
begin
|
94
|
+
params ||= {}
|
95
|
+
yield(params.merge(access_token: access_token.token))
|
96
|
+
rescue WeichatRails::AccessTokenExpiredError
|
97
|
+
access_token.refresh
|
98
|
+
retry unless (tries -= 1).zero?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module WeichatRails
|
2
|
+
module AutoGenerateSecretKey
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_create do
|
7
|
+
self.wechat_secret_key = generate_wechat_secret_key(generator: :urlsafe_base64,size: 16).downcase
|
8
|
+
self.app_token = generate_wechat_secret_key
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def generate_wechat_secret_key(options={})
|
16
|
+
# SecureRandom: hex, base64, random_bytes, urlsafe_base64, random_number, uuid
|
17
|
+
generator_method_type = options.delete(:generator).try(:to_sym) || :hex
|
18
|
+
generator_method = SecureRandom.method(generator_method_type)
|
19
|
+
token_size = options.delete(:size).try(:to_i) || 12
|
20
|
+
return generator_method.call if generator_method_type == :uuid
|
21
|
+
generator_method.call(token_size)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
module WeichatRails
|
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
|
+
#break data if (parse_as != :json || data["errcode"].blank?)
|
34
|
+
|
35
|
+
#如果返回的是json数据并且errcode有值,则会对errcode再做开关判断
|
36
|
+
|
37
|
+
case data["errcode"]
|
38
|
+
when 0 # for request didn't expect results
|
39
|
+
[true,data]
|
40
|
+
|
41
|
+
when 42001, 40014 #42001: access_token超时, 40014:不合法的access_token
|
42
|
+
raise AccessTokenExpiredError
|
43
|
+
|
44
|
+
when 1613189120 # for wx duokefu' msg code
|
45
|
+
[true,data]
|
46
|
+
|
47
|
+
else
|
48
|
+
raise ResponseError.new(data['errcode'], data['errmsg'])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
def parse_response response, as
|
55
|
+
content_type = response.headers[:content_type]
|
56
|
+
parse_as = {
|
57
|
+
/^application\/json/ => :json,
|
58
|
+
/^image\/.*/ => :file
|
59
|
+
}.inject([]){|memo, match| memo<<match[1] if content_type =~ match[0]; memo}.first || as || :text
|
60
|
+
|
61
|
+
case parse_as
|
62
|
+
when :file
|
63
|
+
file = Tempfile.new("tmp")
|
64
|
+
file.binmode
|
65
|
+
file.write(response.body)
|
66
|
+
file.close
|
67
|
+
data = file
|
68
|
+
|
69
|
+
when :json
|
70
|
+
data = JSON.parse(response.body)
|
71
|
+
|
72
|
+
else
|
73
|
+
data = response.body
|
74
|
+
end
|
75
|
+
|
76
|
+
return yield(parse_as, data)
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module WeichatRails
|
2
|
+
class Message
|
3
|
+
|
4
|
+
JSON_KEY_MAP = {
|
5
|
+
"ToUserName" => "touser",
|
6
|
+
"MediaId" => "media_id",
|
7
|
+
"ThumbMediaId" => "thumb_media_id"
|
8
|
+
}
|
9
|
+
|
10
|
+
class << self
|
11
|
+
def from_hash msg_hash
|
12
|
+
self.new(msg_hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to to_user
|
16
|
+
self.new(:ToUserName=>to_user, :CreateTime=>Time.now.to_i)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class ArticleBuilder
|
21
|
+
attr_reader :items
|
22
|
+
delegate :count, to: :items
|
23
|
+
def initialize
|
24
|
+
@items=Array.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def item title="title", description=nil, pic_url=nil, url=nil
|
28
|
+
items << {:Title=> title, :Description=> description, :PicUrl=> pic_url, :Url=> url}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :message_hash
|
33
|
+
|
34
|
+
def initialize(msg_hash)
|
35
|
+
@message_hash = msg_hash || {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](key)
|
39
|
+
message_hash[key]
|
40
|
+
end
|
41
|
+
|
42
|
+
def reply
|
43
|
+
Message.new( :ToUserName=>message_hash[:FromUserName], :FromUserName=>message_hash[:ToUserName], :CreateTime=>Time.now.to_i)
|
44
|
+
end
|
45
|
+
|
46
|
+
def as type
|
47
|
+
case type
|
48
|
+
when :text
|
49
|
+
message_hash[:Content]
|
50
|
+
|
51
|
+
when :image, :voice, :video
|
52
|
+
WeichatRails.api.media(message_hash[:MediaId])
|
53
|
+
|
54
|
+
when :location
|
55
|
+
message_hash.slice(:Location_X, :Location_Y, :Scale, :Label).inject({}){|results, value|
|
56
|
+
results[value[0].to_s.underscore.to_sym] = value[1]
|
57
|
+
results
|
58
|
+
}
|
59
|
+
else
|
60
|
+
raise "Don't know how to parse message as #{type}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#add wechat_user for load wechat_user in proc callback
|
65
|
+
#def wechat_user user
|
66
|
+
# update(:wechat_user=>user)
|
67
|
+
#end
|
68
|
+
def kefu msg_type
|
69
|
+
update(:MsgType => msg_type)
|
70
|
+
end
|
71
|
+
|
72
|
+
def to openid
|
73
|
+
update(:ToUserName=>openid)
|
74
|
+
end
|
75
|
+
|
76
|
+
def text content
|
77
|
+
update(:MsgType=>"text", :Content=>content)
|
78
|
+
end
|
79
|
+
|
80
|
+
def image media_id
|
81
|
+
update(:MsgType=>"image", :Image=>{:MediaId=>media_id})
|
82
|
+
end
|
83
|
+
|
84
|
+
def voice media_id
|
85
|
+
update(:MsgType=>"voice", :Voice=>{:MediaId=>media_id})
|
86
|
+
end
|
87
|
+
|
88
|
+
def video media_id, opts={}
|
89
|
+
video_fields = camelize_hash_keys({media_id: media_id}.merge(opts.slice(:title, :description)))
|
90
|
+
update(:MsgType=>"video", :Video=>video_fields)
|
91
|
+
end
|
92
|
+
|
93
|
+
def music thumb_media_id, music_url, opts={}
|
94
|
+
music_fields = camelize_hash_keys(opts.slice(:title, :description, :HQ_music_url).merge(music_url: music_url, thumb_media_id: thumb_media_id))
|
95
|
+
update(:MsgType=>"music", :Music=>music_fields)
|
96
|
+
end
|
97
|
+
|
98
|
+
def news collection, &block
|
99
|
+
if block_given?
|
100
|
+
article = ArticleBuilder.new
|
101
|
+
collection.each{|item| yield(article, item)}
|
102
|
+
items = article.items
|
103
|
+
else
|
104
|
+
items = collection.collect do |item|
|
105
|
+
camelize_hash_keys(item.symbolize_keys.slice(:title, :description, :pic_url, :url))
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
update(:MsgType=>"news", :ArticleCount=> items.count,
|
110
|
+
:Articles=> items.collect{|item| camelize_hash_keys(item)})
|
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").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
|
+
end
|
130
|
+
JSON.generate(json_hash)
|
131
|
+
end
|
132
|
+
|
133
|
+
def save_to! model_class
|
134
|
+
model = model_class.new(underscore_hash_keys(message_hash))
|
135
|
+
model.save!
|
136
|
+
return self
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def camelize_hash_keys hash
|
141
|
+
deep_recursive(hash){|key, value| [key.to_s.camelize.to_sym, value]}
|
142
|
+
end
|
143
|
+
|
144
|
+
def underscore_hash_keys hash
|
145
|
+
deep_recursive(hash){|key, value| [key.to_s.underscore.to_sym, value]}
|
146
|
+
end
|
147
|
+
|
148
|
+
def update fields={}
|
149
|
+
message_hash.merge!(fields)
|
150
|
+
return self
|
151
|
+
end
|
152
|
+
|
153
|
+
def deep_recursive hash, &block
|
154
|
+
hash.inject({}) do |memo, val|
|
155
|
+
key,value = *val
|
156
|
+
case value.class.name
|
157
|
+
when "Hash"
|
158
|
+
value = deep_recursive(value, &block)
|
159
|
+
when "Array"
|
160
|
+
value = value.collect{|item| item.is_a?(Hash) ? deep_recursive(item, &block) : item}
|
161
|
+
end
|
162
|
+
|
163
|
+
key,value = yield(key, value)
|
164
|
+
memo.merge!(key => value)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
@@ -0,0 +1,167 @@
|
|
1
|
+
module WeichatRails
|
2
|
+
module Responder
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
self.before_filter :init_wechat_or_token#, only: [:show, :create]
|
7
|
+
self.skip_before_filter :verify_authenticity_token
|
8
|
+
self.before_filter :verify_signature, only: [:show, :create]
|
9
|
+
#delegate :wehcat, to: :class
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_accessor :wechat, :token,:wechat_user
|
13
|
+
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
|
17
|
+
#定义整体的处理规则,对于不同的message_type,在block块中进行返回数据处理,
|
18
|
+
#on :text do |res,content|
|
19
|
+
#
|
20
|
+
#end
|
21
|
+
def on message_type, with: nil, respond: nil, &block
|
22
|
+
raise "Unknow message type" unless message_type.in? [:text, :image, :voice, :video, :location, :link, :event, :fallback]
|
23
|
+
config=respond.nil? ? {} : {:respond=>respond}
|
24
|
+
config.merge!(:proc=>block) if block_given?
|
25
|
+
|
26
|
+
if (with.present? && !message_type.in?([:text, :event]))
|
27
|
+
raise "Only text and event message can take :with parameters"
|
28
|
+
else
|
29
|
+
config.merge!(:with=>with) if with.present?
|
30
|
+
end
|
31
|
+
|
32
|
+
responders(message_type) << config
|
33
|
+
return config
|
34
|
+
end
|
35
|
+
|
36
|
+
def responders type
|
37
|
+
@responders ||= Hash.new
|
38
|
+
@responders[type] ||= Array.new
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
#指定的过滤方法,默认不使用指定的匹配方法,如为true,
|
43
|
+
def use_matcher mat = false
|
44
|
+
@use_matcher = mat
|
45
|
+
end
|
46
|
+
|
47
|
+
def use_matcher?
|
48
|
+
@use_matcher
|
49
|
+
end
|
50
|
+
|
51
|
+
#用于处理用户请求,该方法只关注用户信息类型及用户请求的内容或事件信息
|
52
|
+
#事件类型:subscribe(订阅)、unsubscribe(取消订阅),SCAN(关注后扫描),LOCATION(上报地理位置),CLICK(自定义菜单事件),VIEW(点击菜单跳转链接时的事件推送)
|
53
|
+
def responder_for message, &block
|
54
|
+
message_type = message[:MsgType].to_sym
|
55
|
+
responders = responders(message_type)
|
56
|
+
if use_matcher?
|
57
|
+
responder = {:method => :find_matcher}
|
58
|
+
case message_type
|
59
|
+
when :text
|
60
|
+
yield(responder,message[:Content])
|
61
|
+
when :event
|
62
|
+
yield(responder,message[:Event],message[:EventKey])
|
63
|
+
when :image,:voice,:shortvideo
|
64
|
+
yield(responder,'MEDIA',picurl: message[:PicUrl],media_id: message[:MediaId])
|
65
|
+
else
|
66
|
+
yield(responder)
|
67
|
+
end
|
68
|
+
else
|
69
|
+
case message_type
|
70
|
+
when :text
|
71
|
+
yield(* match_responders(responders, message[:Content]))
|
72
|
+
when :event
|
73
|
+
yield(* match_responders(responders, message[:Event]))
|
74
|
+
else
|
75
|
+
yield(responders.first)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
#该方法用于确定预先用on定义的返回信息(一个数组 [{with:,proc:,respond:},*args])做循环处理
|
83
|
+
#responds : 预先定义的某类返回信息,类型为[:text, :image, :voice, :video, :location, :link, :event, :fallback]中之一,为一个hash数组
|
84
|
+
#value : 请求内容
|
85
|
+
#优先返回具有with值的on规则
|
86
|
+
#注:使用on定义的规则只适用于直接在控制器中写死的规则,属于类级别,不适用于后台配置类型,如果responders[msg_type]为空的情况下不产生任何内容
|
87
|
+
def match_responders responders, value
|
88
|
+
mat = responders.inject({scoped:nil, general:nil}) do |matched, responder|
|
89
|
+
condition = responder[:with]
|
90
|
+
|
91
|
+
if condition.nil?
|
92
|
+
matched[:general] ||= [responder, value]
|
93
|
+
next matched
|
94
|
+
end
|
95
|
+
|
96
|
+
if condition.is_a? Regexp
|
97
|
+
matched[:scoped] ||= [responder] + $~.captures if(value =~ condition)
|
98
|
+
else
|
99
|
+
matched[:scoped] ||= [responder, value] if(value == condition)
|
100
|
+
end
|
101
|
+
matched
|
102
|
+
end
|
103
|
+
return mat[:scoped] || mat[:general]
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
#用于公众号开发者中心服务器配置中的URL配置,其中xxx为该公众号在开发者数据库中的唯一识别码,由auto_generate_secret_key 来产生
|
109
|
+
#如:http://m.pipgame.com/wx/xxxxx
|
110
|
+
def show
|
111
|
+
render :text => params[:echostr]
|
112
|
+
end
|
113
|
+
|
114
|
+
#创建两个message对像,原请求message及返回message信息,返回的message中传入当前公众号对像,用于回调函数中访问该公众号在数据库中的配置信息
|
115
|
+
def create
|
116
|
+
req = WeichatRails::Message.from_hash(params[:xml] || post_xml)
|
117
|
+
#add whchat_user for multiplay wechat user
|
118
|
+
#req.wechat_user(self.wechat_user)
|
119
|
+
#如果是多客服消息,直接返回
|
120
|
+
response = self.class.responder_for(req) do |responder, *args|
|
121
|
+
responder ||= self.class.responders(:fallback).first
|
122
|
+
|
123
|
+
#next method(responder[:method]).call(*args.unshift(req)) if (responder[:method])
|
124
|
+
next if responder.nil?
|
125
|
+
next find_matcher(*args.unshift(req)) if (responder[:method])
|
126
|
+
next req.reply.text responder[:respond] if (responder[:respond])
|
127
|
+
next responder[:proc].call(*args.unshift(req)) if (responder[:proc])
|
128
|
+
end
|
129
|
+
|
130
|
+
if response.respond_to? :to_xml
|
131
|
+
render xml: response.to_xml
|
132
|
+
else
|
133
|
+
render :nothing => true, :status => 200, :content_type => 'text/html'
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
def verify_signature
|
139
|
+
array = [self.token, params[:timestamp], params[:nonce]].compact.sort
|
140
|
+
render :text => "Forbidden", :status => 403 if params[:signature] != Digest::SHA1.hexdigest(array.join)
|
141
|
+
end
|
142
|
+
|
143
|
+
def post_xml
|
144
|
+
data = Hash.from_xml(reqest.raw_post)
|
145
|
+
HashWithIndifferentAccess.new_from_hash_copying_default data.fetch('xml', {})
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
#def wechat_model
|
150
|
+
#@wechat_model || WeichatRails.config.public_account_class.constantize
|
151
|
+
#end
|
152
|
+
|
153
|
+
#TODO init wechat , wechat_user,token from database
|
154
|
+
def init_wechat_or_token
|
155
|
+
raise NotImplementedError, "controller must implement init_wechat_or_token method!if you just need reply,you can init the token,otherwise you need to init whchat and token like: wechat = Wechat::Api.new(opts[:appid], opts[:secret], opts[:access_token]) token = opts[:token]
|
156
|
+
"
|
157
|
+
end
|
158
|
+
|
159
|
+
#need to inplement
|
160
|
+
#在这里处理不同值的内容匹配:subscribe(订阅)、unsubscribe(取消订阅),SCAN(关注后扫描),LOCATION(上报地理位置),CLICK(自定义菜单事件),VIEW(点击菜单跳转链接时的事件推送)
|
161
|
+
def find_matcher(req,keyword,event_key=nil)
|
162
|
+
raise NotImplementedError, "controller must implement find_matcher method,eg: get matcher content from database"
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "weichat_rails/api"
|
2
|
+
require "weichat_rails/auto_generate_secret_key"
|
3
|
+
|
4
|
+
module WeichatRails
|
5
|
+
|
6
|
+
autoload :Message, "weichat_rails/message"
|
7
|
+
autoload :Responder, "weichat_rails/responder"
|
8
|
+
|
9
|
+
class AccessTokenExpiredError < StandardError; end
|
10
|
+
|
11
|
+
class ResponseError < StandardError
|
12
|
+
attr_reader :error_code
|
13
|
+
def initialize(errcode, errmsg)
|
14
|
+
error_code = errcode
|
15
|
+
super "#{errmsg}(#{error_code})"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def config
|
22
|
+
@config || OpenStruct.new({wechat_secret_string: nil,wechat_token_string: nil})
|
23
|
+
end
|
24
|
+
|
25
|
+
#can configure the wechat_secret_string,wechat_token_string in weichat_rails_config.rb file
|
26
|
+
def configure
|
27
|
+
yield config if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
#DEFAULT_TOKEN_COLUMN_NAME = "wechat_token".freeze
|
33
|
+
#DEFAULT_WECHAT_SECRET_KEY = "wechat_secret_key".freeze
|
34
|
+
|
35
|
+
|
36
|
+
#def self.api
|
37
|
+
# # @api ||= WechatRails::Api.new(self.config.appid, self.config.secret, self.config.access_token)
|
38
|
+
#end
|
39
|
+
end
|
40
|
+
|
41
|
+
if defined? ActionController::Base
|
42
|
+
class ActionController::Base
|
43
|
+
def self.wechat_responder opts={}
|
44
|
+
self.send(:include, WeichatRails::Responder)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'weichat_rails/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "weichat_rails"
|
8
|
+
spec.version = WeichatRails::VERSION
|
9
|
+
spec.authors = ["javy_liu"]
|
10
|
+
spec.email = ["javy_liu@163.com"]
|
11
|
+
spec.summary = %q{weichat interface for rails based on weichat-rails}
|
12
|
+
spec.description = %q{weichat use for more than on public weichat account,base on the mysql,~> rails 3.2.16}
|
13
|
+
spec.homepage = "https://github.com/javyliu/weichat_rails"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
|
24
|
+
spec.add_dependency "rails", "~> 3.2.14"
|
25
|
+
spec.add_dependency "nokogiri", '>=1.6.0'
|
26
|
+
spec.add_dependency 'rest-client'
|
27
|
+
spec.add_dependency 'dalli'
|
28
|
+
|
29
|
+
end
|
metadata
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: weichat_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- javy_liu
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-07-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.5'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.2.14
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.2.14
|
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.6.0
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.6.0
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rest-client
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: dalli
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
description: weichat use for more than on public weichat account,base on the mysql,~>
|
98
|
+
rails 3.2.16
|
99
|
+
email:
|
100
|
+
- javy_liu@163.com
|
101
|
+
executables: []
|
102
|
+
extensions: []
|
103
|
+
extra_rdoc_files: []
|
104
|
+
files:
|
105
|
+
- ".gitignore"
|
106
|
+
- Gemfile
|
107
|
+
- LICENSE.txt
|
108
|
+
- README.md
|
109
|
+
- Rakefile
|
110
|
+
- lib/weichat_rails.rb
|
111
|
+
- lib/weichat_rails/access_token.rb
|
112
|
+
- lib/weichat_rails/api.rb
|
113
|
+
- lib/weichat_rails/auto_generate_secret_key.rb
|
114
|
+
- lib/weichat_rails/client.rb
|
115
|
+
- lib/weichat_rails/message.rb
|
116
|
+
- lib/weichat_rails/responder.rb
|
117
|
+
- lib/weichat_rails/version.rb
|
118
|
+
- weichat_rails.gemspec
|
119
|
+
homepage: https://github.com/javyliu/weichat_rails
|
120
|
+
licenses:
|
121
|
+
- MIT
|
122
|
+
metadata: {}
|
123
|
+
post_install_message:
|
124
|
+
rdoc_options: []
|
125
|
+
require_paths:
|
126
|
+
- lib
|
127
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
requirements: []
|
138
|
+
rubyforge_project:
|
139
|
+
rubygems_version: 2.4.6
|
140
|
+
signing_key:
|
141
|
+
specification_version: 4
|
142
|
+
summary: weichat interface for rails based on weichat-rails
|
143
|
+
test_files: []
|
144
|
+
has_rdoc:
|