qy_wechat_api 1.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +1 -0
- data/Gemfile +17 -0
- data/LICENSE.txt +22 -0
- data/README.md +120 -0
- data/Rakefile +2 -0
- data/lib/qy_wechat_api.rb +50 -0
- data/lib/qy_wechat_api/api.rb +8 -0
- data/lib/qy_wechat_api/api/base.rb +32 -0
- data/lib/qy_wechat_api/api/department.rb +45 -0
- data/lib/qy_wechat_api/api/media.rb +83 -0
- data/lib/qy_wechat_api/api/menu.rb +27 -0
- data/lib/qy_wechat_api/api/message.rb +123 -0
- data/lib/qy_wechat_api/api/oauth.rb +27 -0
- data/lib/qy_wechat_api/api/tag.rb +51 -0
- data/lib/qy_wechat_api/api/user.rb +56 -0
- data/lib/qy_wechat_api/carrierwave/qy_wechat_api_uploader.rb +5 -0
- data/lib/qy_wechat_api/client.rb +64 -0
- data/lib/qy_wechat_api/config.rb +20 -0
- data/lib/qy_wechat_api/handler.rb +3 -0
- data/lib/qy_wechat_api/handler/errors.rb +5 -0
- data/lib/qy_wechat_api/handler/global_code.rb +156 -0
- data/lib/qy_wechat_api/handler/result_handler.rb +51 -0
- data/lib/qy_wechat_api/storage/object_storage.rb +23 -0
- data/lib/qy_wechat_api/storage/redis_storage.rb +28 -0
- data/lib/qy_wechat_api/storage/storage.rb +75 -0
- data/lib/qy_wechat_api/version.rb +3 -0
- data/qy_wechat_api.gemspec +32 -0
- data/spec/api/media_spec.rb +72 -0
- data/spec/api/medias/favicon.ico +0 -0
- data/spec/api/medias/ruby-logo.jpg +0 -0
- data/spec/api/menu_spec.rb +60 -0
- data/spec/api/message_spec.rb +50 -0
- data/spec/spec_helper.rb +115 -0
- metadata +169 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 14663e1a3e66b15083a41484dab4a2cef0282d05
|
4
|
+
data.tar.gz: 6906d10cfc13429cc508e496022d622524b51c17
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: de9fe4bdcd7f1ad3c514876da2b746adcf0489f1de3c3ab7ffd3719dc66e7133617f0796cc8210560a8a70a9795be1db2402b269db7a92c2de2a73155d719b27
|
7
|
+
data.tar.gz: 99a2f042b72834f8dd5251d97e8f0f27f0d6bafbf8364e7cf63a378b0071f91f15b6b4f0f65e9eff176d0cd6ee0d970a59317c694a53c26365c9fe8bd37b9e4d
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
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
|
23
|
+
.DS_Store
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--format documentation --color spec --drb
|
data/Gemfile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
source 'https://rubygems.org'
|
2
|
+
|
3
|
+
group :test, :development do
|
4
|
+
gem 'rspec', '~> 3.1.0'
|
5
|
+
gem "redis-namespace", "~> 1.4.1"
|
6
|
+
gem "rake", "~> 0.9.6"
|
7
|
+
gem 'simplecov', '~> 0.7.1', :require => false
|
8
|
+
gem "codeclimate-test-reporter", require: nil
|
9
|
+
gem 'coveralls', require: false
|
10
|
+
# For debugger
|
11
|
+
gem "pry-rails", "~> 0.3.2"
|
12
|
+
|
13
|
+
gem "pry-debugger", "~> 0.2.2"
|
14
|
+
end
|
15
|
+
|
16
|
+
# Specify your gem's dependencies in qy_wechat_api.gemspec
|
17
|
+
gemspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 lanrion
|
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,120 @@
|
|
1
|
+
**企业号对应多个管理组,请前往 `设置` => `权限管理` 任意创建一个管理组,在管理组最下角即可获取 CorpID Secret**
|
2
|
+
|
3
|
+
**有问题请及时提issue**
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
gem "qy_wechat_api", git: "https://github.com/lanrion/qy_wechat_api.git"
|
7
|
+
```
|
8
|
+
|
9
|
+
# token 存储方案
|
10
|
+
|
11
|
+
## 对象存储
|
12
|
+
如果你是单个企业号,建议使用这个方案,无需任何配置即可使用。
|
13
|
+
|
14
|
+
## Redis 存储
|
15
|
+
```ruby
|
16
|
+
redis = Redis.new(host: "127.0.0.1", port: "6379")
|
17
|
+
|
18
|
+
namespace = "qy_wechat_api:redis_storage"
|
19
|
+
|
20
|
+
# cleanup keys in the current namespace when restart server everytime.
|
21
|
+
exist_keys = redis.keys("#{namespace}:*")
|
22
|
+
exist_keys.each{|key|redis.del(key)}
|
23
|
+
|
24
|
+
redis_with_ns = Redis::Namespace.new("#{namespace}", redis: redis)
|
25
|
+
|
26
|
+
QyWechatApi.configure do |config|
|
27
|
+
config.redis = redis_with_ns
|
28
|
+
end
|
29
|
+
```
|
30
|
+
|
31
|
+
## 自定义存储方案
|
32
|
+
TODO...
|
33
|
+
|
34
|
+
# API基本用法
|
35
|
+
|
36
|
+
请务必结合:http://qydev.weixin.qq.com/wiki/index.php 理解以下API参数使用。
|
37
|
+
|
38
|
+
## 初始化
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
group_client = QyWechatApi::Client.new(corpid, corpsecret)
|
42
|
+
# 为了确保用户输入的corpid, corpsecret是准确的,请务必执行:
|
43
|
+
group_client.is_valid?
|
44
|
+
```
|
45
|
+
|
46
|
+
## 部门
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
group_client.department.create(name, parent_id, order=nil)
|
50
|
+
group_client.department.update(id, name, parent_id, order=nil)
|
51
|
+
group_client.department.delete(id)
|
52
|
+
group_client.department.list
|
53
|
+
```
|
54
|
+
|
55
|
+
## 成员
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
group_client.user.create(user_id, name, options={})
|
59
|
+
group_client.user.update(user_id, options={})
|
60
|
+
group_client.user.delete(user_id)
|
61
|
+
group_client.user.get(user_id)
|
62
|
+
group_client.user.simple_list(department_id, fetch_child=nil, status=nil)
|
63
|
+
```
|
64
|
+
|
65
|
+
## 标签
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
group_client.tag.create(name)
|
69
|
+
group_client.tag.update(id, name)
|
70
|
+
group_client.tag.delete(id)
|
71
|
+
group_client.tag.get(id)
|
72
|
+
group_client.tag.add_tag_users(id, user_ids)
|
73
|
+
group_client.tag.delete_tag_users(id, user_ids)
|
74
|
+
group_client.tag.list
|
75
|
+
```
|
76
|
+
|
77
|
+
## 自定义菜单
|
78
|
+
|
79
|
+
menu_json的生成方法请参考:
|
80
|
+
https://github.com/lanrion/weixin_rails_middleware/wiki/DIY-menu
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
group_client.menu.create(menu_json, app_id)
|
84
|
+
group_client.menu.delete(app_id)
|
85
|
+
group_client.menu.get(app_id)
|
86
|
+
```
|
87
|
+
|
88
|
+
## Oauth2用法
|
89
|
+
|
90
|
+
先要配置你应用的 可信域名 `2458023e.ngrok.com`
|
91
|
+
state 为开发者自定义参数,可选
|
92
|
+
|
93
|
+
```ruby
|
94
|
+
# 生成授权url
|
95
|
+
group_client.oauth.authorize_url("http://2458023e.ngrok.com", "state")
|
96
|
+
|
97
|
+
# 获取code后,获取用户信息
|
98
|
+
# app_id: 跳转链接时所在的企业应用ID
|
99
|
+
group_client.oauth.get_user_info("code", "app_id")
|
100
|
+
```
|
101
|
+
|
102
|
+
## 发送消息
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
# params: (users, parties, tags, agent_id, content, safe=0)
|
106
|
+
# users, parties, tags 如果是多个用户,传数组,如果是全部,则直接传 "@all"
|
107
|
+
group_client.message.send_text("@all", "@all", "@all", app_id, text_message)
|
108
|
+
```
|
109
|
+
**其他发送消息方法请查看 api/message.rb**
|
110
|
+
|
111
|
+
## 上传多媒体文件
|
112
|
+
```ruby
|
113
|
+
# params: media, media_type
|
114
|
+
group_client.media.upload(image_jpg_file, "image")
|
115
|
+
|
116
|
+
# 获取下载链接
|
117
|
+
# 返回一个URL,请开发者自行使用此url下载
|
118
|
+
group_client.media.get_media_by_id(media_id)
|
119
|
+
```
|
120
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "rest-client"
|
4
|
+
require "carrierwave"
|
5
|
+
require 'yajl/json_gem'
|
6
|
+
|
7
|
+
require "qy_wechat_api/carrierwave/qy_wechat_api_uploader"
|
8
|
+
require "qy_wechat_api/config"
|
9
|
+
require "qy_wechat_api/client"
|
10
|
+
require "qy_wechat_api/handler"
|
11
|
+
require "qy_wechat_api/api"
|
12
|
+
|
13
|
+
module QyWechatApi
|
14
|
+
|
15
|
+
# Storage
|
16
|
+
autoload(:Storage, "qy_wechat_api/storage/storage")
|
17
|
+
autoload(:ObjectStorage, "qy_wechat_api/storage/object_storage")
|
18
|
+
autoload(:RedisStorage, "qy_wechat_api/storage/redis_storage")
|
19
|
+
|
20
|
+
ENDPOINT_URL = "https://qyapi.weixin.qq.com/cgi-bin"
|
21
|
+
OK_MSG = "ok".freeze
|
22
|
+
OK_CODE = 0.freeze
|
23
|
+
|
24
|
+
class << self
|
25
|
+
|
26
|
+
def http_get_without_token(url, params={})
|
27
|
+
get_api_url = ENDPOINT_URL + url
|
28
|
+
load_json(RestClient.get(get_api_url, params: params))
|
29
|
+
end
|
30
|
+
|
31
|
+
def http_post_without_token(url, payload={}, params={})
|
32
|
+
post_api_url = ENDPOINT_URL + url
|
33
|
+
payload = JSON.dump(payload) if !payload[:media].is_a?(File)
|
34
|
+
load_json(RestClient.post(post_api_url, payload, params: params))
|
35
|
+
end
|
36
|
+
|
37
|
+
# return hash
|
38
|
+
def load_json(string)
|
39
|
+
result_hash = JSON.parse(string)
|
40
|
+
code = result_hash.delete("errcode")
|
41
|
+
en_msg = result_hash.delete("errmsg")
|
42
|
+
ResultHandler.new(code, en_msg, result_hash)
|
43
|
+
end
|
44
|
+
|
45
|
+
def open_endpoint(url)
|
46
|
+
"https://open.weixin.qq.com#{url}"
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
require "qy_wechat_api/api/base"
|
2
|
+
require "qy_wechat_api/api/department"
|
3
|
+
require "qy_wechat_api/api/media"
|
4
|
+
require "qy_wechat_api/api/message"
|
5
|
+
require "qy_wechat_api/api/tag"
|
6
|
+
require "qy_wechat_api/api/user"
|
7
|
+
require "qy_wechat_api/api/menu"
|
8
|
+
require "qy_wechat_api/api/oauth"
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module QyWechatApi
|
4
|
+
module Api
|
5
|
+
class Base
|
6
|
+
attr_accessor :access_token, :corp_id
|
7
|
+
|
8
|
+
def initialize(access_token, corp_id=nil)
|
9
|
+
@access_token = access_token
|
10
|
+
@corp_id = corp_id
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def http_get(url, params={})
|
15
|
+
request_url = "#{base_url}/#{url}"
|
16
|
+
params = params.merge({access_token: access_token})
|
17
|
+
QyWechatApi.http_get_without_token(request_url, params )
|
18
|
+
end
|
19
|
+
|
20
|
+
def http_post(url, payload={}, params={})
|
21
|
+
request_url = "#{base_url}/#{url}"
|
22
|
+
params = params.merge({access_token: access_token})
|
23
|
+
QyWechatApi.http_post_without_token(request_url, payload, params)
|
24
|
+
end
|
25
|
+
|
26
|
+
def base_url
|
27
|
+
""
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module QyWechatApi
|
4
|
+
module Api
|
5
|
+
class Department < Base
|
6
|
+
|
7
|
+
# name 是 部门名称。长度限制为1~64个字符
|
8
|
+
# parentid 是 父亲部门id。根部门id为1
|
9
|
+
# order 否 在父部门中的次序。从1开始,数字越大排序越靠后
|
10
|
+
def create(name, parent_id, order=nil)
|
11
|
+
payload = {name: name}
|
12
|
+
payload[:parentid] = parent_id
|
13
|
+
payload[:order] = order if not order.nil?
|
14
|
+
http_post("create", payload)
|
15
|
+
end
|
16
|
+
|
17
|
+
# id 是 部门id
|
18
|
+
# name 否 更新的部门名称。长度限制为0~64个字符。修改部门名称时指定该参数
|
19
|
+
# parentid 否 父亲部门id。根部门id为1
|
20
|
+
# order 否 在父部门中的次序。从1开始,数字越大排序越靠后
|
21
|
+
def update(id, name=nil, parent_id=nil, order=nil)
|
22
|
+
payload = {id: id}
|
23
|
+
payload[:name] = name if not name.nil?
|
24
|
+
payload[:parentid] = parent_id if not parent_id.nil?
|
25
|
+
payload[:order] = order if not order.nil?
|
26
|
+
http_post("update", payload)
|
27
|
+
end
|
28
|
+
|
29
|
+
def delete(id)
|
30
|
+
http_get("delete", id: id)
|
31
|
+
end
|
32
|
+
|
33
|
+
def list
|
34
|
+
http_get("list")
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def base_url
|
40
|
+
"/department"
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module QyWechatApi
|
4
|
+
module Api
|
5
|
+
class Media < Base
|
6
|
+
|
7
|
+
# 媒体文件类型,分别有图片(image)、语音(voice)、视频(video),普通文件(file)
|
8
|
+
# media: 支持传路径或者文件实例
|
9
|
+
def upload(media, media_type)
|
10
|
+
file = process_file(media)
|
11
|
+
http_post("upload", {media: file}, {type: media_type})
|
12
|
+
end
|
13
|
+
|
14
|
+
# 返回一个URL,请开发者自行使用此url下载
|
15
|
+
def get_media_by_id(media_id)
|
16
|
+
http_get("get", {media_id: media_id})
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def base_url
|
22
|
+
"/media"
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_file(media)
|
26
|
+
return media if media.is_a?(File) && jpep?(media)
|
27
|
+
|
28
|
+
media_url = media
|
29
|
+
uploader = QyWechatApiUploader.new
|
30
|
+
|
31
|
+
if http?(media_url) # remote
|
32
|
+
uploader.download!(media_url.to_s)
|
33
|
+
else # local
|
34
|
+
media_file = media.is_a?(File) ? media : File.new(media_url)
|
35
|
+
uploader.cache!(media_file)
|
36
|
+
end
|
37
|
+
file = process_media(uploader)
|
38
|
+
CarrierWave.clean_cached_files! # clear last one day cache
|
39
|
+
file
|
40
|
+
end
|
41
|
+
|
42
|
+
def process_media(uploader)
|
43
|
+
uploader = covert(uploader)
|
44
|
+
uploader.file.to_file
|
45
|
+
end
|
46
|
+
|
47
|
+
# JUST ONLY FOR JPG IMAGE
|
48
|
+
def covert(uploader)
|
49
|
+
# image process
|
50
|
+
unless (uploader.file.content_type =~ /image/).nil?
|
51
|
+
if !jpep?(uploader.file)
|
52
|
+
require "mini_magick"
|
53
|
+
# covert to jpeg
|
54
|
+
image = MiniMagick::Image.open(uploader.path)
|
55
|
+
image.format("jpg")
|
56
|
+
uploader.cache!(File.open(image.path))
|
57
|
+
image.destroy! # remove /tmp from MinMagick generate
|
58
|
+
end
|
59
|
+
end
|
60
|
+
uploader
|
61
|
+
end
|
62
|
+
|
63
|
+
def http?(uri)
|
64
|
+
return false if !uri.is_a?(String)
|
65
|
+
uri = URI.parse(uri)
|
66
|
+
uri.scheme =~ /^https?$/
|
67
|
+
end
|
68
|
+
|
69
|
+
def jpep?(file)
|
70
|
+
content_type = if file.respond_to?(:content_type)
|
71
|
+
file.content_type
|
72
|
+
else
|
73
|
+
content_type(file.path)
|
74
|
+
end
|
75
|
+
!(content_type =~ /jpeg/).nil?
|
76
|
+
end
|
77
|
+
|
78
|
+
def content_type(media_path)
|
79
|
+
MIME::Types.type_for(media_path).first.content_type
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module QyWechatApi
|
3
|
+
module Api
|
4
|
+
class Menu < Base
|
5
|
+
|
6
|
+
def create(menu, agent_id)
|
7
|
+
menu = JSON.load(menu) if menu.is_a?(String)
|
8
|
+
http_post("create", menu, {agentid: agent_id})
|
9
|
+
end
|
10
|
+
|
11
|
+
def delete(agent_id)
|
12
|
+
http_get("delete", {agentid: agent_id})
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(agent_id)
|
16
|
+
http_get("get", {agentid: agent_id})
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def base_url
|
22
|
+
"/menu"
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|