weixin_authorize_905 1.6.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +19 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +11 -0
  6. data/Gemfile +5 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +72 -0
  9. data/Rakefile +1 -0
  10. data/lib/weixin_authorize.rb +97 -0
  11. data/lib/weixin_authorize/api.rb +3 -0
  12. data/lib/weixin_authorize/api/custom.rb +176 -0
  13. data/lib/weixin_authorize/api/data_cube.rb +8 -0
  14. data/lib/weixin_authorize/api/groups.rb +60 -0
  15. data/lib/weixin_authorize/api/mass.rb +80 -0
  16. data/lib/weixin_authorize/api/media.rb +149 -0
  17. data/lib/weixin_authorize/api/menu.rb +36 -0
  18. data/lib/weixin_authorize/api/oauth.rb +50 -0
  19. data/lib/weixin_authorize/api/qrcode.rb +62 -0
  20. data/lib/weixin_authorize/api/template.rb +34 -0
  21. data/lib/weixin_authorize/api/user.rb +69 -0
  22. data/lib/weixin_authorize/carrierwave/weixin_uploader.rb +4 -0
  23. data/lib/weixin_authorize/client.rb +95 -0
  24. data/lib/weixin_authorize/config.rb +35 -0
  25. data/lib/weixin_authorize/handler.rb +3 -0
  26. data/lib/weixin_authorize/handler/exceptions.rb +5 -0
  27. data/lib/weixin_authorize/handler/global_code.rb +127 -0
  28. data/lib/weixin_authorize/handler/result_handler.rb +52 -0
  29. data/lib/weixin_authorize/js_ticket/object_store.rb +21 -0
  30. data/lib/weixin_authorize/js_ticket/redis_store.rb +41 -0
  31. data/lib/weixin_authorize/js_ticket/store.rb +40 -0
  32. data/lib/weixin_authorize/token/object_store.rb +25 -0
  33. data/lib/weixin_authorize/token/redis_store.rb +38 -0
  34. data/lib/weixin_authorize/token/store.rb +72 -0
  35. data/lib/weixin_authorize/version.rb +3 -0
  36. data/spec/1_fetch_access_token_spec.rb +43 -0
  37. data/spec/2_fetch_jsticket_spec.rb +10 -0
  38. data/spec/api/custom_spec.rb +71 -0
  39. data/spec/api/groups_spec.rb +74 -0
  40. data/spec/api/mass_spec.rb +70 -0
  41. data/spec/api/media_spec.rb +82 -0
  42. data/spec/api/medias/favicon.ico +0 -0
  43. data/spec/api/medias/ruby-logo.jpg +0 -0
  44. data/spec/api/menu_spec.rb +26 -0
  45. data/spec/api/qrcode_spec.rb +22 -0
  46. data/spec/api/template_spec.rb +40 -0
  47. data/spec/api/user_spec.rb +26 -0
  48. data/spec/spec_helper.rb +130 -0
  49. data/weixin_authorize.gemspec +48 -0
  50. metadata +301 -0
