wework 0.1.0 → 0.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d7d9c3e6a5439548c70364e4a742552f7a31409d
4
- data.tar.gz: 3453fdfbf36889692aeb268f727098832c6d6ff5
3
+ metadata.gz: da08105f38235bd4f06667e179d9c033a82146f1
4
+ data.tar.gz: cb7194f21cbb79a9c1ffa8e88e8e7362a167b663
5
5
  SHA512:
6
- metadata.gz: 6c2d7704089c8e27229aca12e714eabf6ff81855f8e74d2b99704b251e4fc7c23dd6d0960a86ef151a593138fd90ab69a7c3a1cebb589582732f9d2f46d67c19
7
- data.tar.gz: 0c0815e57a9e04a8601d5680082b8ff9e5a65421bf2e228c8ecb97b2198798383c5c569b1210b21ebf87bec6ecc4f29c9c758a2bc74291ee99de52aaa2158cdd
6
+ metadata.gz: 7ca30a8f14e08e9a5e47a22e7bee4235da1849cbbae5044ea81a1385458880a1c6470f5428b66d47d912caa240639995f4a0e21a9ba28dba629870b837e1defb
7
+ data.tar.gz: 720dc74a741a7e828c577326aafe2f76764bda9077efc2dcd04315e8b627599dae4444365099dad454794412cfcc86303d4af99e572ce3ec0fcc95c7222aa630
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ *.gem
11
+ config.yml
data/README.md CHANGED
@@ -20,20 +20,18 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO:
24
-
25
- ## Development
26
-
27
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
28
-
29
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
23
+ TODO: ...
30
24
 
31
25
  ## Contributing
32
26
 
33
- Bug reports and pull requests are welcome on GitHub at https://github.com/seandong/wework. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
34
-
27
+ * Fork `Wework` on GitHub
28
+ * Make your changes
29
+ * Ensure all tests pass (`bundle exec rake`)
30
+ * Send a pull request
31
+ * If we like them we'll merge them
32
+ * If we've accepted a patch, feel free to ask for commit access!
35
33
 
36
34
  ## License
37
35
 
38
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
36
+ Copyright (c) 2016 MyColorway. See LICENSE.txt for further details.
39
37
 
