dingtalk 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5c09a5bf48e4218e9d13e279d06c8cfd48a86a2e
4
+ data.tar.gz: e9c87c1c241cd7720c842ef4f58050f63ba2ffce
5
+ SHA512:
6
+ metadata.gz: da08a027f6ed2fb3a7fec2d742bba04ac2e2ec2065419bb8656ad79486896dfac7285d015aff1e7b7db78cd6428124a1fd264e664291d6da021723091b151cbe
7
+ data.tar.gz: ecd25008d1d7344ffe2b6f160a5edf770b460ac2c7c70f5de95e2a3043028f6b8c07558b67ddc7479a9b273831d2578d6c29f35461ac6941706bb622348a8cde
@@ -0,0 +1,20 @@
1
+ Copyright 2017 ysllyfe
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,28 @@
1
+ # Dingtalk
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'dingtalk'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install dingtalk
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,33 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Dingtalk'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+
33
+ task default: :test
@@ -0,0 +1,77 @@
1
+ require "rest-client"
2
+ require "redis"
3
+ require "json"
4
+
5
+ require "dingtalk/version"
6
+ require "dingtalk/config"
7
+ require "dingtalk/handler"
8
+ require "dingtalk/api"
9
+ require "dingtalk/eco_api"
10
+ require "dingtalk/client"
11
+
12
+ module Dingtalk
13
+
14
+ module Token
15
+ autoload(:Store, "dingtalk/token/store")
16
+ autoload(:ObjectStore, "dingtalk/token/object_store")
17
+ autoload(:RedisStore, "dingtalk/token/redis_store")
18
+ end
19
+
20
+ OK_MSG = "ok".freeze
21
+ OK_CODE = 0.freeze
22
+ CUSTOM_ENDPOINT = "custom_endpoint".freeze
23
+ ECO_ENDPOINT = 'eco'.freeze
24
+
25
+ class << self
26
+ attr_accessor :current_endpoint
27
+
28
+ def eco?
29
+ @current_endpoint == ECO_ENDPOINT
30
+ end
31
+
32
+ def http_get_without_token(url, url_params={}, endpoint="api")
33
+ @current_endpoint = endpoint
34
+ load_json(resource(url).get(params: url_params), url)
35
+ end
36
+
37
+ def http_post_without_token(url, _post_body={}, url_params={}, endpoint="api")
38
+ @current_endpoint = endpoint
39
+ load_json(resource(url).post(JSON.dump(_post_body), params: url_params, content_type: :json), url)
40
+ end
41
+
42
+ def resource(url)
43
+ RestClient::Resource.new(endpoint_url(url), rest_client_options)
44
+ end
45
+
46
+ def load_json(string, url)
47
+ result_hash = JSON.parse(string.force_encoding("UTF-8").gsub(/[\u0011-\u001F]/, ""))
48
+ if eco?
49
+ EcoResultHandler.new(url, result_hash)
50
+ else
51
+ code = result_hash.delete("errcode")
52
+ en_msg = result_hash.delete("errmsg")
53
+ ResultHandler.new(code, en_msg, result_hash)
54
+ end
55
+ end
56
+
57
+ def endpoint_url(url)
58
+ if eco?
59
+ eco_endpoint
60
+ else
61
+ "#{api_endpoint}" + url
62
+ end
63
+ end
64
+
65
+ def api_endpoint
66
+ "https://oapi.dingtalk.com"
67
+ end
68
+
69
+ def eco_endpoint
70
+ "https://eco.taobao.com/router/rest"
71
+ end
72
+
73
+ def calculate_expire(expires_in)
74
+ Time.now.to_i + expires_in.to_i - key_expired.to_i
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/api/*.rb"].each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,21 @@
1
+ module Dingtalk
2
+ module Api
3
+ module Department
4
+ # https://oapi.dingtalk.com/department/list?access_token=ACCESS_TOKEN
5
+ def list_department
6
+ list_url = "#{department_base_url}/list"
7
+ http_get(list_url)
8
+ end
9
+
10
+ def get_department(id)
11
+ get_url = "#{department_base_url}/get"
12
+ http_get(get_url, {id: id})
13
+ end
14
+
15
+ private
16
+ def department_base_url
17
+ "/department"
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ module Dingtalk
2
+ module Api
3
+ module Message
4
+ def send_text(to_users = [], agentid = nil, message)
5
+ send_message(to_users, agentid, :msgtype => "text", :text => { :content => message})
6
+ end
7
+
8
+ def send_link(to_users = [], agentid = nil, opts = {})
9
+ # opts :messageUrl=>"",:picUrl=>"",:title=>"",:text=>""
10
+ send_message(to_users, agentid, :msgtype => "link", :link => opts)
11
+ end
12
+
13
+ def send_oa(to_users = [], agentid = nil)
14
+
15
+ end
16
+
17
+ private
18
+ def send_message(to_users = [], agentid = nil, opts = {})
19
+ url = "#{message_base_url}/send"
20
+ body = {:touser=>to_users.join('|'), :agentid=>agentid}.merge(opts)
21
+ http_post(url, body)
22
+ end
23
+
24
+ def message_base_url
25
+ "/message"
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ module Dingtalk
2
+ module Api
3
+ module Microapp
4
+ def microapp_visible_scopes(agent_id)
5
+ url = "#{microapp_base_url}/visible_scopes"
6
+ http_post(url, {agentid: agent_id})
7
+ end
8
+
9
+ private
10
+ def microapp_base_url
11
+ "/microapp"
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ module Dingtalk
2
+ module Api
3
+ module User
4
+ def user_simplelist(department_id, opt={})
5
+ simplelist_url = "#{user_base_url}/simplelist"
6
+ http_get(simplelist_url, opt.merge(department_id: department_id))
7
+ end
8
+ private
9
+ def user_base_url
10
+ "/user"
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,61 @@
1
+ require "monitor"
2
+ require "redis"
3
+ require 'digest/md5'
4
+ module Dingtalk
5
+ class Client
6
+ include MonitorMixin
7
+ include Api::Department
8
+ include Api::User
9
+ include Api::Microapp
10
+ include Api::Message
11
+
12
+ include EcoApi::Role
13
+
14
+ attr_accessor :app_id, :app_secret, :expired_at # Time.now + expires_in
15
+ attr_accessor :access_token, :redis_key
16
+
17
+ def initialize(app_id, app_secret, options={})
18
+ @app_id = app_id
19
+ @app_secret = app_secret
20
+ @expired_at = Time.now.to_i
21
+ @redis_key = security_redis_key(options[:redis_key] || "dingtalk_#{app_id}")
22
+ super()
23
+ end
24
+
25
+ def get_access_token
26
+ synchronize{ token_store.access_token }
27
+ end
28
+
29
+ def is_valid?
30
+ token_store.valid?
31
+ end
32
+
33
+ def token_store
34
+ Token::Store.init_with(self)
35
+ end
36
+
37
+ def http_get(url, url_params={}, endpoint="plain")
38
+ url_params = endpoint == Dingtalk::ECO_ENDPOINT ? url_params.merge(eco_access_token_param) : url_params.merge(access_token_param)
39
+ Dingtalk.http_get_without_token(url, url_params, endpoint)
40
+ end
41
+
42
+ def http_post(url, post_body={}, url_params={}, endpoint="plain")
43
+ url_params = endpoint == Dingtalk::ECO_ENDPOINT ? eco_access_token_param.merge(url_params) : access_token_param.merge(url_params)
44
+ Dingtalk.http_post_without_token(url, post_body, url_params, endpoint)
45
+ end
46
+
47
+ private
48
+
49
+ def eco_access_token_param
50
+ {session: get_access_token, timestamp: Time.now.strftime("%Y-%m-%d %H:%M:%S"), format: 'json', v: '2.0'}
51
+ end
52
+
53
+ def access_token_param
54
+ {access_token: get_access_token}
55
+ end
56
+
57
+ def security_redis_key(key)
58
+ Digest::MD5.hexdigest(key.to_s).upcase
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,29 @@
1
+ module Dingtalk
2
+ class << self
3
+ attr_accessor :config
4
+ def configure
5
+ yield self.config ||= Config.new
6
+ end
7
+
8
+ def redis
9
+ return nil if config.nil?
10
+ @redis ||= config.redis
11
+ end
12
+
13
+ def key_expired
14
+ return 100 if config.nil?
15
+ config.key_expired || 100
16
+ end
17
+
18
+ def rest_client_options
19
+ if config.nil?
20
+ return {timeout: 5, open_timeout: 5, verify_ssl: true}
21
+ end
22
+ config.rest_client_options
23
+ end
24
+ end
25
+
26
+ class Config
27
+ attr_accessor :redis, :rest_client_options, :key_expired
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ Dir["#{File.dirname(__FILE__)}/eco_api/*.rb"].each do |path|
2
+ require path
3
+ end
@@ -0,0 +1,13 @@
1
+ module Dingtalk
2
+ module EcoApi
3
+ module Role
4
+ def role_list
5
+ http_get('dingtalk.corp.role.list', {method: 'dingtalk.corp.role.list'}, 'eco')
6
+ end
7
+
8
+ def role_simplelist(role_id)
9
+ http_get('dingtalk.corp.role.simplelist', {method: 'dingtalk.corp.role.simplelist', role_id: role_id}, 'eco')
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ require "dingtalk/handler/global_code"
2
+ require "dingtalk/handler/result_handler"
3
+ require "dingtalk/handler/eco_result_handler"
@@ -0,0 +1,56 @@
1
+ module Dingtalk
2
+ class EcoResultHandler
3
+ attr_accessor :code, :cn_msg, :en_msg, :result, :response_name
4
+
5
+ def initialize(m, response={})
6
+ @response_name = "#{m.gsub(/\./, '_')}_response"
7
+ @response = package_result(response)
8
+ @result = @response[@response_name]
9
+ end
10
+
11
+ def is_ok?
12
+ @response[:error_response].nil?
13
+ end
14
+ alias_method :ok?, :is_ok?
15
+
16
+ def code
17
+ @response[:error_response][:code]
18
+ end
19
+
20
+ def en_msg
21
+ @response[:error_response][:msg]
22
+ end
23
+
24
+ def cn_msg
25
+ @response[:error_response][:sub_msg]
26
+ end
27
+
28
+ def sub_code
29
+ @response[:error_response][:sub_code]
30
+ end
31
+
32
+ def full_message
33
+ if is_ok?
34
+ "SUCCESS, Please use #result to get result."
35
+ else
36
+ "#{code}: #{en_msg}(#{sub_code}:#{cn_msg})."
37
+ end
38
+ end
39
+ alias_method :full_messages, :full_message
40
+
41
+ def full_error_message
42
+ full_message if !is_ok?
43
+ end
44
+ alias_method :full_error_messages, :full_error_message
45
+
46
+ private
47
+ def package_result(response)
48
+ return response if !response.is_a?(Hash)
49
+ if defined?(Rails)
50
+ ActiveSupport::HashWithIndifferentAccess.new(response)
51
+ else
52
+ response
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,319 @@
1
+ module Dingtalk
2
+ GLOBAL_CODES = {
3
+ -1 => "系统繁忙",
4
+ 0 => "请求成功",
5
+ 404 => "请求的URI地址不存在",
6
+ 33001 => "无效的企业ID",
7
+ 33002 => "无效的微应用的名称",
8
+ 33003 => "无效的微应用的描述",
9
+ 33004 => "无效的微应用的ICON",
10
+ 33005 => "无效的微应用的移动端主页",
11
+ 33006 => "无效的微应用的PC端主页",
12
+ 33007 => "微应用的移动端的主页与PC端主页不同",
13
+ 33008 => "无效的微应用OA后台的主页",
14
+ 34001 => "无效的会话id",
15
+ 34002 => "无效的会话消息的发送者",
16
+ 34003 => "无效的会话消息的发送者的企业Id",
17
+ 34004 => "无效的会话消息的类型",
18
+ 34005 => "无效的会话音频消息的播放时间",
19
+ 34006 => "发送者不在企业中",
20
+ 34007 => "发送者不在会话中",
21
+ 34008 => "图片不能为空",
22
+ 34009 => "链接内容不能为空",
23
+ 34010 => "文件不能为空",
24
+ 34011 => "音频文件不能为空",
25
+ 34012 => "找不到发送者的企业",
26
+ 34013 => "找不到群会话对象",
27
+ 34014 => "会话消息的json结构无效或不完整",
28
+ 34015 => "发送群会话消息失败",
29
+ 34016 => "消息内容长度超过限制",
30
+ 40001 => "获取access_token时Secret错误,或者access_token无效",
31
+ 40002 => "不合法的凭证类型",
32
+ 40003 => "不合法的UserID",
33
+ 40004 => "不合法的媒体文件类型",
34
+ 40005 => "不合法的文件类型",
35
+ 40006 => "不合法的文件大小",
36
+ 40007 => "不合法的媒体文件id",
37
+ 40008 => "不合法的消息类型",
38
+ 40009 => "不合法的部门id",
39
+ 40010 => "不合法的父部门id",
40
+ 40011 => "不合法的排序order",
41
+ 40012 => "不合法的发送者",
42
+ 40013 => "不合法的corpid",
43
+ 40014 => "不合法的access_token",
44
+ 40015 => "发送者不在会话中",
45
+ 40016 => "不合法的会话ID",
46
+ 40017 => "在会话中没有找到与发送者在同一企业的人",
47
+ 40018 => "不允许以递归方式查询部门用户列表",
48
+ 40019 => "该手机号码对应的用户最多可以加入5个非认证企业",
49
+ 40020 => "当前团队人数已经达到上限,用电脑登录钉钉企业管理后台,升级成为认证企业",
50
+ 40021 => "更换的号码已注册过钉钉,无法使用该号码",
51
+ 40022 => "企业中的手机号码和登陆钉钉的手机号码不一致,暂时不支持修改用户信息,可以删除后重新添加",
52
+ 40023 => "部门人数达到上限",
53
+ 40024 => "(安全校验不通过)保存失败,团队人数超限。请在手机钉钉绑定支付宝完成实名认证,或者申请企业认证,人数上限自动扩充。",
54
+ 40025 => "无效的部门JSONArray对象,合法格式需要用中括号括起来,且如果属于多部门,部门id需要用逗号分隔",
55
+ 40029 => "不合法的oauth_code",
56
+ 40031 => "不合法的UserID列表",
57
+ 40032 => "不合法的UserID列表长度",
58
+ 40033 => "不合法的请求字符,不能包含\\uxxxx格式的字符",
59
+ 40035 => "不合法的参数",
60
+ 40038 => "不合法的请求格式",
61
+ 40039 => "不合法的URL长度",
62
+ 40048 => "url中包含不合法domain",
63
+ 40055 => "不合法的agent结构",
64
+ 40056 => "不合法的agentid",
65
+ 40057 => "不合法的callbackurl",
66
+ 40061 => "设置应用头像失败",
67
+ 40062 => "不合法的应用模式",
68
+ 40063 => "不合法的分机号",
69
+ 40064 => "不合法的工作地址",
70
+ 40065 => "不合法的备注",
71
+ 40066 => "不合法的部门列表",
72
+ 40067 => "标题长度不合法",
73
+ 40068 => "不合法的偏移量",
74
+ 40069 => "不合法的分页大小",
75
+ 40070 => "不合法的排序参数",
76
+ 40073 => "不存在的openid",
77
+ 40077 => "不存在的预授权码",
78
+ 40078 => "不存在的临时授权码",
79
+ 40079 => "不存在的授权信息",
80
+ 40080 => "不合法的suitesecret",
81
+ 40082 => "不合法的suitetoken",
82
+ 40083 => "不合法的suiteid",
83
+ 40084 => "不合法的永久授权码",
84
+ 40085 => "不存在的suiteticket",
85
+ 40086 => "不合法的第三方应用appid",
86
+ 40087 => "创建永久授权码失败",
87
+ 40088 => "不合法的套件key或secret",
88
+ 40089 => "不合法的corpid或corpsecret",
89
+ 40090 => "套件已经不存在",
90
+ 40091 => "用户授权码创建失败,需要用户重新授权",
91
+ 41001 => "缺少access_token参数",
92
+ 41002 => "缺少corpid参数",
93
+ 41003 => "缺少refresh_token参数",
94
+ 41004 => "缺少secret参数",
95
+ 41005 => "缺少多媒体文件数据",
96
+ 41006 => "缺少media_id参数",
97
+ 41007 => "无效的ssocode",
98
+ 41008 => "缺少oauth",
99
+ 41009 => "缺少UserID",
100
+ 41010 => "缺少url",
101
+ 41011 => "缺少agentid",
102
+ 41012 => "缺少应用头像mediaid",
103
+ 41013 => "缺少应用名字",
104
+ 41014 => "缺少应用描述",
105
+ 41015 => "缺少Content",
106
+ 41016 => "缺少标题",
107
+ 41021 => "缺少suitekey",
108
+ 41022 => "缺少suitetoken",
109
+ 41023 => "缺少suiteticket",
110
+ 41024 => "缺少suitesecret",
111
+ 41025 => "缺少permanent_code",
112
+ 41026 => "缺少tmp_auth_code",
113
+ 41027 => "需要授权企业的corpid参数",
114
+ 41028 => "禁止给全员发送消息",
115
+ 41029 => "超过消息接收者人数上限",
116
+ 41030 => "企业未对该套件授权",
117
+ 41031 => "auth_corpid和permanent_code不匹配",
118
+ 41044 => "禁止发送消息",
119
+ 41045 => "超过发送全员消息的次数上限",
120
+ 41046 => "超过发送全员消息的每分钟次数上限",
121
+ 41047 => "超过给该企业发消息的每分钟次数上限",
122
+ 41048 => "超过给企业发消息的每分钟次数总上限",
123
+ 41049 => "包含违禁内容",
124
+ 41050 => "无效的活动编码",
125
+ 41051 => "活动权益的校验失败",
126
+ 41100 => "时间参数不合法",
127
+ 41101 => "数据内容过长",
128
+ 41102 => "参数值过大",
129
+ 42001 => "access_token超时",
130
+ 42002 => "refresh_token超时",
131
+ 42003 => "oauth_code超时",
132
+ 42007 => "预授权码失效",
133
+ 42008 => "临时授权码失效",
134
+ 42009 => "suitetoken失效",
135
+ 43001 => "需要GET请求",
136
+ 43002 => "需要POST请求",
137
+ 43003 => "需要HTTPS",
138
+ 43004 => "无效的HTTP HEADER Content-Type",
139
+ 43005 => "需要Content-Type为application/json;charset=UTF-8",
140
+ 43007 => "需要授权",
141
+ 43008 => "参数需要multipart类型",
142
+ 43009 => "post参数需要json类型",
143
+ 43010 => "需要处于回调模式",
144
+ 43011 => "需要企业授权",
145
+ 44001 => "多媒体文件为空",
146
+ 44002 => "POST的数据包为空",
147
+ 44003 => "图文消息内容为空",
148
+ 44004 => "文本消息内容为空",
149
+ 45001 => "多媒体文件大小超过限制",
150
+ 45002 => "消息内容超过限制",
151
+ 45003 => "标题字段超过限制",
152
+ 45004 => "描述字段超过限制",
153
+ 45005 => "链接字段超过限制",
154
+ 45006 => "图片链接字段超过限制",
155
+ 45007 => "语音播放时间超过限制",
156
+ 45008 => "图文消息超过限制",
157
+ 45009 => "接口调用超过限制",
158
+ 45016 => "系统分组,不允许修改",
159
+ 45017 => "分组名字过长",
160
+ 45018 => "分组数量超过上限",
161
+ 45024 => "账号数量超过上限",
162
+ 46001 => "不存在媒体数据",
163
+ 46004 => "不存在的员工",
164
+ 47001 => "解析JSON/XML内容错误",
165
+ 48002 => "Api禁用",
166
+ 48003 => "suitetoken无效",
167
+ 48004 => "授权关系无效",
168
+ 49000 => "缺少chatid",
169
+ 49001 => "绑定的微应用超过个数限制",
170
+ 49002 => "一个群只能被一个ISV套件绑定一次",
171
+ 49003 => "操作者必须为群主",
172
+ 49004 => "添加成员列表和删除成员列表不能有交集",
173
+ 49005 => "群人数超过人数限制",
174
+ 49006 => "群成员列表必须包含群主",
175
+ 49007 => "超过创建群的个数上限",
176
+ 50001 => "redirect_uri未授权",
177
+ 50002 => "员工不在权限范围",
178
+ 50003 => "应用已停用",
179
+ 50005 => "企业已禁用",
180
+ 51000 => "跳转的域名未授权",
181
+ 51001 => "跳转的corpid未授权",
182
+ 51002 => "跳转请求不是来自钉钉客户端",
183
+ 51003 => "跳转找不到用户信息",
184
+ 51004 => "跳转找不到用户的企业信息",
185
+ 51005 => "跳转用户不是企业管理员",
186
+ 51006 => "跳转生成code失败",
187
+ 51007 => "跳转获取用户企业身份失败",
188
+ 51008 => "跳转url解码失败",
189
+ 51009 => "要跳转的地址不是标准url",
190
+ 52010 => "无效的corpid",
191
+ 52011 => "jsapi ticket 读取失败",
192
+ 52012 => "jsapi 签名生成失败",
193
+ 52013 => "签名校验失败",
194
+ 52014 => "无效的url参数",
195
+ 52015 => "无效的随机字符串参数",
196
+ 52016 => "无效的签名参数",
197
+ 52017 => "无效的jsapi列表参数",
198
+ 52018 => "无效的时间戳",
199
+ 52019 => "无效的agentid",
200
+ 60001 => "不合法的部门名称",
201
+ 60002 => "部门层级深度超过限制",
202
+ 60003 => "部门不存在",
203
+ 60004 => "父亲部门不存在",
204
+ 60005 => "不允许删除有成员的部门",
205
+ 60006 => "不允许删除有子部门的部门",
206
+ 60007 => "不允许删除根部门",
207
+ 60008 => "父部门下该部门名称已存在",
208
+ 60009 => "部门名称含有非法字符",
209
+ 60010 => "部门存在循环关系",
210
+ 60011 => "管理员权限不足,(user/department/agent)无权限",
211
+ 60012 => "不允许删除默认应用",
212
+ 60013 => "不允许关闭应用",
213
+ 60014 => "不允许开启应用",
214
+ 60015 => "不允许修改默认应用可见范围",
215
+ 60016 => "部门id已经存在",
216
+ 60017 => "不允许设置企业",
217
+ 60018 => "不允许更新根部门",
218
+ 60019 => "从部门查询人员失败",
219
+ 60020 => "访问ip不在白名单之中",
220
+ 60066 => "企业的设置不存在",
221
+ 60067 => "部门的企业群群主不存在",
222
+ 60068 => "部门的管理员不存在",
223
+ 60102 => "UserID在公司中已存在",
224
+ 60103 => "手机号码不合法",
225
+ 60104 => "手机号码在公司中已存在",
226
+ 60105 => "邮箱不合法",
227
+ 60106 => "邮箱已存在",
228
+ 60107 => "使用该手机登录钉钉的用户已经在企业中",
229
+ 60110 => "部门个数超出限制",
230
+ 60111 => "UserID不存在",
231
+ 60112 => "用户name不合法",
232
+ 60113 => "身份认证信息(手机/邮箱)不能同时为空",
233
+ 60114 => "性别不合法",
234
+ 60118 => "用户无有效邀请字段(邮箱,手机号)",
235
+ 60119 => "不合法的position",
236
+ 60120 => "用户已禁用",
237
+ 60121 => "找不到该用户",
238
+ 60122 => "不合法的extattr",
239
+ 60123 => "不合法的jobnumber",
240
+ 60124 => "用户不在此群中",
241
+ 60125 => "CRM配置信息创建失败",
242
+ 60126 => "CRM配置信息更新失败",
243
+ 60127 => "CRM人员配置信息删除失败",
244
+ 70001 => "企业不存在或者已经被解散",
245
+ 70002 => "获取套件下的微应用失败",
246
+ 70003 => "agentid对应微应用不存在",
247
+ 70004 => "企业下没有对应该agentid的微应用",
248
+ 70005 => "ISV激活套件失败",
249
+ 71006 => "回调地址已经存在",
250
+ 71007 => "回调地址已不存在",
251
+ 71008 => "回调call_back_tag必须在指定的call_back_tag列表中",
252
+ 71009 => "返回文本非success",
253
+ 71010 => "POST的JSON数据不包含所需要的参数字段或包含的参数格式非法",
254
+ 71011 => "传入的url参数不是合法的url格式",
255
+ 71012 => "url地址访问异常,错误原因为:%s",
256
+ 71013 => "此域名或IP不能注册或者接收回调事件",
257
+ 72001 => "获取钉盘空间失败",
258
+ 72002 => "授权钉盘空间访问权限失败",
259
+ 80001 => "可信域名没有IPC备案,后续将不能在该域名下正常使用jssdk",
260
+ 81001 => "两个用户没有任何关系,请先相互成为好友",
261
+ 81002 => "用户拒收消息",
262
+ 88005 => "管理日历个人日历操作失败",
263
+ 89001 => "管理日历启动导出任务失败",
264
+ 89011 => "管理日历写入数据失败",
265
+ 89012 => "管理日历更新数据失败",
266
+ 90001 => "您的服务器调用钉钉开放平台所有接口的请求都被暂时禁用了",
267
+ 90002 => "您的服务器调用钉钉开放平台当前接口的所有请求都被暂时禁用了",
268
+ 90003 => "您的企业调用钉钉开放平台所有接口的请求都被暂时禁用了,仅对企业自己的Accesstoken有效",
269
+ 90004 => "您当前使用的CorpId及CorpSecret被暂时禁用了,仅对企业自己的Accesstoken有效",
270
+ 90005 => "您的企业调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效",
271
+ 90006 => "您当前使用的CorpId及CorpSecret调用当前接口次数过多,请求被暂时禁用了,仅对企业自己的Accesstoken有效",
272
+ 90007 => "您当前要调用的企业的接口次数过多,对该企业的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
273
+ 90008 => "您当前要调用的企业的当前接口次数过多,对此企业下该接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
274
+ 90009 => "您调用企业接口超过了限制,对所有企业的所有接口的请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
275
+ 90010 => "您调用企业当前接口超过了限制,对所有企业的该接口的请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
276
+ 90011 => "您的套件调用企业接口超过了限制,该套件的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
277
+ 90012 => "您的套件调用企业当前接口超过了限制,该套件对此接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
278
+ 90013 => "您的套件调用当前企业的接口超过了限制,该套件对此企业的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
279
+ 90014 => "您的套件调用企业当前接口超过了限制,该套件对此企业该接口的所有请求都被暂时禁用了,仅对企业授权给ISV的Accesstoken有效",
280
+ 900001 => "加密明文文本非法",
281
+ 900002 => "加密时间戳参数非法",
282
+ 900003 => "加密随机字符串参数非法",
283
+ 900004 => "不合法的aeskey",
284
+ 900005 => "签名不匹配",
285
+ 900006 => "计算签名错误",
286
+ 900007 => "计算加密文字错误",
287
+ 900008 => "计算解密文字错误",
288
+ 900009 => "计算解密文字长度不匹配",
289
+ 900010 => "计算解密文字corpid不匹配",
290
+ 420001 => "客户不存在",
291
+ 420002 => "客户查询失败",
292
+ 420003 => "联系人不存在",
293
+ 420004 => "联系人查询失败",
294
+ 420005 => "客户删除失败",
295
+ 420006 => "联系人删除失败",
296
+ 420007 => "跟进人绑定失败",
297
+ 420008 => "客户id非法",
298
+ 420009 => "跟进人id非法",
299
+ 4200010 => "客户联系人id非法",
300
+ 4200011 => "客户描述表单不存在",
301
+ 4200012 => "客户描述表单查询失败",
302
+ 4200013 => "联系人描述表单不存在",
303
+ 4200014 => "联系人描述表单查询失败",
304
+ 4200015 => "客户描述表单格式校验错误",
305
+ 4200016 => "客户描述表单格缺少固定字段",
306
+ 4200017 => "客户联系人描述表单格式校验错误",
307
+ 4200018 => "客户联系人描述表单格缺少固定字段",
308
+ 4200019 => "客户描述表单数据格式校验错误",
309
+ 4200020 => "客户描述表单数据缺少固定字段",
310
+ 4200021 => "客户联系人描述表单数据格式校验错误",
311
+ 4200022 => "客户联系人描述表单数据缺少固定字段",
312
+ 800001 => "仅限ISV调用",
313
+ 41042 => "加密失败",
314
+ 41043 => "解密失败",
315
+ 40100 => "分机号已经存在",
316
+ 40101 => "邮箱已经存在",
317
+ 50004 => "企业部门不在授权范围"
318
+ } unless defined?(GLOBAL_CODES)
319
+ end
@@ -0,0 +1,37 @@
1
+ module Dingtalk
2
+ class ResultHandler
3
+ attr_accessor :code, :cn_msg, :en_msg, :result
4
+
5
+ def initialize(code, en_msg, result={})
6
+ @code = code || OK_CODE
7
+ @en_msg = en_msg || OK_MSG
8
+ @cn_msg = GLOBAL_CODES[@code.to_i]
9
+ @result = package_result(result)
10
+ end
11
+
12
+ def is_ok?
13
+ code == OK_CODE
14
+ end
15
+ alias_method :ok?, :is_ok?
16
+
17
+ def full_message
18
+ "#{code}: #{en_msg}(#{cn_msg})."
19
+ end
20
+ alias_method :full_messages, :full_message
21
+
22
+ def full_error_message
23
+ full_message if !is_ok?
24
+ end
25
+ alias_method :full_error_messages, :full_error_message
26
+
27
+ private
28
+ def package_result(result)
29
+ return result if !result.is_a?(Hash)
30
+ if defined?(Rails)
31
+ ActiveSupport::HashWithIndifferentAccess.new(result)
32
+ else
33
+ result
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+ module Dingtalk
3
+ module Token
4
+ class ObjectStore < Store
5
+
6
+ def valid?
7
+ super
8
+ end
9
+
10
+ def token_expired?
11
+ # 如果当前token过期时间小于现在的时间,则重新获取一次
12
+ client.expired_at <= Time.now.to_i
13
+ end
14
+
15
+ def refresh_token
16
+ super
17
+ end
18
+
19
+ def access_token
20
+ super
21
+ client.access_token
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ module Dingtalk
2
+ module Token
3
+ class RedisStore < Store
4
+
5
+ def valid?
6
+ redis.del(client.redis_key)
7
+ super
8
+ end
9
+
10
+ def token_expired?
11
+ redis.hvals(client.redis_key).empty?
12
+ end
13
+
14
+ def refresh_token
15
+ super
16
+ redis.hmset(
17
+ client.redis_key, "access_token",
18
+ client.access_token, "expired_at",
19
+ client.expired_at
20
+ )
21
+ redis.expireat(client.redis_key, client.expired_at.to_i)
22
+ end
23
+
24
+ def access_token
25
+ super
26
+ client.access_token = redis.hget(client.redis_key, "access_token")
27
+ client.expired_at = redis.hget(client.redis_key, "expired_at")
28
+ client.access_token
29
+ end
30
+
31
+ def redis
32
+ Dingtalk.redis
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,69 @@
1
+ module Dingtalk
2
+ module Token
3
+ class Store
4
+ attr_accessor :client
5
+ def initialize(client)
6
+ @client = client
7
+ end
8
+
9
+ def self.init_with(client)
10
+ if Dingtalk.redis.nil?
11
+ ObjectStore.new(client)
12
+ else
13
+ RedisStore.new(client)
14
+ end
15
+ end
16
+
17
+ def valid?
18
+ authenticate["valid"]
19
+ end
20
+
21
+ def refresh_token
22
+ handle_valid_exception
23
+ set_access_token
24
+ end
25
+
26
+ def authenticate
27
+ auth_result = http_get_access_token
28
+ auth = false
29
+ if auth_result.is_ok?
30
+ set_access_token(auth_result.result)
31
+ auth = true
32
+ end
33
+ {"valid" => auth, "handler" => auth_result}
34
+ end
35
+
36
+ def access_token
37
+ refresh_token if token_expired?
38
+ end
39
+
40
+ def set_access_token(access_token_infos=nil)
41
+ token_infos = access_token_infos || http_get_access_token.result
42
+ client.access_token = token_infos["access_token"]
43
+ client.expired_at = Dingtalk.calculate_expire(token_infos["expires_in"])
44
+ end
45
+
46
+ def token_expired?
47
+ raise NotImplementedError, "Subclasses must implement a token_expired? method"
48
+ end
49
+
50
+ def http_get_access_token
51
+ Dingtalk.http_get_without_token("/gettoken", authenticate_headers)
52
+ end
53
+
54
+ def authenticate_headers
55
+ {corpid: client.app_id, corpsecret: client.app_secret}
56
+ end
57
+
58
+ private
59
+
60
+ def handle_valid_exception
61
+ auth_result = authenticate
62
+ if !auth_result["valid"]
63
+ result_handler = auth_result["handler"]
64
+ raise ValidAccessTokenException, result_handler.full_error_message
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Dingtalk
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :dingtalk do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,164 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dingtalk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - ysllyfe
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-09-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
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: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
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: redis
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: pry-rails
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Description of Dingtalk.
112
+ email:
113
+ - ysllyfe@163.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - MIT-LICENSE
119
+ - README.md
120
+ - Rakefile
121
+ - lib/dingtalk.rb
122
+ - lib/dingtalk/api.rb
123
+ - lib/dingtalk/api/department.rb
124
+ - lib/dingtalk/api/message.rb
125
+ - lib/dingtalk/api/microapp.rb
126
+ - lib/dingtalk/api/user.rb
127
+ - lib/dingtalk/client.rb
128
+ - lib/dingtalk/config.rb
129
+ - lib/dingtalk/eco_api.rb
130
+ - lib/dingtalk/eco_api/role.rb
131
+ - lib/dingtalk/handler.rb
132
+ - lib/dingtalk/handler/eco_result_handler.rb
133
+ - lib/dingtalk/handler/global_code.rb
134
+ - lib/dingtalk/handler/result_handler.rb
135
+ - lib/dingtalk/token/object_store.rb
136
+ - lib/dingtalk/token/redis_store.rb
137
+ - lib/dingtalk/token/store.rb
138
+ - lib/dingtalk/version.rb
139
+ - lib/tasks/dingtalk_tasks.rake
140
+ homepage: http://github.com/ysllyfe/dingtalk
141
+ licenses:
142
+ - MIT
143
+ metadata: {}
144
+ post_install_message:
145
+ rdoc_options: []
146
+ require_paths:
147
+ - lib
148
+ required_ruby_version: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ required_rubygems_version: !ruby/object:Gem::Requirement
154
+ requirements:
155
+ - - ">="
156
+ - !ruby/object:Gem::Version
157
+ version: '0'
158
+ requirements: []
159
+ rubyforge_project:
160
+ rubygems_version: 2.5.1
161
+ signing_key:
162
+ specification_version: 4
163
+ summary: Summary of Dingtalk.
164
+ test_files: []