wechat-rb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.rspec +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +104 -0
- data/Rakefile +2 -0
- data/lib/wechat.rb +56 -0
- data/lib/wechat/access_token.rb +32 -0
- data/lib/wechat/data_object.rb +53 -0
- data/lib/wechat/data_objects/broadcast.rb +102 -0
- data/lib/wechat/data_objects/follower.rb +40 -0
- data/lib/wechat/data_objects/media.rb +49 -0
- data/lib/wechat/data_objects/menu.rb +49 -0
- data/lib/wechat/data_objects/message.rb +65 -0
- data/lib/wechat/data_objects/qrcode.rb +39 -0
- data/lib/wechat/error.rb +24 -0
- data/lib/wechat/local_resource.rb +36 -0
- data/lib/wechat/request.rb +48 -0
- data/lib/wechat/response.rb +40 -0
- data/lib/wechat/version.rb +3 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/wechat/data_object_spec.rb +40 -0
- data/spec/wechat/request_spec.rb +23 -0
- data/spec/wechat/response_spec.rb +6 -0
- data/spec/wechat_spec.rb +25 -0
- data/wechat.gemspec +23 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 61ec7a5b42fe03ce985fca363b29374a69b051d4
|
4
|
+
data.tar.gz: 47a37f39ef34d8435f287b89ffd656ccdb3adae8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 674382d229096c6b2a711187413cce1394cec05048882c2fbceca14ec7192a3a9950306895431e42c97e5a7cd4eb545b55d670d39c3092e02c4e4e537aae0e06
|
7
|
+
data.tar.gz: 43a1e9a64c2cd1363f267de614bdcd99615778eecdf3282deabb5971ce370014636fc499ad7de1e7e9bc5cbb2bf933a2d3b1a2233da7547d4be4d36811a08190
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
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
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Eshaam Rabaney
|
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,104 @@
|
|
1
|
+
# wechat-rb
|
2
|
+
|
3
|
+
Simple Ruby wrapper for the Wechat Admin API.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
$ gem install wechat-rb
|
9
|
+
|
10
|
+
|
11
|
+
## Wechat API Hint
|
12
|
+
|
13
|
+
This wrapper tries to implement all available methods of the Wechat Admin API in a
|
14
|
+
Ruby-like fashion. However, the Wechat API lacks a decent amount of methods that
|
15
|
+
you expect an API to provide.
|
16
|
+
Thus, if methods are missing or a certain implementation
|
17
|
+
style was choosen it is most likely due to the inconsistency of the API itself.
|
18
|
+
Feel free to get in touch or submit a pull request if you encounter any problems.
|
19
|
+
|
20
|
+
|
21
|
+
Must-known facts about the Wechat API:
|
22
|
+
|
23
|
+
* Wechat requires multimedia files such as images and videos to be uploaded to their servers is some cases. To help make things easier, this wrapper will upload the multimedia files on the fly for you and you can even pass external url for upload.
|
24
|
+
* Return values differ from method to method due to the way the Wechat API is implemented.
|
25
|
+
Thus, a Hash as a return value or an Array of Hashes was choosen as the global return object. Basically it is a parsed JSON response.
|
26
|
+
* Please refer to the Wechat API documentation for detailed information on parameters, return values or error codes.
|
27
|
+
|
28
|
+
|
29
|
+
## Configuration and Setup
|
30
|
+
### Authentication
|
31
|
+
|
32
|
+
Authenticate with the api credentials provided in your Wechat Admin Account.
|
33
|
+
|
34
|
+
Wechat.configure do |c|
|
35
|
+
c.api_appid = 'my_appid'
|
36
|
+
c.api_appsecret = 'my_secret'
|
37
|
+
# OPTIONAL, defaults to https://api.weixin.qq.com/cgi-bin
|
38
|
+
c.api_endpoint = 'https://api.weixin.qq.com/cgi-bin'
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
## Interacting with the API
|
43
|
+
|
44
|
+
You can interact with the API on the provided data objects
|
45
|
+
|
46
|
+
#### Message
|
47
|
+
|
48
|
+
# Send a custom Text message
|
49
|
+
Wechat::Message.send({to: 'oRVIRuNafMJvn-eEwd0r9nv5HhqE'}, {text: "Hello World"})
|
50
|
+
|
51
|
+
# Send a custom Image message
|
52
|
+
Wechat::Message.send({to: 'oRVIRuNafMJvn-eEwd0r9nv5HhqE'}, {image: "http://example.com/image.jpg"})
|
53
|
+
|
54
|
+
# Send a custom News message
|
55
|
+
Wechat::Message.send({to: 'oRVIRuNafMJvn-eEwd0r9nv5HhqE'}, {news: [{title: 'My title', description: 'My descript', url: 'www.example.com', pic_url: 'http://example.com/image.jpg'}]})
|
56
|
+
|
57
|
+
|
58
|
+
#### Broadcast
|
59
|
+
|
60
|
+
# Send a News Message Broadcast to all Followers
|
61
|
+
Wechat::Broadcast.send(news: [{title: 'My title', description: 'My descript', url: 'www.example.com', pic_url: 'http://example.com/image.jpg'}])
|
62
|
+
|
63
|
+
# Send a News Message Broadcast to select Followers
|
64
|
+
Wechat::Broadcast.send(news: [{title: 'My title', description: 'My descript', url: 'www.example.com', pic_url: 'http://example.com/image.jpg'}], ['oRVIRuNafMJvn-eEwd0r9nv5HhqE'])
|
65
|
+
|
66
|
+
|
67
|
+
#### Follower
|
68
|
+
|
69
|
+
# Get list of Followers
|
70
|
+
Wechat::Follower.list
|
71
|
+
|
72
|
+
# Get follower profile
|
73
|
+
Wechat::Follower.show('oRVIRuNafMJvn-eEwd0r9nv5HhqE')
|
74
|
+
|
75
|
+
|
76
|
+
#### Menu
|
77
|
+
|
78
|
+
# Create a Custom menu
|
79
|
+
Wechat::Menu.create({button: [{type: 'click',name: 'News', key: 'news_today'}]})
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
## Todo
|
84
|
+
* More tests :)
|
85
|
+
* Implement Callback Messages
|
86
|
+
* Implement Better Error Handling and Access Token
|
87
|
+
|
88
|
+
## Contributing
|
89
|
+
|
90
|
+
1. Fork it
|
91
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
92
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
93
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
94
|
+
5. Create a new Pull Request
|
95
|
+
|
96
|
+
|
97
|
+
## Ruby Versions
|
98
|
+
|
99
|
+
This gem was developed and tested with versions 2.0.0
|
100
|
+
|
101
|
+
|
102
|
+
## Copyright
|
103
|
+
|
104
|
+
Copyright (c) 2014 Eshaam Rabaney. See LICENSE.txt for details.
|
data/Rakefile
ADDED
data/lib/wechat.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require "base64"
|
2
|
+
require 'json'
|
3
|
+
require 'rest_client'
|
4
|
+
require 'uri'
|
5
|
+
require 'open-uri'
|
6
|
+
require "multi_xml"
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
require "wechat/local_resource"
|
11
|
+
require "wechat/error"
|
12
|
+
require 'wechat/access_token'
|
13
|
+
require 'wechat/request'
|
14
|
+
require 'wechat/response'
|
15
|
+
require 'wechat/data_object'
|
16
|
+
require 'wechat/data_objects/broadcast'
|
17
|
+
require 'wechat/data_objects/follower'
|
18
|
+
require 'wechat/data_objects/media'
|
19
|
+
require 'wechat/data_objects/menu'
|
20
|
+
require 'wechat/data_objects/message'
|
21
|
+
require 'wechat/data_objects/qrcode'
|
22
|
+
require "wechat/version"
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
module Wechat
|
27
|
+
class << self
|
28
|
+
|
29
|
+
# @!attribute api_endpoint
|
30
|
+
# @return [String] Base URL for WeChat API. default: https://api.weixin.qq.com/cgi-bin
|
31
|
+
# @!attribute file_endpoint
|
32
|
+
# @return [String] Base URL for WeChat Media API. default: http://file.api.weixin.qq.com/cgi-bin
|
33
|
+
# @!attribute api_appid
|
34
|
+
# @return [String] Obtained in WeChat Account
|
35
|
+
# @!attribute api_appsecret
|
36
|
+
# @return [String] Obtained in WeChat Account
|
37
|
+
|
38
|
+
attr_accessor :api_endpoint, :file_endpoint, :api_appid, :api_appsecret,:access_token
|
39
|
+
|
40
|
+
|
41
|
+
|
42
|
+
def api_endpoint
|
43
|
+
@api_endpoint ||= 'https://api.weixin.qq.com/cgi-bin'
|
44
|
+
end
|
45
|
+
|
46
|
+
def file_endpoint
|
47
|
+
@file_endpoint ||= 'http://file.api.weixin.qq.com/cgi-bin'
|
48
|
+
end
|
49
|
+
|
50
|
+
# Set configuration options using a block
|
51
|
+
def configure
|
52
|
+
yield self
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Wechat
|
2
|
+
class AccessToken
|
3
|
+
attr_accessor :appid, :secret
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
raise 'Wechat.api_appid not set' if Wechat.api_appid.nil?
|
7
|
+
raise 'Wechat.api_appsecret not set' if Wechat.api_appsecret.nil?
|
8
|
+
|
9
|
+
@appid = Wechat.api_appid
|
10
|
+
@secret = Wechat.api_appsecret
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
def token
|
15
|
+
Wechat.access_token ||= self.refresh
|
16
|
+
end
|
17
|
+
|
18
|
+
def refresh
|
19
|
+
url = "#{Wechat.api_endpoint}/token"
|
20
|
+
data = RestClient.get(url, params:{grant_type: "client_credential", appid: @appid, secret: @secret})
|
21
|
+
return valid_token(data)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def valid_token token_data
|
26
|
+
rep = JSON.parse(token_data)
|
27
|
+
raise "Response didn't have access_token" if rep['access_token'].empty?
|
28
|
+
return rep['access_token']
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
class DataObject
|
4
|
+
class << self
|
5
|
+
|
6
|
+
# Make a HTTP GET request
|
7
|
+
#
|
8
|
+
# @param method_name [String] The path, relative to Wechat.api_endpoint
|
9
|
+
# @param params [Hash] custom params hash
|
10
|
+
# @return [Hash]
|
11
|
+
def get(method_name, params)
|
12
|
+
|
13
|
+
if params.empty?
|
14
|
+
self.new.request 'get', method_name, params
|
15
|
+
else
|
16
|
+
self.new.request 'get', [method_name + '?', parameterize_params(params)].join("&"), {}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Make a HTTP POST request
|
21
|
+
#
|
22
|
+
# @param method_name [String] The path, relative to Wechat.api_endpoint
|
23
|
+
# @param params [Hash] custom params hash
|
24
|
+
# @return [Hash]
|
25
|
+
def post(method_name, params)
|
26
|
+
self.new.request 'post', method_name, params
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
# Custom Parameterizer for Wechat
|
32
|
+
#
|
33
|
+
# @param params [Hash] custom params hash
|
34
|
+
# @return [String] key => value is returned as key=value
|
35
|
+
def parameterize_params(params)
|
36
|
+
params.inject(""){|string, (k, v)| string << "#{k}=#{v}"; string << "&"; string}[0..-2]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# Make a HTTP request
|
41
|
+
#
|
42
|
+
# @param http_verb [String] Http method
|
43
|
+
# @param method_name [String] The path, relative to Wechat.api_endpoint
|
44
|
+
# @param params [Hash] custom params hash
|
45
|
+
# @return [Hash]
|
46
|
+
def request(http_verb, method_name, params)
|
47
|
+
response = Wechat::Request.new(http_verb, method_name, params).send_request
|
48
|
+
#hashiefy(response)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Methods for the Advanced Broadcast Interface
|
4
|
+
# http://admin.wechat.com/wiki/index.php?title=Advanced_Broadcast_Interface
|
5
|
+
#
|
6
|
+
class Broadcast < DataObject
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Send a broadcast.
|
10
|
+
#
|
11
|
+
# @param message [Hash] internal id of key field
|
12
|
+
# @param key_value [Integer, String] value of interal id field
|
13
|
+
# @param params [Hash] Contact information to create
|
14
|
+
# @return [Hash] internal id of the contact
|
15
|
+
# @example
|
16
|
+
# Wechat::Broadcast.send(news: articles)
|
17
|
+
|
18
|
+
|
19
|
+
def send(message = {}, followers = [])
|
20
|
+
|
21
|
+
to_followers = generate_send_list(followers)
|
22
|
+
send_message = generate_content(message)
|
23
|
+
b = {}
|
24
|
+
b.merge!(to_followers)
|
25
|
+
b.merge!(send_message)
|
26
|
+
broadcast_message = JSON.generate(b)
|
27
|
+
|
28
|
+
|
29
|
+
post "message/mass/send", broadcast_message
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
#private
|
34
|
+
def generate_send_list(followers)
|
35
|
+
|
36
|
+
if followers.empty?
|
37
|
+
|
38
|
+
all_followers = Wechat::Follower.list
|
39
|
+
send_followers = {touser: all_followers["data"]["openid"]}
|
40
|
+
|
41
|
+
else
|
42
|
+
send_followers = {touser: followers}
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def generate_content(message)
|
49
|
+
type = message.keys[0]
|
50
|
+
body = message.values[0]
|
51
|
+
|
52
|
+
case type
|
53
|
+
|
54
|
+
when :text
|
55
|
+
content = {text: {content: body},msgtype: "text"}
|
56
|
+
when :audio
|
57
|
+
content = {voice: {media_id: upload_media_for_broadcast('voice',body)},msgtype: "voice"}
|
58
|
+
when :image
|
59
|
+
content = {image: {media_id: upload_media_for_broadcast('image',body)},msgtype: "image"}
|
60
|
+
|
61
|
+
when :news
|
62
|
+
items = []
|
63
|
+
|
64
|
+
body.each do |article|
|
65
|
+
|
66
|
+
new_article = {
|
67
|
+
thumb_media_id: upload_media_for_broadcast('image',article[:picurl]),
|
68
|
+
title: article[:title],
|
69
|
+
content_source_url: article[:url],
|
70
|
+
content: article[:url],
|
71
|
+
digest: article[:description],
|
72
|
+
}
|
73
|
+
|
74
|
+
items << new_article
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
news_upload_result = upload_news_articles_for_broadcast(items)
|
80
|
+
|
81
|
+
content = {mpnews: {media_id: news_upload_result["media_id"]},msgtype: "mpnews"}
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
def upload_media_for_broadcast(type,item)
|
90
|
+
result = Wechat::Media.upload(type,item)
|
91
|
+
return result['media_id']
|
92
|
+
end
|
93
|
+
|
94
|
+
def upload_news_articles_for_broadcast(articles)
|
95
|
+
post "media/uploadnews", JSON.generate({articles: articles})
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Methods for the Followers
|
4
|
+
|
5
|
+
class Follower < DataObject
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Obtain list of followers
|
9
|
+
# => http://admin.wechat.com/wiki/index.php?title=Follower_List
|
10
|
+
|
11
|
+
|
12
|
+
# @return [Hash] List of OpenIDs
|
13
|
+
# @example
|
14
|
+
# Wechat::Follower.list
|
15
|
+
|
16
|
+
|
17
|
+
def list
|
18
|
+
get("user/get", {})
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# Get a follower profile for a given OpenID
|
24
|
+
# => http://admin.wechat.com/wiki/index.php?title=User_Profile
|
25
|
+
|
26
|
+
# @param openid [String] the openid of follower
|
27
|
+
# @return [Hash] result data
|
28
|
+
# @example
|
29
|
+
# Wechat::Follower.show('oRVIRuBVh0Dh91HAzauOW-WiZ3g0')
|
30
|
+
|
31
|
+
|
32
|
+
def show(openid)
|
33
|
+
get("user/info", {openid: openid})
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Methods for transfering media files
|
4
|
+
# => http://admin.wechat.com/wiki/index.php?title=Transferring_Multimedia_Files
|
5
|
+
#
|
6
|
+
class Media < DataObject
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# Upload files to wechat media api
|
10
|
+
|
11
|
+
# @param type [String] the type of media being uploaded. Either 'image','audio','video','voice'
|
12
|
+
# @param path [String] the relative path to a file or pass URL
|
13
|
+
# @return [Hash] result data
|
14
|
+
# @example
|
15
|
+
# Wechat::Media.upload('image','/path/to/my/image.jpg')
|
16
|
+
# Wechat::Media.upload('image','http://example.com/image.jpg')
|
17
|
+
|
18
|
+
|
19
|
+
def upload(type, path)
|
20
|
+
|
21
|
+
if path.include?'http'
|
22
|
+
# We create a local representation of the remote resource
|
23
|
+
# local_resource = LocalResource.new URI.parse(path)
|
24
|
+
local_resource = LocalResource.new(URI.parse(path))
|
25
|
+
# We have a copy of the remote file for processing
|
26
|
+
local_copy_of_remote_file = local_resource.file
|
27
|
+
|
28
|
+
|
29
|
+
path = local_copy_of_remote_file.path
|
30
|
+
end
|
31
|
+
|
32
|
+
file = File.new(path,'rb')
|
33
|
+
post "media/upload", {media: file, type: type}
|
34
|
+
end
|
35
|
+
|
36
|
+
# Download files from wechat media api
|
37
|
+
|
38
|
+
# @param media_id [String] the media_id of the file to be downloaded
|
39
|
+
# @return [String] path to downloaded file
|
40
|
+
|
41
|
+
def download(media_id)
|
42
|
+
get "media/get", {media_id: media_id}
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Methods for the Custom Menu API
|
4
|
+
|
5
|
+
class Menu < DataObject
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Get the menu currently live if one exists
|
9
|
+
# http://admin.wechat.com/wiki/index.php?title=Query
|
10
|
+
|
11
|
+
# @return [Hash] result data
|
12
|
+
# @example
|
13
|
+
# Wechat::Menu.query
|
14
|
+
|
15
|
+
|
16
|
+
def query
|
17
|
+
get("menu/get", {})
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# Create a custom menu
|
22
|
+
|
23
|
+
# @param menu [Hash]
|
24
|
+
# @return [Hash] result data
|
25
|
+
# @example
|
26
|
+
# Wechat::Menu.create({button: [{type: 'click',name: 'News', key: 'news_today'}]})
|
27
|
+
|
28
|
+
|
29
|
+
def create(menu)
|
30
|
+
post("menu/create", JSON.generate(menu))
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
# Delete the menu currently live if one exists
|
36
|
+
# http://admin.wechat.com/wiki/index.php?title=Delete
|
37
|
+
|
38
|
+
# @return [Hash] result data
|
39
|
+
# @example
|
40
|
+
# Wechat::Menu.delete
|
41
|
+
|
42
|
+
def delete
|
43
|
+
get("menu/delete", {})
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Methods for the Contact API
|
4
|
+
#
|
5
|
+
#
|
6
|
+
class Message < DataObject
|
7
|
+
class << self
|
8
|
+
|
9
|
+
attr_reader :message_hash
|
10
|
+
|
11
|
+
|
12
|
+
def from_hash(post_xml)
|
13
|
+
@message_hash = MultiXml.parse(post_xml)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
def send(to='', message = {})
|
19
|
+
processed_message = process_message(to,message)
|
20
|
+
post( "message/custom/send", processed_message)
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def verify_signature(token)
|
26
|
+
array = [token, params[:timestamp], params[:nonce]].compact.sort
|
27
|
+
render :text => "Forbidden", :status => 403 if params[:signature] != Digest::SHA1.hexdigest(array.join)
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
|
32
|
+
#private
|
33
|
+
|
34
|
+
|
35
|
+
def process_message(to, message)
|
36
|
+
@new_message = {
|
37
|
+
:touser=>to
|
38
|
+
}
|
39
|
+
|
40
|
+
type = message.keys[0]
|
41
|
+
body = message.values[0]
|
42
|
+
|
43
|
+
case type
|
44
|
+
|
45
|
+
when :text
|
46
|
+
@new_message.merge!(:msgtype=>"text", :text=>{:content=>body})
|
47
|
+
when :image
|
48
|
+
@new_message.merge!(:msgtype=>"image", :image=>{:media_id=>body})
|
49
|
+
when :audio
|
50
|
+
@new_message.merge!(:msgtype=>"voice", :voice=>{:media_id=>body})
|
51
|
+
when :video
|
52
|
+
@new_message.merge!(:msgtype=>"video", :video=>{:media_id=>body})
|
53
|
+
when :news
|
54
|
+
@new_message.merge!(:msgtype=>"news", :news=>{:articles=>body})
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Methods for Generating Parametric QR Code
|
4
|
+
# http://admin.wechat.com/wiki/index.php?title=Generating_Parametric_QR_Code
|
5
|
+
|
6
|
+
|
7
|
+
class Qrcode < DataObject
|
8
|
+
class << self
|
9
|
+
|
10
|
+
# Create a qrcode
|
11
|
+
|
12
|
+
# @return [Hash] result data
|
13
|
+
# @example
|
14
|
+
# Wechat::Qrcode.create
|
15
|
+
|
16
|
+
|
17
|
+
def create
|
18
|
+
options = {expire_seconds: 1800,action_name: 'QR_SCENE',
|
19
|
+
action_info: {scene: {scene_id: 2.times.map{ 10 + Random.rand(20) }.join }}}
|
20
|
+
post("qrcode/create", JSON.generate(options))
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
# Get the QR code image url from a ticket number
|
26
|
+
|
27
|
+
# @param ticket [String] the ticket number of qr code obtained from create
|
28
|
+
# @return [String] QR code image url
|
29
|
+
# @example
|
30
|
+
# Wechat::Qrcode.show('gQEc8DoAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL0xFUG15QWJtMHBhci1FMV9uMjJCAAIEL3gHVAMECAcAAA==')
|
31
|
+
|
32
|
+
def show(ticket)
|
33
|
+
return "https://mp.weixin.qq.com/cgi-bin/showqrcode/cgi-bin/showqrcode?ticket=#{CGI.escape(ticket)}"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/wechat/error.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
# Custom error class for rescuing from Wechat errors
|
4
|
+
class Error < StandardError
|
5
|
+
def initialize(code)
|
6
|
+
@code = code
|
7
|
+
|
8
|
+
super(build_error_message)
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_error_message
|
12
|
+
"HTTP-Code: #{@code}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Raised when Wechat returns a 400 HTTP status code
|
17
|
+
class BadRequest < Error; end
|
18
|
+
|
19
|
+
# Raised when Wechat returns a 401 HTTP status code
|
20
|
+
class Unauthorized < Error; end
|
21
|
+
|
22
|
+
# Raised when Wechat returns a 500 HTTP status code
|
23
|
+
class InternalServerError < Error; end
|
24
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
class LocalResource
|
4
|
+
attr_reader :uri
|
5
|
+
|
6
|
+
def initialize(uri)
|
7
|
+
@uri = uri
|
8
|
+
end
|
9
|
+
|
10
|
+
def file
|
11
|
+
@file ||= Tempfile.new(tmp_filename, tmp_folder, encoding: encoding).tap do |f|
|
12
|
+
io.rewind
|
13
|
+
f.write(io.read)
|
14
|
+
f.close
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def io
|
19
|
+
@io ||= uri.open
|
20
|
+
end
|
21
|
+
|
22
|
+
def encoding
|
23
|
+
io.rewind
|
24
|
+
io.read.encoding
|
25
|
+
end
|
26
|
+
|
27
|
+
def tmp_filename
|
28
|
+
File.basename(uri.path)
|
29
|
+
end
|
30
|
+
|
31
|
+
def tmp_folder
|
32
|
+
'/tmp'
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
class Request
|
4
|
+
attr_accessor :http_verb, :path, :params
|
5
|
+
|
6
|
+
def initialize(http_verb, path, params = {})
|
7
|
+
self.path = path
|
8
|
+
self.http_verb = http_verb
|
9
|
+
self.params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def send_request
|
13
|
+
case http_verb.to_sym
|
14
|
+
when :post
|
15
|
+
RestClient.post(wechat_uri, params) do |response, request, result, &block|
|
16
|
+
Wechat::Response.new(response).result
|
17
|
+
end
|
18
|
+
else
|
19
|
+
RestClient.get(wechat_uri, :content_type => :json) do |response, request, result, &block|
|
20
|
+
Wechat::Response.new(response).result
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def access_token
|
26
|
+
Wechat::AccessToken.new.token
|
27
|
+
end
|
28
|
+
|
29
|
+
def wechat_uri
|
30
|
+
if (@path.include?'media/upload') || (@path.include?'media/get')
|
31
|
+
base = Wechat.file_endpoint
|
32
|
+
else
|
33
|
+
base = Wechat.api_endpoint
|
34
|
+
end
|
35
|
+
[base, @path].join('/') + append_access_token
|
36
|
+
end
|
37
|
+
|
38
|
+
def append_access_token
|
39
|
+
if @path.include?'?'
|
40
|
+
return "&access_token=#{access_token}"
|
41
|
+
else
|
42
|
+
return "?&access_token=#{access_token}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Wechat
|
2
|
+
|
3
|
+
class Response
|
4
|
+
attr_accessor :code, :data, :parse_as
|
5
|
+
|
6
|
+
def initialize(response)
|
7
|
+
type = response.headers[:content_type]
|
8
|
+
|
9
|
+
case type
|
10
|
+
when /image|audio|video/
|
11
|
+
self.parse_as = :file
|
12
|
+
else
|
13
|
+
self.parse_as = :json
|
14
|
+
end
|
15
|
+
self.data = response
|
16
|
+
self.code = response.code
|
17
|
+
end
|
18
|
+
|
19
|
+
def result
|
20
|
+
if code == 200
|
21
|
+
case parse_as
|
22
|
+
when :file
|
23
|
+
file = Tempfile.new(["tmp", ".#{data.headers[:content_type].split('/')[1] }"])
|
24
|
+
file.binmode
|
25
|
+
file.write(data.body)
|
26
|
+
file.close
|
27
|
+
file.path
|
28
|
+
when :json
|
29
|
+
JSON.parse(data)
|
30
|
+
end
|
31
|
+
|
32
|
+
elsif !code.nil? && code == 401
|
33
|
+
raise Wechat::Unauthorized.new(code)
|
34
|
+
else
|
35
|
+
raise Wechat::BadRequest.new(code)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'wechat'
|
2
|
+
require 'rspec'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
5
|
+
WebMock.disable_net_connect!
|
6
|
+
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
|
10
|
+
config.before(:all) { stub_wechat_authentication! }
|
11
|
+
end
|
12
|
+
|
13
|
+
def stub_wechat_authentication!
|
14
|
+
Wechat.configure do |config|
|
15
|
+
config.api_appid = "my_username"
|
16
|
+
config.api_appsecret = "my_password"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wechat::DataObject do
|
4
|
+
context "as a class" do
|
5
|
+
|
6
|
+
describe '.get' do
|
7
|
+
it "delegates to the instance request method" do
|
8
|
+
Wechat::DataObject.any_instance.should_receive(:request).with('get', 'test_method', {}).and_return(nil)
|
9
|
+
Wechat::DataObject.get('test_method', {})
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '.post' do
|
16
|
+
it "delegates to the instance request method" do
|
17
|
+
Wechat::DataObject.any_instance.should_receive(:request).with('post', 'test_method', {}).and_return(nil)
|
18
|
+
Wechat::DataObject.post('test_method', {})
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
describe '.parameterize_params' do
|
25
|
+
it "converts hash to params string" do
|
26
|
+
params = {"a" => 1, "b" => 2, "c" => 3}
|
27
|
+
expect(Wechat::DataObject.parameterize_params(params)).to eq("a=1&b=2&c=3")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "as an instance" do
|
33
|
+
let(:data_object) { Wechat::DataObject.new }
|
34
|
+
|
35
|
+
it "provides a simpel #request that delegates to Wechat::Request" do
|
36
|
+
Wechat::Request.any_instance.should_receive(:send_request).and_return(nil)
|
37
|
+
data_object.request('get', 'test_method', {})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wechat::Request do
|
4
|
+
let(:client) { Wechat::Client.new }
|
5
|
+
let(:request) { Wechat::Request.new('get', 'some-path', {:a => 1}) }
|
6
|
+
|
7
|
+
describe '#initialize' do
|
8
|
+
it 'sets client, path, http_verb and params attributes on initialize' do
|
9
|
+
expect(request.http_verb).to eq('get')
|
10
|
+
expect(request.path).to eq("some-path")
|
11
|
+
expect(request.params).to eq({:a => 1})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
describe '#client' do
|
16
|
+
it "provides a simple client accessor method" do
|
17
|
+
expect(request.client).to be_a(Wechat::Client)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
end
|
data/spec/wechat_spec.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Wechat do
|
4
|
+
|
5
|
+
describe ".configure" do
|
6
|
+
[:api_endpoint, :api_appid, :api_appsecret,:file_endpoint].each do |key|
|
7
|
+
it "sets the #{key.to_s.gsub('_', ' ')}" do
|
8
|
+
Wechat.configure do |config|
|
9
|
+
config.send("#{key}=", key)
|
10
|
+
end
|
11
|
+
expect(Wechat.instance_variable_get(:"@#{key}")).to eq key
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
describe ".api_endpoint getter" do
|
18
|
+
it "returns specific url as default value" do
|
19
|
+
Wechat.api_endpoint = nil
|
20
|
+
Wechat.api_endpoint.should eq('https://api.weixin.qq.com/cgi-bin')
|
21
|
+
Wechat.file_endpoint = nil
|
22
|
+
Wechat.file_endpoint.should eq('http://file.api.weixin.qq.com/cgi-bin')
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/wechat.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'wechat/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wechat-rb"
|
8
|
+
spec.version = Wechat::VERSION
|
9
|
+
spec.authors = ["Eshaam Rabaney"]
|
10
|
+
spec.email = ["eshaam.rabaney@gmail.com"]
|
11
|
+
spec.summary = %q{A Ruby library for interacting with the echat Official Accounts API.}
|
12
|
+
spec.description = %q{Easy to use ruby library for Wechat Official Accounts.}
|
13
|
+
spec.homepage = "https://github.com/eshaam/wechat-rb"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
spec.add_dependency "rest-client"
|
20
|
+
spec.add_dependency "multi_xml"
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wechat-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eshaam Rabaney
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-09-03 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: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: multi_xml
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
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: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Easy to use ruby library for Wechat Official Accounts.
|
70
|
+
email:
|
71
|
+
- eshaam.rabaney@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".gitignore"
|
77
|
+
- ".rspec"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- lib/wechat.rb
|
83
|
+
- lib/wechat/access_token.rb
|
84
|
+
- lib/wechat/data_object.rb
|
85
|
+
- lib/wechat/data_objects/broadcast.rb
|
86
|
+
- lib/wechat/data_objects/follower.rb
|
87
|
+
- lib/wechat/data_objects/media.rb
|
88
|
+
- lib/wechat/data_objects/menu.rb
|
89
|
+
- lib/wechat/data_objects/message.rb
|
90
|
+
- lib/wechat/data_objects/qrcode.rb
|
91
|
+
- lib/wechat/error.rb
|
92
|
+
- lib/wechat/local_resource.rb
|
93
|
+
- lib/wechat/request.rb
|
94
|
+
- lib/wechat/response.rb
|
95
|
+
- lib/wechat/version.rb
|
96
|
+
- spec/spec_helper.rb
|
97
|
+
- spec/wechat/data_object_spec.rb
|
98
|
+
- spec/wechat/request_spec.rb
|
99
|
+
- spec/wechat/response_spec.rb
|
100
|
+
- spec/wechat_spec.rb
|
101
|
+
- wechat.gemspec
|
102
|
+
homepage: https://github.com/eshaam/wechat-rb
|
103
|
+
licenses:
|
104
|
+
- MIT
|
105
|
+
metadata: {}
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
|
+
requirements:
|
117
|
+
- - ">="
|
118
|
+
- !ruby/object:Gem::Version
|
119
|
+
version: '0'
|
120
|
+
requirements: []
|
121
|
+
rubyforge_project:
|
122
|
+
rubygems_version: 2.2.2
|
123
|
+
signing_key:
|
124
|
+
specification_version: 4
|
125
|
+
summary: A Ruby library for interacting with the echat Official Accounts API.
|
126
|
+
test_files:
|
127
|
+
- spec/spec_helper.rb
|
128
|
+
- spec/wechat/data_object_spec.rb
|
129
|
+
- spec/wechat/request_spec.rb
|
130
|
+
- spec/wechat/response_spec.rb
|
131
|
+
- spec/wechat_spec.rb
|