@@ -0,0 +1,70 @@
1
+ module Wework
2
+ module Api
3
+ class Agent < Base
4
+ # user agent: UA is mozilla/5.0 (iphone; cpu iphone os 10_2 like mac os x) applewebkit/602.3.12 (khtml, like gecko) mobile/14c92 wxwork/1.3.2 micromessenger/6.2
5
+
6
+ def get_info
7
+ get 'agent/get', params: {agentid: agent_id}
8
+ end
9
+
10
+ def set_info data={}
11
+ post 'agent/set', data.merge(agentid: agent_id)
12
+ end
13
+
14
+ def menu_create menu
15
+ post 'menu/create', menu, params: {agentid: agent_id}
16
+ end
17
+
18
+ def menu_delete
19
+ get 'menu/delete', params: {agentid: agent_id}
20
+ end
21
+
22
+ def media_upload type, file
23
+ post_file 'media/upload', file, params: { type: type }
24
+ end
25
+
26
+ def media_get(media_id)
27
+ get 'media/get', params: { media_id: media_id }, as: :file
28
+ end
29
+
30
+ def message_send user_ids, department_ids, payload={}
31
+ payload[:agentid] = agent_id
32
+ payload[:touser] = Array.wrap(user_ids).join('|') if user_ids.present?
33
+ payload[:toparty] = Array.wrap(department_ids).join('|') if department_ids.present?
34
+ post 'message/send', payload
35
+ end
36
+
37
+ def text_message_send user_ids, department_ids, content
38
+ message_send user_ids, department_ids, {text: {content: content}, msgtype: 'text'}
39
+ end
40
+
41
+ def image_message_send user_ids, department_ids, media_id
42
+ message_send user_ids, department_ids, {image: {media_id: media_id}, msgtype: 'image'}
43
+ end
44
+
45
+ def voice_message_send user_ids, department_ids, media_id
46
+ message_send user_ids, department_ids, {voice: {media_id: media_id}, msgtype: 'voice'}
47
+ end
48
+
49
+ def file_message_send user_ids, department_ids, media_id
50
+ message_send user_ids, department_ids, {file: {media_id: media_id}, msgtype: 'file'}
51
+ end
52
+
53
+ def video_message_send user_ids, department_ids, media_id, title='', description=''
54
+ message_send user_ids, department_ids, {video: {media_id: media_id, title: 'title', description: description}, msgtype: 'video'}
55
+ end
56
+
57
+ def textcard_message_send user_ids, department_ids, title, description, url, btntxt='详情'
58
+ message_send user_ids, department_ids, {textcard: {title: title, description: description, url: url, btntxt: btntxt}, msgtype: 'textcard'}
59
+ end
60
+
61
+ def news_message_send user_ids, department_ids, news=[]
62
+ message_send user_ids, department_ids, {news: {articles: news}, msgtype: 'news'}
63
+ end
64
+
65
+ def agent_id
66
+ @agent_id.to_i
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,54 @@
1
+ require 'wework/token/store'
2
+ require 'wework/token/redis_store'
3
+
4
+ module Wework
5
+ module Api
6
+ class Base
7
+ attr_reader :corp_id, :agent_id, :agent_secret
8
+ attr_accessor :options
9
+
10
+ delegate :access_token, to: :token_store
11
+
12
+ def initialize(corp_id, agent_id, agent_secret, options={})
13
+ @corp_id = corp_id
14
+ @agent_id = agent_id
15
+ @agent_secret = agent_secret
16
+ @options = options
17
+ end
18
+
19
+ def token_store
20
+ @token_store ||= Token::RedisStore.new self
21
+ end
22
+
23
+ def request
24
+ @request ||= Wework::Request.new(API_ENDPOINT, false)
25
+ end
26
+
27
+ def get(path, headers = {})
28
+ with_access_token(headers[:params]) do |params|
29
+ request.get path, headers.merge(params: params)
30
+ end
31
+ end
32
+
33
+ def post(path, payload, headers = {})
34
+ with_access_token(headers[:params]) do |params|
35
+ request.post path, payload, headers.merge(params: params)
36
+ end
37
+ end
38
+
39
+ def post_file(path, file, headers = {})
40
+ with_access_token(headers[:params]) do |params|
41
+ request.post_file path, file, headers.merge(params: params)
42
+ end
43
+ end
44
+
45
+ def with_access_token(params = {}, tries = 2)
46
+ params ||= {}
47
+ yield(params.merge(access_token: access_token))
48
+ rescue AccessTokenExpiredError
49
+ token_store.refresh_token
50
+ retry unless (tries -= 1).zero?
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ module Wework
2
+ module Api
3
+ class Contact < Base
4
+
5
+ def initialize(corp_id, corp_secret)
6
+ super(corp_id, CONTACT_AGENT_ID, corp_secret)
7
+ end
8
+
9
+ def user_create userid, name, mobile, department, data={}
10
+ post 'user/create', data.merge(userid: userid, name: name, mobile: mobile, department: department)
11
+ end
12
+
13
+ def user_get userid
14
+ get 'user/get', params: {userid: userid}
15
+ end
16
+
17
+ def user_update userid, data={}
18
+ post 'user/update', data.merge(userid: userid)
19
+ end
20
+
21
+ def user_delete userid
22
+ get 'user/delete', params: {userid: userid}
23
+ end
24
+
25
+ def user_batchdelete useridlist=[]
26
+ post 'user/batchdelete', {useridlist: useridlist}
27
+ end
28
+
29
+ def user_simplelist department_id, fetch_child=0
30
+ get 'user/simplelist', params: {department_id: department_id, fetch_child: fetch_child}
31
+ end
32
+
33
+ def user_list department_id, fetch_child=0
34
+ get 'user/list', params: {department_id: department_id, fetch_child: fetch_child}
35
+ end
36
+
37
+ def department_create name, parentid=0, data={}
38
+ post 'department/create', data.merge(name: name, parentid: parentid)
39
+ end
40
+
41
+ def department_update department_id, data={}
42
+ post 'department/update', data.merge(id: department_id)
43
+ end
44
+
45
+ def department_delete department_id
46
+ get 'department/delete', params: {id: department_id}
47
+ end
48
+
49
+ def department_list department_id=0
50
+ get 'department/list', params: {id: department_id}
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,25 @@
1
+ module Wework
2
+
3
+ class << self
4
+ attr_accessor :config
5
+ def configure
6
+ yield config
7
+ end
8
+
9
+ def config
10
+ @config ||= Config.new
11
+ end
12
+
13
+ def redis
14
+ config.redis
15
+ end
16
+
17
+ def http_timeout_options
18
+ config.http_timeout_options || {write: 5, connect: 5, read: 5}
19
+ end
20
+ end
21
+
22
+ class Config
23
+ attr_accessor :redis, :http_timeout_options
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ module Wework
2
+ class Engine
3
+
4
+ attr_reader :corp_id, :corp_secret, :app_id, :app_secret
5
+
6
+ def initialize(options={})
7
+ @corp_id = options[:corp_id]
8
+ @corp_secret = options[:corp_secret]
9
+ @app_id = options[:app_id]
10
+ @app_secret = options[:app_secret]
11
+ end
12
+
13
+ def contract
14
+ @contract ||= Wework::Api::Contact.new(corp_id, corp_secret) if contract?
15
+ end
16
+
17
+ def agent
18
+ @agent ||= Wework::Api::Agent.new(corp_id, app_id, app_secret) if agent?
19
+ end
20
+
21
+ private
22
+
23
+ def agent?
24
+ corp_id.present? && app_id.present? && app_secret.present?
25
+ end
26
+
27
+ def contract?
28
+ corp_id.present? && corp_secret.present?
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,101 @@
1
+ # Reference this: https://github.com/Eric-Guo/wechat/blob/master/lib/wechat/http_client.rb
2
+ require 'http'
3
+
4
+ module Wework
5
+ class Request
6
+ attr_reader :base, :ssl_context, :httprb
7
+
8
+ def initialize(base, skip_verify_ssl)
9
+ @base = base
10
+ @httprb = HTTP.timeout(:global, **Wework.http_timeout_options)
11
+ @ssl_context = OpenSSL::SSL::SSLContext.new
12
+ @ssl_context.ssl_version = :TLSv1_client
13
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE if skip_verify_ssl
14
+ end
15
+
16
+ def get(path, get_header = {})
17
+ request(path, get_header) do |url, header|
18
+ params = header.delete(:params)
19
+ httprb.headers(header).get(url, params: params, ssl_context: ssl_context)
20
+ end
21
+ end
22
+
23
+ def post(path, post_body, post_header = {})
24
+ request(path, post_header) do |url, header|
25
+ params = header.delete(:params)
26
+ httprb.headers(header).post(url, params: params, json: post_body, ssl_context: ssl_context)
27
+ end
28
+ end
29
+
30
+ def post_file(path, file, post_header = {})
31
+ request(path, post_header) do |url, header|
32
+ params = header.delete(:params)
33
+ httprb.headers(header)
34
+ .post(url, params: params,
35
+ form: { media: HTTP::FormData::File.new(file),
36
+ hack: 'X' }, # Existing here for http-form_data 1.0.1 handle single param improperly
37
+ ssl_context: ssl_context)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def request(path, header = {}, &_block)
44
+ url_base = header.delete(:base) || base
45
+ as = header.delete(:as)
46
+ header['Accept'] = 'application/json'
47
+ response = yield("#{url_base}#{path}", header)
48
+
49
+ raise ResponseError.new(response.status) unless HTTP_OK_STATUS.include?(response.status)
50
+
51
+ parse_response(response, as || :json) do |parse_as, data|
52
+ break data unless parse_as == :json && data['errcode'].present?
53
+ result = Wework::Result.new(data)
54
+ raise AccessTokenExpiredError if result.token_expired?
55
+ result
56
+ end
57
+ end
58
+
59
+ def parse_response(response, as)
60
+ content_type = response.headers[:content_type]
61
+ parse_as = {
62
+ %r{^application\/json} => :json,
63
+ %r{^image\/.*} => :file
64
+ }.each_with_object([]) { |match, memo| memo << match[1] if content_type =~ match[0] }.first || as || :text
65
+
66
+ case parse_as
67
+ when :file
68
+ file = Tempfile.new('tmp')
69
+ file.binmode
70
+ file.write(response.body)
71
+ file.close
72
+ data = file
73
+
74
+ when :json
75
+ data = JSON.parse response.body.to_s.gsub(/[\u0000-\u001f]+/, '')
76
+ else
77
+ data = response.body
78
+ end
79
+
80
+ yield(parse_as, data)
81
+ end
82
+ end
83
+
84
+ class Result < OpenStruct
85
+ def initialize(data)
86
+ data['full_message'] = "(#{data['errcode']}) #{data['errmsg']}"
87
+ super data
88
+ end
89
+
90
+ def token_expired?
91
+ # 42001: access_token timeout
92
+ # 40014: invalid access_token
93
+ # 40001, invalid credential, access_token is invalid or not latest hint
94
+ [42001, 40014, 40001].include?(errcode)
95
+ end
96
+
97
+ def success?
98
+ errcode == SUCCESS_CODE
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,40 @@
1
+ module Wework
2
+ module Token
3
+ class RedisStore < Store
4
+
5
+ def initialize(agent)
6
+ raise RedisNotConfigException if redis.nil?
7
+ super
8
+ end
9
+
10
+ def access_token
11
+ super
12
+ redis.hget(key, "access_token")
13
+ end
14
+
15
+ def expired?
16
+ redis.hvals(key).empty?
17
+ end
18
+
19
+ def refresh_token
20
+ result = super
21
+
22
+ expires_at = Time.now.to_i + result['expires_in'].to_i - 100
23
+ redis.hmset(
24
+ key,
25
+ "access_token", result['access_token'],
26
+ "expires_at", expires_at
27
+ )
28
+
29
+ redis.expireat(key, expires_at)
30
+ end
31
+
32
+ private
33
+
34
+ def redis
35
+ Wework.redis
36
+ end
37
+
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,31 @@
1
+ module Wework
2
+ module Token
3
+ class Store
4
+
5
+ attr_accessor :agent
6
+
7
+ def initialize(agent)
8
+ @agent = agent
9
+ end
10
+
11
+ def access_token
12
+ refresh_token if expired?
13
+ end
14
+
15
+ def expired?
16
+ raise NotImplementedError, "Subclasses must implement a token_expired? method"
17
+ end
18
+
19
+ def refresh_token
20
+ agent.request.get 'gettoken', params: {corpid: agent.corp_id, corpsecret: agent.agent_secret}
21
+ end
22
+
23
+ private
24
+
25
+ def key
26
+ @key ||= Digest::MD5.hexdigest("#{ACCESS_TOKEN_PREFIX}_#{agent.agent_id}_#{agent.agent_secret}")
27
+ end
28
+
29
+ end
30
+ end
31
+ end
@@ -1,3 +1,3 @@
1
1
  module Wework
