wxapi 1.0.0
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 +9 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +26 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/wx_api.rb +41 -0
- data/lib/wxapi/access_token.rb +45 -0
- data/lib/wxapi/account.rb +112 -0
- data/lib/wxapi/aes.rb +41 -0
- data/lib/wxapi/kf_message.rb +143 -0
- data/lib/wxapi/material.rb +48 -0
- data/lib/wxapi/menu.rb +72 -0
- data/lib/wxapi/templet_message.rb +103 -0
- data/lib/wxapi/user.rb +41 -0
- data/lib/wxapi/utils.rb +12 -0
- data/lib/wxapi/version.rb +3 -0
- data/wxapi.gemspec +39 -0
- metadata +146 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a96de275aacbda637fb5d2f52229cbfae165c168f7b657dd5c6608b742805e20
|
4
|
+
data.tar.gz: 0e4e7d0888413af25fa4392b9f5a4b7adfabbc8782721eb6fdd900392f3f3ccc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6050182becc85a57cab38778ae027fe22497a26ee33366c36c95edcb1b4736cf72f5f6ff1f42421d908a4b7d907df1259430cfe1cba38a364d6c98d1816d0b08
|
7
|
+
data.tar.gz: e8522ae70f8312f1cc18b039b99e51be5e5c782ff46b7b70cb7ca0a3e16a473993fbd63f248d18e284777acb316a4f93ebbad0c3f7a8976b0322a4975ebd6074
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 zhangS2
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# 简介
|
2
|
+
|
3
|
+
基于ruby的微信公众号开发的API,包含对微信公众号菜单栏、客服消息、模板消息、帐号管理等接口的封装。
|
4
|
+
|
5
|
+
API长期更新维护,建议使用最新版本。
|
6
|
+
|
7
|
+
## 安装
|
8
|
+
|
9
|
+
添加 Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'wxapi'
|
13
|
+
```
|
14
|
+
|
15
|
+
执行 bundle:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
或者直接通过 gem 安装:
|
20
|
+
|
21
|
+
$ gem install wxapi
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
项目最早来源于 https://index.ruby-china.com/gems/wechat_public_api
|
26
|
+
我在原项目的基础上进行了一些修改和优化,使其更加符合微信公众号开发的规范,增加了更多的扩展性。
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "wx_api"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/wx_api.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require "wxapi/version"
|
2
|
+
require "wxapi/menu"
|
3
|
+
require "wxapi/kf_message"
|
4
|
+
require "wxapi/templet_message"
|
5
|
+
require "wxapi/access_token"
|
6
|
+
require "wxapi/account"
|
7
|
+
require "wxapi/aes"
|
8
|
+
require "wxapi/utils"
|
9
|
+
require "wxapi/user"
|
10
|
+
require "wxapi/material"
|
11
|
+
|
12
|
+
class WxApi
|
13
|
+
include AccessToken
|
14
|
+
include Account
|
15
|
+
include Aes
|
16
|
+
include Kf
|
17
|
+
include Material
|
18
|
+
include Menu
|
19
|
+
include Tp
|
20
|
+
include User
|
21
|
+
include Utils
|
22
|
+
|
23
|
+
# 默认不缓存 access_token, access_token_cache = True 缓存
|
24
|
+
# @param <hash> aoptions
|
25
|
+
# => example
|
26
|
+
# wechat_api = WechatPublicApi.new appid: 'xx', app_secret: 'xx', access_token_cache: true
|
27
|
+
# wechat_api.app_id -- get appid
|
28
|
+
#
|
29
|
+
# api.get_access_token
|
30
|
+
#
|
31
|
+
|
32
|
+
attr_accessor :app_id, :app_secret, :access_token_cache
|
33
|
+
attr_accessor :prefix
|
34
|
+
def initialize(options={})
|
35
|
+
@app_id = options[:app_id]
|
36
|
+
@app_secret = options[:app_secret]
|
37
|
+
@access_token_cache = options[:access_token_cache]
|
38
|
+
@prefix = options[:api_prefix]
|
39
|
+
@prefix="https://api.weixin.qq.com" if @prefix.nil?
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 获得微信公众号的 access_token (Get wechat public access_token)
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-17
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module AccessToken
|
10
|
+
|
11
|
+
###
|
12
|
+
# 获取 access_token
|
13
|
+
# 判断access_token_cache,决定是否需要缓存数据
|
14
|
+
# @return <string> nil or access_token
|
15
|
+
def get_access_token()
|
16
|
+
appid = @app_id
|
17
|
+
secret = @app_secret
|
18
|
+
access_token_cache = @access_token_cache
|
19
|
+
|
20
|
+
unless access_token_cache
|
21
|
+
response = HTTParty.get("#{prefix}/cgi-bin/token?grant_type=client_credential&appid=#{appid}&secret=#{secret}").body
|
22
|
+
response_body = (JSON.parse response)
|
23
|
+
|
24
|
+
# 抛出异常
|
25
|
+
throw response_body['errmsg'] unless response_body['access_token']
|
26
|
+
|
27
|
+
return response_body['access_token']
|
28
|
+
end
|
29
|
+
|
30
|
+
_cache_key = "#{appid}_access_token"
|
31
|
+
_cached_access_token = $redis.get _cache_key
|
32
|
+
if _cached_access_token == nil or _cached_access_token == ''
|
33
|
+
response = HTTParty.get("#{prefix}/cgi-bin/token?grant_type=client_credential&appid=#{appid}&secret=#{secret}").body
|
34
|
+
response_body = (JSON.parse response)
|
35
|
+
|
36
|
+
# 抛出异常
|
37
|
+
throw response_body['errmsg'] unless response_body['access_token']
|
38
|
+
|
39
|
+
_cached_access_token = response_body['access_token']
|
40
|
+
$redis.set _cache_key, _cached_access_token, ex: 2.minutes
|
41
|
+
end
|
42
|
+
_cached_access_token
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 微信公众号帐号管理 (带参二维码获得)
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-18
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Account
|
10
|
+
###
|
11
|
+
# 获取临时场景带惨二维码,30天有效
|
12
|
+
# @param <int> sceneid -- 场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000)
|
13
|
+
# @return <string> url -- 二维码网址
|
14
|
+
#
|
15
|
+
def get_qrscene(sceneid)
|
16
|
+
access_token = get_access_token()
|
17
|
+
|
18
|
+
# 获取ticket
|
19
|
+
uri = URI.parse("#{prefix}/cgi-bin/qrcode/create?access_token=#{access_token}")
|
20
|
+
post_data = {
|
21
|
+
'expire_seconds' => 2592000,
|
22
|
+
'action_name' => 'QR_SCENE',
|
23
|
+
'action_info' => {'scene' => {'scene_id' => sceneid}}}
|
24
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
25
|
+
http.use_ssl = true
|
26
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
27
|
+
request = Net::HTTP::Post.new("/cgi-bin/qrcode/create?access_token=#{access_token}")
|
28
|
+
request.add_field('Content-Type', 'application/json')
|
29
|
+
request.body = post_data.to_json
|
30
|
+
response = http.request(request)
|
31
|
+
content = JSON.parse(response.body)
|
32
|
+
ticket = content['ticket']
|
33
|
+
|
34
|
+
# 通过ticket换取二维码
|
35
|
+
"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=#{ticket}"
|
36
|
+
end
|
37
|
+
|
38
|
+
###
|
39
|
+
# 获取永久二维码
|
40
|
+
# @param <int> sceneid -- 场景值ID,临时二维码时为32位非0整型,永久二维码时最大值为100000(目前参数只支持1--100000)
|
41
|
+
# @return <string> url -- 二维码网址
|
42
|
+
#
|
43
|
+
def get_qrsrtscene(sceneid)
|
44
|
+
access_token = get_access_token()
|
45
|
+
|
46
|
+
# 获取ticket
|
47
|
+
uri = URI.parse("#{prefix}/cgi-bin/qrcode/create?access_token=#{access_token}")
|
48
|
+
post_data = {
|
49
|
+
'action_name' => 'QR_LIMIT_STR_SCENE',
|
50
|
+
'action_info' => {'scene' => {'scene_id' => sceneid}}}
|
51
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
52
|
+
http.use_ssl = true
|
53
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
54
|
+
request = Net::HTTP::Post.new("/cgi-bin/qrcode/create?access_token=#{access_token}")
|
55
|
+
request.add_field('Content-Type', 'application/json')
|
56
|
+
request.body = post_data.to_json
|
57
|
+
response = http.request(request)
|
58
|
+
content = JSON.parse(response.body)
|
59
|
+
ticket = content['ticket']
|
60
|
+
|
61
|
+
# 通过ticket换取二维码
|
62
|
+
"https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=#{ticket}"
|
63
|
+
end
|
64
|
+
|
65
|
+
###
|
66
|
+
# 保存带参数二维码到指定位置
|
67
|
+
# @param <string> path -- 例如: "#{Rails.root}/public/qrcode"
|
68
|
+
# @param <string> filename -- 文件名,可选参数,默认不填写则使用时间戳+随机数的方式命名
|
69
|
+
#
|
70
|
+
# @return <string> path -- 二维码的保存路径
|
71
|
+
def save_qrcode(path, *filename)
|
72
|
+
# 判断是否需要新建文件
|
73
|
+
unless File.exist?(path)
|
74
|
+
FileUtils.makedirs(path)
|
75
|
+
end
|
76
|
+
|
77
|
+
if filename
|
78
|
+
path = "#{path}/#{filename}"
|
79
|
+
else
|
80
|
+
path = "#{path}/#{Time.now.to_i.to_s}_#{rand 1000.9999}"
|
81
|
+
end
|
82
|
+
|
83
|
+
File.open(path, 'wb') do |f|
|
84
|
+
f.write(HTTParty.get(url).body)
|
85
|
+
end
|
86
|
+
|
87
|
+
path
|
88
|
+
end
|
89
|
+
|
90
|
+
###
|
91
|
+
# @param <string> longurl -- 需要被压缩的url
|
92
|
+
#
|
93
|
+
# @return <json> shorturl -- 返回短链接 {"errcode":0,"errmsg":"ok","short_url":"http:\/\/w.url.cn\/s\/AvCo6Ih"}
|
94
|
+
# if false
|
95
|
+
# @return {"errcode":40013,"errmsg":"invalid appid"}
|
96
|
+
#
|
97
|
+
def get_shorturl(longurl)
|
98
|
+
access_token = get_access_token()
|
99
|
+
uri = URI.parse("#{prefix}/cgi-bin/shorturl?access_token=#{access_token}")
|
100
|
+
post_data = {action: "long2short", long_url: longurl}
|
101
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
102
|
+
http.use_ssl = true
|
103
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
104
|
+
request = Net::HTTP::Post.new("/cgi-bin/shorturl?access_token=#{access_token}")
|
105
|
+
request.add_field('Content-Type', 'application/json')
|
106
|
+
request.body = post_data.to_json
|
107
|
+
response = http.request(request)
|
108
|
+
|
109
|
+
JSON.parse(response.body)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/lib/wxapi/aes.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 消息加密解密
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-18
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Aes
|
10
|
+
# 解密
|
11
|
+
def decrypt(key, dicrypted_string)
|
12
|
+
cipher = OpenSSL::Cipher::AES.new(256, :CBC)
|
13
|
+
cipher.decrypt
|
14
|
+
cipher.key = key
|
15
|
+
cipher.iv = '0000000000000000'
|
16
|
+
cipher.padding = 0
|
17
|
+
cipher.update(dicrypted_string) << cipher.final
|
18
|
+
end
|
19
|
+
|
20
|
+
# 加密
|
21
|
+
def encrypt(aes_key, text, app_id)
|
22
|
+
text = text.force_encoding("ASCII-8BIT")
|
23
|
+
random = SecureRandom.hex(8)
|
24
|
+
msg_len = [text.length].pack("N")
|
25
|
+
text = "#{random}#{msg_len}#{text}#{app_id}"
|
26
|
+
text = WxAuth.encode(text)
|
27
|
+
text = handle_cipher(:encrypt, aes_key, text)
|
28
|
+
Base64.encode64(text)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
def handle_cipher(action, aes_key, text)
|
33
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
34
|
+
cipher.send(action)
|
35
|
+
cipher.padding = 0
|
36
|
+
cipher.key = aes_key
|
37
|
+
cipher.iv = aes_key[0...16]
|
38
|
+
cipher.update(text) + cipher.final
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 客服消息管理 (about kf_account message)
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-17
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Kf
|
10
|
+
|
11
|
+
###
|
12
|
+
# execute post
|
13
|
+
# => example
|
14
|
+
# message = {
|
15
|
+
# touser: openid,
|
16
|
+
# msgtype: 'text',
|
17
|
+
# text: {content: content},
|
18
|
+
# customservice:{
|
19
|
+
# kf_account: 'xxxxxxxx'
|
20
|
+
# }
|
21
|
+
# }
|
22
|
+
#
|
23
|
+
# @param <JSON> message
|
24
|
+
#
|
25
|
+
def post_customer_message(message)
|
26
|
+
# get access_token
|
27
|
+
access_token = get_access_token()
|
28
|
+
|
29
|
+
uri = URI.parse("#{prefix}/cgi-bin/message/custom/send?access_token=#{access_token}")
|
30
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
31
|
+
http.use_ssl = true
|
32
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
33
|
+
request = Net::HTTP::Post.new("/cgi-bin/message/custom/send?access_token=#{access_token}")
|
34
|
+
request.add_field('Content-Type', 'application/json')
|
35
|
+
request.body = message.to_json.gsub(/\\u([0-9a-z]{4})/) {|s| [$1.to_i(16)].pack("U")}
|
36
|
+
response = http.request(request)
|
37
|
+
JSON.parse(response.body)
|
38
|
+
end
|
39
|
+
|
40
|
+
###
|
41
|
+
# @param <string> openid -- 用户的openid
|
42
|
+
# @param <string> content -- 需要发送的客服消息内容
|
43
|
+
#
|
44
|
+
# 发送文字消息
|
45
|
+
def kf_text_message(openid, content, kf_account=nil)
|
46
|
+
|
47
|
+
custom_message = {
|
48
|
+
touser: openid,
|
49
|
+
msgtype: 'text',
|
50
|
+
text: {content: content}
|
51
|
+
}
|
52
|
+
|
53
|
+
if kf_account
|
54
|
+
custom_message.merge({customservice:{account: kf_account}})
|
55
|
+
end
|
56
|
+
|
57
|
+
post_customer_message custom_message
|
58
|
+
end
|
59
|
+
|
60
|
+
###
|
61
|
+
# @param <string> media_id -- 发送的图片/语音/视频/图文消息(点击跳转到图文消息页)的媒体ID
|
62
|
+
#
|
63
|
+
# 发送图片消息
|
64
|
+
def kf_image_message(openid, media_id, kf_account=nil)
|
65
|
+
|
66
|
+
custom_message = {
|
67
|
+
touser: openid,
|
68
|
+
msgtype: 'image',
|
69
|
+
image: {media_id: media_id}
|
70
|
+
}
|
71
|
+
|
72
|
+
if kf_account
|
73
|
+
custom_message.merge({customservice:{account: kf_account}})
|
74
|
+
end
|
75
|
+
|
76
|
+
post_customer_message custom_message
|
77
|
+
end
|
78
|
+
|
79
|
+
# 发送图文消息(点击跳转到图文消息页面)
|
80
|
+
def kf_mpnews_message(openid, media_id, kf_account=nil)
|
81
|
+
custom_message = {
|
82
|
+
touser: openid,
|
83
|
+
msgtype: 'mpnews',
|
84
|
+
mpnews: {media_id: media_id}
|
85
|
+
}
|
86
|
+
if kf_account
|
87
|
+
custom_message.merge({customservice:{account: kf_account}})
|
88
|
+
end
|
89
|
+
|
90
|
+
post_customer_message custom_message
|
91
|
+
end
|
92
|
+
|
93
|
+
###
|
94
|
+
# @param <JSON> articles -- 图文消息列表
|
95
|
+
# => example
|
96
|
+
# articles = [
|
97
|
+
# {
|
98
|
+
# "title":"Happy Day",
|
99
|
+
# "description":"Is Really A Happy Day",
|
100
|
+
# "url":"URL",
|
101
|
+
# "picurl":"PIC_URL"
|
102
|
+
# },
|
103
|
+
# {
|
104
|
+
# "title":"Happy Day",
|
105
|
+
# "description":"Is Really A Happy Day",
|
106
|
+
# "url":"URL",
|
107
|
+
# "picurl":"PIC_URL"
|
108
|
+
# }
|
109
|
+
# ]
|
110
|
+
#
|
111
|
+
# 发送图文消息(点击跳转到外链)
|
112
|
+
def kf_news_message(openid, articles, kf_account=nil)
|
113
|
+
custom_message = {
|
114
|
+
touser: openid,
|
115
|
+
msgtype: 'news',
|
116
|
+
news: {
|
117
|
+
articles: articles
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
if kf_account
|
122
|
+
custom_message.merge({customservice:{account: kf_account}})
|
123
|
+
end
|
124
|
+
post_customer_message custom_message
|
125
|
+
end
|
126
|
+
|
127
|
+
# 发送语音消息
|
128
|
+
def kf_voice_message(openid, media_id, kf_account=nil)
|
129
|
+
|
130
|
+
custom_message = {
|
131
|
+
touser: openid,
|
132
|
+
msgtype: 'voice',
|
133
|
+
voice: {
|
134
|
+
media_id: media_id
|
135
|
+
}
|
136
|
+
}
|
137
|
+
if kf_account
|
138
|
+
custom_message.merge({customservice:{account: kf_account}})
|
139
|
+
end
|
140
|
+
post_customer_message custom_message
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 微信公众号素材管理
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-18
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Material
|
10
|
+
###
|
11
|
+
# 获得临时图片素材的 media_id
|
12
|
+
# @param <string> file_path -- 图片素材的路径
|
13
|
+
#
|
14
|
+
# @return <json> {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}
|
15
|
+
# if false
|
16
|
+
# @return <json> {"errcode":40004,"errmsg":"invalid media type"}
|
17
|
+
#
|
18
|
+
def upload_image_media(file_path)
|
19
|
+
# request access_token
|
20
|
+
access_token = get_access_token()
|
21
|
+
response = RestClient.post("#{prefix}/cgi-bin/media/upload",
|
22
|
+
{
|
23
|
+
access_token: access_token,
|
24
|
+
type: 'image',
|
25
|
+
media: File.new(file_path, 'rb')})
|
26
|
+
JSON.parse(response)
|
27
|
+
end
|
28
|
+
|
29
|
+
###
|
30
|
+
# 新增永久图片素材
|
31
|
+
# @param <string> file_path -- 图片素材的路径
|
32
|
+
#
|
33
|
+
# @return <json> {"type":"TYPE","media_id":"MEDIA_ID","created_at":123456789}
|
34
|
+
# if false
|
35
|
+
# @return <json> {"errcode":40004,"errmsg":"invalid media type"}
|
36
|
+
#
|
37
|
+
def upload_image_material(file_path)
|
38
|
+
# request access_token
|
39
|
+
access_token = get_access_token()
|
40
|
+
response = RestClient.post('https://api.weixin.qq.com/cgi-bin/material/add_material',
|
41
|
+
{
|
42
|
+
access_token: access_token,
|
43
|
+
type: 'image',
|
44
|
+
media: File.new(file_path, 'rb')})
|
45
|
+
JSON.parse(response)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/wxapi/menu.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 公众号自定义菜单栏模块 (about wechat public menu)
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-17
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Menu
|
10
|
+
###
|
11
|
+
# create wechat public menu
|
12
|
+
# @param <json> post_data
|
13
|
+
#
|
14
|
+
# => post_data example
|
15
|
+
# {
|
16
|
+
# "button": [
|
17
|
+
# {
|
18
|
+
# "type": "view",
|
19
|
+
# "name": "",
|
20
|
+
# "url": "",
|
21
|
+
# "sub_button": []
|
22
|
+
# },
|
23
|
+
# {
|
24
|
+
# "type": "click",
|
25
|
+
# "name": "",
|
26
|
+
# "key": "menu_3",
|
27
|
+
# "sub_button": []
|
28
|
+
# }
|
29
|
+
# ]
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
# if success
|
33
|
+
# @return <JSON> {"errcode"=>0, "errmsg"=>"ok"}
|
34
|
+
# if failed
|
35
|
+
# @return <JSON> {"errcode"=>40166, "errmsg"=>"...."}
|
36
|
+
#
|
37
|
+
def create_menu(post_data)
|
38
|
+
# request access_token
|
39
|
+
access_token = get_access_token()
|
40
|
+
post_data = post_data.to_json.gsub(/\\u([0-9a-z]{4})/) {|s| [$1.to_i(16)].pack("U")}
|
41
|
+
|
42
|
+
uri = URI.parse("#{prefix}/cgi-bin/menu/create?access_token=#{access_token}")
|
43
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
44
|
+
http.use_ssl = true
|
45
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
46
|
+
request = Net::HTTP::Post.new("/cgi-bin/menu/create?access_token=#{access_token}")
|
47
|
+
request.add_field('Content-Type', 'application/json')
|
48
|
+
request.body = post_data
|
49
|
+
response = http.request(request)
|
50
|
+
(JSON.parse response.body)
|
51
|
+
end
|
52
|
+
|
53
|
+
# get wechat public menu list
|
54
|
+
def query_menu()
|
55
|
+
# request access_token
|
56
|
+
access_token = get_access_token()
|
57
|
+
response = HTTParty.get("#{prefix}/cgi-bin/menu/get?access_token=#{access_token}").body
|
58
|
+
(JSON.parse response)
|
59
|
+
end
|
60
|
+
|
61
|
+
###
|
62
|
+
# delete wechat query from access_token
|
63
|
+
# @return <JSON> {"errcode"=>0, "errmsg"=>"ok"}
|
64
|
+
#
|
65
|
+
def delete_menu()
|
66
|
+
# request access_token
|
67
|
+
access_token = get_access_token
|
68
|
+
response = HTTParty.get("#{prefix}/cgi-bin/menu/delete?access_token=#{access_token}").body
|
69
|
+
(JSON.parse response)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 微信公众号模板消息相关接口 (About templet message of wechat public)
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-17
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Tp
|
10
|
+
###
|
11
|
+
# @param <JSON> message
|
12
|
+
# @param <string> url_params -- 消息路径
|
13
|
+
# 发送模板消息接口
|
14
|
+
def post_template_message(message, url_params)
|
15
|
+
# get access_token
|
16
|
+
access_token = get_access_token()
|
17
|
+
|
18
|
+
uri = URI.parse("#{prefix}/cgi-bin/message/template/#{url_params}?access_token=#{access_token}")
|
19
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
20
|
+
http.use_ssl = true
|
21
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
22
|
+
request = Net::HTTP::Post.new("/cgi-bin/message/template/#{url_params}?access_token=#{access_token}")
|
23
|
+
request.add_field('Content-Type', 'application/json')
|
24
|
+
# 部分字符转换为json后 成为unicode编码
|
25
|
+
request.body = message.to_json.gsub(/\\u([0-9a-z]{4})/) {|s| [$1.to_i(16)].pack("U")}
|
26
|
+
response = http.request(request)
|
27
|
+
JSON.parse(response.body)
|
28
|
+
end
|
29
|
+
|
30
|
+
###
|
31
|
+
# @param <string> openid -- 用户的openid
|
32
|
+
# @param <string> templet_id -- 微信公众号后台提供的模板ID
|
33
|
+
# @param <string> url -- 模板跳转链接
|
34
|
+
# @param <string> appid -- 小程序的appid
|
35
|
+
# @param <string> pagepath -- 小程序页面路径 (eq: /index/index)
|
36
|
+
# @param <JSON> data -- 模板数据
|
37
|
+
# => data example
|
38
|
+
# data = {
|
39
|
+
# "first": {
|
40
|
+
# "value":"恭喜你购买成功!",
|
41
|
+
# "color":"#173177"
|
42
|
+
# },
|
43
|
+
# "keyword1":{
|
44
|
+
# "value":"巧克力",
|
45
|
+
# "color":"#173177"
|
46
|
+
# },
|
47
|
+
# "keyword2": {
|
48
|
+
# "value":"39.8元",
|
49
|
+
# "color":"#173177"
|
50
|
+
# },
|
51
|
+
# "keyword3": {
|
52
|
+
# "value":"2014年9月22日",
|
53
|
+
# "color":"#173177"
|
54
|
+
# },
|
55
|
+
# "remark":{
|
56
|
+
# "value":"欢迎再次购买!",
|
57
|
+
# "color":"#173177"
|
58
|
+
# }
|
59
|
+
# }
|
60
|
+
# 小程序模板消息
|
61
|
+
def tp_miniprogram_message(openid, templet_id, url, appid, pagepath, data)
|
62
|
+
message = {
|
63
|
+
'touser': openid,
|
64
|
+
'template_id': templet_id,
|
65
|
+
'url': url,
|
66
|
+
"miniprogram":{
|
67
|
+
"appid": appid,
|
68
|
+
"pagepath": pagepath
|
69
|
+
},
|
70
|
+
'data': data
|
71
|
+
}
|
72
|
+
|
73
|
+
post_template_message message, 'send'
|
74
|
+
end
|
75
|
+
|
76
|
+
# 普通的模板消息,不跳转小程序
|
77
|
+
def tp_general_message(openid, templet_id, url, data)
|
78
|
+
message = {
|
79
|
+
'touser': openid,
|
80
|
+
'template_id': templet_id,
|
81
|
+
'url': url,
|
82
|
+
'data': data
|
83
|
+
}
|
84
|
+
post_template_message message, 'send'
|
85
|
+
end
|
86
|
+
|
87
|
+
# 删除模板
|
88
|
+
def tp_delete_template(templet_id)
|
89
|
+
message = {
|
90
|
+
'template_id': template_id
|
91
|
+
}
|
92
|
+
post_template_message message, 'del_private_template'
|
93
|
+
end
|
94
|
+
|
95
|
+
# 获得模板列表
|
96
|
+
def tp_get_all()
|
97
|
+
access_token = get_access_token()
|
98
|
+
response = RestClient.get("#{prefix}cgi-bin/template/get_all_private_template?access_token=#{access_token}").body
|
99
|
+
response_body = (JSON.parse response)
|
100
|
+
response_body
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
data/lib/wxapi/user.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 公众号用户管理
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-18
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module User
|
10
|
+
# 获取用户信息
|
11
|
+
# @return <JSON>
|
12
|
+
# {
|
13
|
+
# "subscribe": 1,
|
14
|
+
# "openid": "o6_bmjrPTlm6_2sgVt7hMZOPfL2M",
|
15
|
+
# "nickname": "Band",
|
16
|
+
# "sex": 1,
|
17
|
+
# "language": "zh_CN",
|
18
|
+
# "city": "广州",
|
19
|
+
# "province": "广东",
|
20
|
+
# "country": "中国",
|
21
|
+
# "headimgurl":"http://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/0",
|
22
|
+
# "subscribe_time": 1382694957,
|
23
|
+
# "unionid": " o6_bmasdasdsad6_2sgVt7hMZOPfL"
|
24
|
+
# "remark": "",
|
25
|
+
# "groupid": 0,
|
26
|
+
# "tagid_list":[128,2],
|
27
|
+
# "subscribe_scene": "ADD_SCENE_QR_CODE",
|
28
|
+
# "qr_scene": 98765,
|
29
|
+
# "qr_scene_str": ""
|
30
|
+
# }
|
31
|
+
#
|
32
|
+
# if failed
|
33
|
+
# {"errcode":40013,"errmsg":"invalid appid"}
|
34
|
+
#
|
35
|
+
def get_userinfo(openid)
|
36
|
+
# request access_token
|
37
|
+
access_token = get_access_token()
|
38
|
+
JSON.parse RestClient.get("#{prefix}/cgi-bin/user/info?access_token=#{access_token}&openid=#{openid}&lang=zh_CN").body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/wxapi/utils.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
#####################################################
|
2
|
+
# 基于微信公众号的小工具,公用
|
3
|
+
# Created by zhangmingxin
|
4
|
+
# Date: 2018-05-18
|
5
|
+
# Wechat number: zmx119966
|
6
|
+
####################################################
|
7
|
+
|
8
|
+
class WxApi
|
9
|
+
module Utils
|
10
|
+
# todo
|
11
|
+
end
|
12
|
+
end
|
data/wxapi.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "wxapi/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "wxapi"
|
8
|
+
spec.version = WxApi::VERSION
|
9
|
+
spec.authors = ["黄滚 twitter: @hg_nohair"]
|
10
|
+
spec.email = ["xurenlu@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{微信公众号开发API,包含对微信公众号菜单栏、客服消息、模板消息、帐号管理等接口的封装,API长期更新维护,建议使用最新版本.}
|
13
|
+
spec.description = %q{微信公众号开发API,包含对微信公众号菜单栏、客服消息、模板消息、帐号管理等接口的封装,API长期更新维护,建议使用最新版本,开发者使用文档:https://yuque.com/qianlansedehei/wechat_public_api}
|
14
|
+
spec.homepage = "https://github.com/xurenlu/wxapi"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if spec.respond_to?(:metadata)
|
20
|
+
# spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
# "public gem pushes."
|
24
|
+
# end
|
25
|
+
|
26
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
27
|
+
f.match(%r{^(test|spec|features)/})
|
28
|
+
end
|
29
|
+
spec.bindir = "exe"
|
30
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
31
|
+
spec.require_paths = ["lib"]
|
32
|
+
|
33
|
+
spec.add_development_dependency "bundler"
|
34
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
+
spec.add_development_dependency "redis"
|
36
|
+
spec.add_development_dependency "json"
|
37
|
+
spec.add_development_dependency "rest-client"
|
38
|
+
spec.add_development_dependency 'unf_ext', '~> 0.0.7.5'
|
39
|
+
end
|
metadata
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wxapi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- '黄滚 twitter: @hg_nohair'
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2024-02-02 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: '0'
|
20
|
+
type: :development
|
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: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: redis
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: json
|
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
|
+
- !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: :development
|
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: unf_ext
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 0.0.7.5
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 0.0.7.5
|
97
|
+
description: 微信公众号开发API,包含对微信公众号菜单栏、客服消息、模板消息、帐号管理等接口的封装,API长期更新维护,建议使用最新版本,开发者使用文档:https://yuque.com/qianlansedehei/wechat_public_api
|
98
|
+
email:
|
99
|
+
- xurenlu@gmail.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- ".gitignore"
|
105
|
+
- Gemfile
|
106
|
+
- LICENSE.txt
|
107
|
+
- README.md
|
108
|
+
- Rakefile
|
109
|
+
- bin/console
|
110
|
+
- bin/setup
|
111
|
+
- lib/wx_api.rb
|
112
|
+
- lib/wxapi/access_token.rb
|
113
|
+
- lib/wxapi/account.rb
|
114
|
+
- lib/wxapi/aes.rb
|
115
|
+
- lib/wxapi/kf_message.rb
|
116
|
+
- lib/wxapi/material.rb
|
117
|
+
- lib/wxapi/menu.rb
|
118
|
+
- lib/wxapi/templet_message.rb
|
119
|
+
- lib/wxapi/user.rb
|
120
|
+
- lib/wxapi/utils.rb
|
121
|
+
- lib/wxapi/version.rb
|
122
|
+
- wxapi.gemspec
|
123
|
+
homepage: https://github.com/xurenlu/wxapi
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
metadata: {}
|
127
|
+
post_install_message:
|
128
|
+
rdoc_options: []
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
version: '0'
|
136
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
version: '0'
|
141
|
+
requirements: []
|
142
|
+
rubygems_version: 3.5.4
|
143
|
+
signing_key:
|
144
|
+
specification_version: 4
|
145
|
+
summary: 微信公众号开发API,包含对微信公众号菜单栏、客服消息、模板消息、帐号管理等接口的封装,API长期更新维护,建议使用最新版本.
|
146
|
+
test_files: []
|