@@ -0,0 +1,80 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Api
4
+ module Mass
5
+
6
+ MSG_TYPE = ["mpnews", "image", "text", "voice", "mpvideo"].freeze
7
+
8
+ # media_info= {"media_id" media_id}
9
+ # https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token=ACCESS_TOKEN
10
+ def mass_with_group(group_id, media_info, msgtype="mpnews", is_to_all=false)
11
+ group_option = {filter: {group_id: group_id, is_to_all: is_to_all}}
12
+ media = generate_media(msgtype, media_info, group_option)
13
+
14
+ mass_url = "#{mass_base_url}/sendall"
15
+ http_post(mass_url, media)
16
+ end
17
+
18
+ # https://api.weixin.qq.com/cgi-bin/message/mass/send?access_token=ACCESS_TOKEN
19
+ # if mpvideo,
20
+ # media_info= {"media_id" => media_id, "title" => "title", "description" => "description"}
21
+ def mass_with_openids(openids, media_info, msgtype="mpnews")
22
+ openid_option = {touser: openids}
23
+ media = generate_media(msgtype, media_info, openid_option)
24
+ mass_url = "#{mass_base_url}/send"
25
+ http_post(mass_url, media)
26
+ end
27
+
28
+ # 请注意,只有已经发送成功的消息才能删除删除消息只是将消息的图文详情页失效,已经收到的用户,还是能在其本地看到消息卡片。
29
+ # 另外,删除群发消息只能删除图文消息和视频消息,其他类型的消息一经发送,无法删除。
30
+ def mass_delete_with_msgid(msg_id)
31
+ mass_url = "#{mass_base_url}/delete"
32
+ http_post(mass_url, {msg_id: msg_id})
33
+ end
34
+
35
+ # 预览接口【订阅号与服务号认证后均可用】
36
+ def mass_preview(openid, media_info, msg_type="mpnews")
37
+ openid_option = {touser: openid}
38
+ media = generate_media(msg_type, media_info, openid_option)
39
+ mass_url = "#{mass_base_url}/preview"
40
+ http_post(mass_url, media)
41
+ end
42
+
43
+ # 查询群发消息发送状态【订阅号与服务号认证后均可用】
44
+ def mass_get_status(msg_id)
45
+ mass_url = "#{mass_base_url}/get"
46
+ http_post(mass_url, {"msg_id" => msg_id})
47
+ end
48
+
49
+ private
50
+
51
+ def mass_base_url
52
+ "/message/mass"
53
+ end
54
+
55
+ def generate_media(msg_type, media_info, option)
56
+ msg_type = msg_type.to_s
57
+ if not MSG_TYPE.include?(msg_type)
58
+ raise MediaTypeException, "#{msg_type} is a invalid msg_type"
59
+ end
60
+ {
61
+ msg_type => convert_media_info(msg_type, media_info),
62
+ "msgtype" => msg_type
63
+ }.merge(option)
64
+ end
65
+
66
+ # 如果用户填写的media信息,是字符串,则转换来符合的数据结构,如果 是hash,则直接使用用户的结构。
67
+ def convert_media_info(msg_type, media_info)
68
+ if media_info.is_a?(String)
69
+ if msg_type == "text"
70
+ return {content: media_info}
71
+ else
72
+ return {media_id: media_info}
73
+ end
74
+ end
75
+ media_info
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,149 @@
1
+ # encoding: utf-8
2
+
3
+ module WeixinAuthorize
4
+ module Api
5
+ module Media
6
+ # 上传多媒体文件
7
+ # http请求方式: POST/FORM
8
+ # http://file.api.weixin.qq.com/cgi-bin/media/upload?access_token=ACCESS_TOKEN&type=TYPE
9
+ # 支持传路径或者文件类型
10
+ def upload_media(media, media_type)
11
+ file = process_file(media)
12
+ upload_media_url = "#{media_base_url}/upload"
13
+ http_post(upload_media_url, {media: file}, {type: media_type}, "file")
14
+ end
15
+
16
+ # 目前仅仅把下载链接返回给第三方开发者,由第三方开发者处理下载
17
+ def download_media_url(media_id)
18
+ download_media_url = WeixinAuthorize.endpoint_url("file", "#{media_base_url}/get")
19
+ params = URI.encode_www_form("access_token" => get_access_token,
20
+ "media_id" => media_id)
21
+ download_media_url += "?#{params}"
22
+ download_media_url
23
+ end
24
+
25
+ # 上传图文消息素材, 主要用于群发消息接口
26
+ # {
27
+ # "articles": [
28
+ # {
29
+ # "thumb_media_id":"mwvBelOXCFZiq2OsIU-p",
30
+ # "author":"xxx",
31
+ # "title":"Happy Day",
32
+ # "content_source_url":"www.qq.com",
33
+ # "content":"content",
34
+ # "digest":"digest"
35
+ # },
36
+ # {
37
+ # "thumb_media_id":"mwvBelOXCFZiq2OsIU-p",
38
+ # "author":"xxx",
39
+ # "title":"Happy Day",
40
+ # "content_source_url":"www.qq.com",
41
+ # "content":"content",
42
+ # "digest":"digest"
43
+ # }
44
+ # ]
45
+ # }
46
+ # Option: author, content_source_url
47
+ def upload_mass_news(news=[])
48
+ upload_news_url = "#{media_base_url}/uploadnews"
49
+ http_post(upload_news_url, {articles: news})
50
+ end
51
+
52
+ # media_id: 需通过基础支持中的上传下载多媒体文件来得到
53
+ # https://file.api.weixin.qq.com/cgi-bin/media/uploadvideo?access_token=ACCESS_TOKEN
54
+
55
+ # return:
56
+ # {
57
+ # "type":"video",
58
+ # "media_id":"IhdaAQXuvJtGzwwc0abfXnzeezfO0NgPK6AQYShD8RQYMTtfzbLdBIQkQziv2XJc",
59
+ # "created_at":1398848981
60
+ # }
61
+ def upload_mass_video(media_id, title="", desc="")
62
+ video_msg = {
63
+ "media_id" => media_id,
64
+ "title" => title,
65
+ "description" => desc
66
+ }
67
+
68
+ http_post("#{media_base_url}/uploadvideo", video_msg)
69
+ end
70
+
71
+ # 上传图文消息内的图片获取URL
72
+ # https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token=ACCESS_TOKEN
73
+ #
74
+ # return:
75
+ # {
76
+ # "url": "http://mmbiz.qpic.cn/mmbiz/gLO17UPS6FS2xsypf378iaNhWacZ1G1UplZYWEYfwvuU6Ont96b1roYs CNFwaRrSaKTPCUdBK9DgEHicsKwWCBRQ/0"
77
+ # }
78
+ def upload_image(image)
79
+ file = process_file(image)
80
+ upload_image_url = "#{media_base_url}/uploadimg"
81
+ http_post(upload_image_url, {media: file}, {type: 'image'}, 'file')
82
+ end
83
+
84
+ private
85
+
86
+ def media_base_url
87
+ "/media"
88
+ end
89
+
90
+ def process_file(media)
91
+ return media if media.is_a?(File) && jpep?(media)
92
+
93
+ media_url = media
94
+ uploader = WeixinUploader.new
95
+
96
+ if http?(media_url) # remote
97
+ uploader.download!(media_url.to_s)
98
+ else # local
99
+ media_file = media.is_a?(File) ? media : File.new(media_url)
100
+ uploader.cache!(media_file)
101
+ end
102
+ file = process_media(uploader)
103
+ CarrierWave.clean_cached_files! # clear last one day cache
104
+ file
105
+ end
106
+
107
+ def process_media(uploader)
108
+ uploader = covert(uploader)
109
+ uploader.file.to_file
110
+ end
111
+
112
+ # JUST ONLY FOR JPG IMAGE
113
+ def covert(uploader)
114
+ # image process
115
+ unless (uploader.file.content_type =~ /image/).nil?
116
+ if !jpep?(uploader.file)
117
+ require "mini_magick"
118
+ # covert to jpeg
119
+ image = MiniMagick::Image.open(uploader.path)
120
+ image.format("jpg")
121
+ uploader.cache!(File.open(image.path))
122
+ image.destroy! # remove /tmp from MinMagick generate
123
+ end
124
+ end
125
+ uploader
126
+ end
127
+
128
+ def http?(uri)
129
+ return false if !uri.is_a?(String)
130
+ uri = URI.parse(uri)
131
+ uri.scheme =~ /^https?$/
132
+ end
133
+
134
+ def jpep?(file)
135
+ content_type = if file.respond_to?(:content_type)
136
+ file.content_type
137
+ else
138
+ content_type(file.path)
139
+ end
140
+ !(content_type =~ /jpeg/).nil?
141
+ end
142
+
143
+ def content_type(media_path)
144
+ MIME::Types.type_for(media_path).first.content_type
145
+ end
146
+
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Api
4
+ module Menu
5
+
6
+ # 自定义菜单查询接口
7
+ # https://api.weixin.qq.com/cgi-bin/menu/get?access_token=ACCESS_TOKEN
8
+ def menu
9
+ get_menu_url = "#{menu_base_url}/get"
10
+ http_get(get_menu_url)
11
+ end
12
+
13
+ # 自定义菜单删除接口
14
+ # https://api.weixin.qq.com/cgi-bin/menu/delete?access_token=ACCESS_TOKEN
15
+ def delete_menu
16
+ delete_menu_url = "#{menu_base_url}/delete"
17
+ http_get(delete_menu_url)
18
+ end
19
+
20
+ # 自定义菜单创建接口
21
+ # https://api.weixin.qq.com/cgi-bin/menu/create?access_token=ACCESS_TOKEN
22
+ def create_menu(menu)
23
+ menu = JSON.load(menu) if menu.is_a?(String)
24
+ create_menu_url = "#{menu_base_url}/create"
25
+ http_post(create_menu_url, menu)
26
+ end
27
+
28
+ private
29
+
30
+ def menu_base_url
31
+ "/menu"
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Api
4
+ module Oauth
5
+
6
+ # 网站应用微信登录授权URL
7
+ # 文档:http://t.cn/RyZVWEY
8
+ def qrcode_authorize_url(redirect_uri, scope="snsapi_login", state="web_wx_login")
9
+ uri = encode_url(redirect_uri)
10
+ WeixinAuthorize.open_endpoint("/connect/qrconnect?appid=#{app_id}&redirect_uri=#{uri}&response_type=code&scope=#{scope}&state=#{state}#wechat_redirect")
11
+ end
12
+
13
+ # 应用授权作用域: scope
14
+ # snsapi_base (不弹出授权页面,直接跳转,只能获取用户openid),
15
+ # snsapi_userinfo (弹出授权页面,可通过openid拿到昵称、性别、所在地。并且,即使在未关注的情况下,只要用户授权,也能获取其信息)
16
+ # default is snsapi_base
17
+ # state 重定向后会带上state参数,开发者可以填写a-zA-Z0-9的参数值
18
+
19
+ # 如果用户点击同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE。若用户禁止授权,则重定向后不会带上code参数,仅会带上state参数redirect_uri?state=STATE
20
+ def authorize_url(redirect_uri, scope="snsapi_base", state="weixin")
21
+ uri = encode_url(redirect_uri)
22
+ WeixinAuthorize.open_endpoint("/connect/oauth2/authorize?appid=#{app_id}&redirect_uri=#{uri}&response_type=code&scope=#{scope}&state=#{state}#wechat_redirect")
23
+ end
24
+
25
+ # 首先请注意,这里通过code换取的网页授权access_token,与基础支持中的access_token不同。公众号可通过下述接口来获取网页授权access_token。如果网页授权的作用域为snsapi_base,则本步骤中获取到网页授权access_token的同时,也获取到了openid,snsapi_base式的网页授权流程即到此为止。
26
+
27
+ # 微信通过请求 #authorize_url 方法后,会返回一个code到redirect_uri中
28
+ def get_oauth_access_token(code)
29
+ WeixinAuthorize.http_get_without_token("/sns/oauth2/access_token?appid=#{app_id}&secret=#{app_secret}&code=#{code}&grant_type=authorization_code", {}, "api")
30
+ end
31
+
32
+ # refresh_token: 填写通过access_token获取到的refresh_token参数
33
+ def refresh_oauth2_token(refresh_token)
34
+ WeixinAuthorize.http_get_without_token("/sns/oauth2/refresh_token?appid=#{app_id}&grant_type=refresh_token&refresh_token=#{refresh_token}", {}, "api")
35
+ end
36
+
37
+ # 如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。
38
+ def get_oauth_userinfo(openid, oauth_token, lang="zh_CN")
39
+ WeixinAuthorize.http_get_without_token("/sns/userinfo?access_token=#{oauth_token}&openid=#{openid}&lang=#{lang}", {}, "api")
40
+ end
41
+
42
+ private
43
+
44
+ def encode_url(uri)
45
+ ERB::Util.url_encode(uri)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Api
4
+ module Qrcode
5
+ # http://mp.weixin.qq.com/wiki/index.php?title=生成带参数的二维码
6
+
7
+ # 临时二维码
8
+ def create_qr_scene(scene_id, expire_seconds=1800)
9
+ qrcode_infos = {action_name: "QR_SCENE", expire_seconds: expire_seconds}
10
+ qrcode_infos.merge!(action_info(scene_id))
11
+ http_post(qrcode_base_url, qrcode_infos)
12
+ end
13
+
14
+ # 临时字符串参数二维码
15
+ def create_qr_str_scene(options, expire_seconds=1800)
16
+ scene_str = options[:scene_str]
17
+ qrcode_infos = {action_name: "QR_STR_SCENE", expire_seconds: expire_seconds}
18
+ qrcode_infos.merge!(action_info(nil, scene_str))
19
+ http_post(qrcode_base_url, qrcode_infos)
20
+ end
21
+
22
+
23
+ # 永久二维码
24
+ # options: scene_id, scene_str
25
+ def create_qr_limit_scene(options)
26
+ scene_id = options[:scene_id]
27
+ qrcode_infos = {action_name: "QR_LIMIT_SCENE"}
28
+ qrcode_infos.merge!(action_info(scene_id))
29
+ http_post(qrcode_base_url, qrcode_infos)
30
+ end
31
+
32
+ # 为永久的字符串参数值
33
+ # options: scene_str
34
+ def create_qr_limit_str_scene(options)
35
+ scene_str = options[:scene_str]
36
+ qrcode_infos = {action_name: "QR_LIMIT_STR_SCENE"}
37
+ qrcode_infos.merge!(action_info(nil, scene_str))
38
+ http_post(qrcode_base_url, qrcode_infos)
39
+ end
40
+
41
+
42
+ # 通过ticket换取二维码, 直接访问即可显示!
43
+ def qr_code_url(ticket)
44
+ WeixinAuthorize.mp_endpoint("/showqrcode?ticket=#{ticket}")
45
+ end
46
+
47
+ private
48
+
49
+ def qrcode_base_url
50
+ "/qrcode/create"
51
+ end
52
+
53
+ def action_info(scene_id, scene_str=nil)
54
+ scene_info = {}
55
+ scene_info[:scene_id] = scene_id if !scene_id.nil?
56
+ scene_info[:scene_str] = scene_str if !scene_str.nil?
57
+ {action_info: {scene: scene_info}}
58
+ end
59
+
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Api
4
+ module Template
5
+
6
+ # 设置所属行业
7
+ # 需要选择公众账号服务所处的2个行业,每月可更改1次所选行业;
8
+ # 初始化行业时,传入两个,每月更改时,传入一个即可。
9
+ def set_template_industry(industry_id1, industry_id2="")
10
+ industries = {industry_id1: industry_id1}
11
+ if industry_id2 != ""
12
+ industries.merge!({industry_id2: industry_id2})
13
+ end
14
+ http_post("/template/api_set_industry", industries)
15
+ end
16
+
17
+ # 获得模板ID
18
+ # code: 模板库中模板的编号,有“TM**”和“OPENTMTM**”等形式
19
+ def add_template(code)
20
+ http_post("/template/api_add_template", template_id_short: code)
21
+ end
22
+
23
+ # 发送模板消息
24
+ def send_template_msg(touser, template_id, url, topcolor, data)
25
+ msg = {
26
+ touser: touser, template_id: template_id,
27
+ url: url, topcolor: topcolor, data: data
28
+ }
29
+ http_post("/message/template/send", msg)
30
+ end
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ module WeixinAuthorize
3
+ module Api
4
+ module User
5
+
6
+ # 获取用户基本信息
7
+ # https://api.weixin.qq.com/cgi-bin/user/info?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN
8
+ # lang: zh_CN, zh_TW, en
9
+ def user(openid, lang="zh_CN")
10
+ user_info_url = "#{user_base_url}/info"
11
+ http_get(user_info_url, {openid: openid, lang: lang})
12
+ end
13
+
14
+ # 批量获取用户基本信息
15
+ # https://api.weixin.qq.com/cgi-bin/user/info/batchget?access_token=ACCESS_TOKEN
16
+ # POST数据格式:JSON
17
+ # POST数据例子:
18
+ # {
19
+ # "user_list": [
20
+ # {
21
+ # "openid": "otvxTs4dckWG7imySrJd6jSi0CWE",
22
+ # "lang": "zh-CN"
23
+ # },
24
+ # {
25
+ # "openid": "otvxTs_JZ6SEiP0imdhpi50fuSZg",
26
+ # "lang": "zh-CN"
27
+ # }
28
+ # ]
29
+ # }
30
+ def users(user_list)
31
+ user_info_batchget_url = "#{user_base_url}/info/batchget"
32
+ post_body = user_list
33
+ http_post(user_info_batchget_url, post_body)
34
+ end
35
+
36
+ # 获取关注者列表
37
+ # https://api.weixin.qq.com/cgi-bin/user/get?access_token=ACCESS_TOKEN&next_openid=NEXT_OPENID
38
+ def followers(next_openid="")
39
+ followers_url = "#{user_base_url}/get"
40
+ http_get(followers_url, {next_openid: next_openid})
41
+ end
42
+
43
+ # 设置备注名
44
+ # http请求方式: POST(请使用https协议)
45
+ # https://api.weixin.qq.com/cgi-bin/user/info/updateremark?access_token=ACCESS_TOKEN
46
+ # POST数据格式:JSON
47
+ # POST数据例子:
48
+ # {
49
+ # "openid":"oDF3iY9ffA-hqb2vVvbr7qxf6A0Q",
50
+ # "remark":"pangzi"
51
+ # }
52
+ def update_remark(openid, remark)
53
+ update_url = "/user/info/updateremark"
54
+ post_body = {
55
+ openid: openid,
56
+ remark: remark
57
+ }
58
+ http_post(update_url, post_body)
59
+ end
60
+
61
+ private
62
+
63
+ def user_base_url
64
+ "/user"
65
+ end
66
+
67
+ end
68
+ end
69
+ end