2
- VERSION = "0.1.0"
2
+ VERSION = '0.1.2'.freeze
3
3
  end
data/lib/wework.rb CHANGED
@@ -1,5 +1,30 @@
1
- require "wework/version"
1
+ require 'redis'
2
+ require 'active_support/all'
3
+ #require 'active_support/core_ext/object/blank'
4
+
5
+ Dir["#{File.dirname(__FILE__)}/wework/*.rb"].each do |path|
6
+ require path
7
+ end
8
+
9
+ require 'wework/api/base'
10
+ require 'wework/api/agent'
11
+ require 'wework/api/contact'
2
12
 
3
13
  module Wework
4
- # Your code goes here...
14
+ API_ENDPOINT = 'https://qyapi.weixin.qq.com/cgi-bin/'.freeze
15
+ ACCESS_TOKEN_PREFIX = 'WEWORK'.freeze
16
+ CONTACT_AGENT_ID = 'CONTACT'.freeze
17
+ HTTP_OK_STATUS = [200, 201].freeze
18
+ SUCCESS_CODE = 0
19
+
20
+ # Exceptions
21
+ class RedisNotConfigException < RuntimeError; end
22
+ class AccessTokenExpiredError < RuntimeError; end
23
+ class ResponseError < StandardError
24
+ attr_reader :error_code
25
+ def initialize(errcode, errmsg='')
26
+ @error_code = errcode
27
+ super "(#{error_code}) #{errmsg}"
28
+ end
29
+ end
5
30
  end
data/wework.gemspec CHANGED
@@ -20,7 +20,11 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
+ spec.add_dependency 'http', '>= 1.0.4', '< 3'
24
+ spec.add_dependency 'activesupport', '~> 5.0'
25
+ spec.add_dependency 'redis', '~>3.2'
26
+
23
27
  spec.add_development_dependency "bundler", "~> 1.13"
24
28
  spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "minitest", "~> 5.0"
29
+ spec.add_development_dependency "minitest", "~> 5.10"
26
30
  end
metadata CHANGED
@@ -1,15 +1,63 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wework
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - seandong
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-12-16 00:00:00.000000000 Z
11
+ date: 2016-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: http
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.4
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '3'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 1.0.4
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '3'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activesupport
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '5.0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '5.0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: redis
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '3.2'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.2'
13
61
  - !ruby/object:Gem::Dependency
14
62
  name: bundler
15
63
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +92,14 @@ dependencies:
44
92
  requirements:
45
93
  - - "~>"
46
94
  - !ruby/object:Gem::Version
47
- version: '5.0'
95
+ version: '5.10'
48
96
  type: :development
49
97
  prerelease: false
50
98
  version_requirements: !ruby/object:Gem::Requirement
51
99
  requirements:
52
100
  - - "~>"
53
101
  - !ruby/object:Gem::Version
54
- version: '5.0'
102
+ version: '5.10'
55
103
  description: Ruby API wrapper for work wechat.
56
104
  email:
57
105
  - sindon@gmail.com
@@ -69,6 +117,14 @@ files:
69
117
  - bin/console
70
118
  - bin/setup
71
119
  - lib/wework.rb
120
+ - lib/wework/api/agent.rb
121
+ - lib/wework/api/base.rb
122
+ - lib/wework/api/contact.rb
123
+ - lib/wework/config.rb
124
+ - lib/wework/engine.rb
125
+ - lib/wework/request.rb
126
+ - lib/wework/token/redis_store.rb
127
+ - lib/wework/token/store.rb
72
128
  - lib/wework/version.rb
73
129
  - wework.gemspec
74
130
  homepage: https://github.com/seandong/wework