qy_wechat_api 1.0.0.beta1

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,51 @@
1
+ # encoding: utf-8
2
+ module QyWechatApi
3
+
4
+ class ResultHandler
5
+
6
+ attr_accessor :code, :cn_msg, :en_msg, :result
7
+
8
+ def initialize(code, en_msg, result={})
9
+ @code = code || OK_CODE
10
+ @en_msg = en_msg || OK_MSG
11
+ @cn_msg = GLOBAL_CODES[@code.to_i]
12
+ @result = package_result(result)
13
+ end
14
+
15
+ # This method is to valid the current request if is true or is false
16
+ def is_ok?
17
+ code == OK_CODE
18
+ end
19
+ alias_method :ok?, :is_ok?
20
+
21
+ # e.g.:
22
+ # 45009: api freq out of limit(接口调用超过限制)
23
+ def full_message
24
+ "#{code}: #{en_msg}(#{cn_msg})."
25
+ end
26
+ alias_method :full_messages, :full_message
27
+
28
+ def full_error_message
29
+ full_message if !is_ok?
30
+ end
31
+ alias_method :full_error_messages, :full_error_message
32
+ alias_method :errors, :full_error_message
33
+
34
+ private
35
+
36
+ # if define Rails constant
37
+ # result = QyWechatApi::ResultHandler.new("0", "success", {:ok => "true"})
38
+ # result.result["ok"] #=> true
39
+ # result.result[:ok] #=> true
40
+ # result.result['ok'] #=> true
41
+ def package_result(result)
42
+ if defined?(Rails)
43
+ ActiveSupport::HashWithIndifferentAccess.new(result)
44
+ else
45
+ result
46
+ end
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,23 @@
1
+ # encoding: utf-8
2
+ module QyWechatApi
3
+ class ObjectStorage < Storage
4
+ def valid?
5
+ super
6
+ end
7
+
8
+ def token_expired?
9
+ # 如果当前token过期时间小于现在的时间,则重新获取一次
10
+ client.expired_at <= Time.now.to_i
11
+ end
12
+
13
+ def refresh_token
14
+ super
15
+ end
16
+
17
+ def access_token
18
+ super
19
+ client.access_token
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ module QyWechatApi
3
+ class RedisStorage < Storage
4
+ def valid?
5
+ weixin_redis.del(client.redis_key)
6
+ super
7
+ end
8
+
9
+ def token_expired?
10
+ weixin_redis.hvals(client.redis_key).empty?
11
+ end
12
+
13
+ def refresh_token
14
+ super
15
+ weixin_redis.hmset(client.redis_key, "access_token", client.access_token,
16
+ "expired_at", client.expired_at)
17
+ weixin_redis.expireat(client.redis_key, client.expired_at.to_i-10) # 提前10秒超时
18
+ end
19
+
20
+ def access_token
21
+ super
22
+ client.access_token = weixin_redis.hget(client.redis_key, "access_token")
23
+ client.expired_at = weixin_redis.hget(client.redis_key, "expired_at")
24
+ client.access_token
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+
3
+ module QyWechatApi
4
+ class Storage
5
+
6
+ attr_accessor :client
7
+
8
+ def initialize(client)
9
+ @client = client
10
+ end
11
+
12
+ def self.init_with(client)
13
+ if QyWechatApi.weixin_redis.nil?
14
+ ObjectStorage.new(client)
15
+ else
16
+ RedisStorage.new(client)
17
+ end
18
+ end
19
+
20
+ def valid?
21
+ authenticate["valid"]
22
+ end
23
+
24
+ def authenticate
25
+ auth_result = http_get_access_token
26
+ auth = false
27
+ if auth_result.is_ok?
28
+ set_access_token_for_client(auth_result.result)
29
+ auth = true
30
+ end
31
+ {"valid" => auth, "handler" => auth_result}
32
+ end
33
+
34
+ def refresh_token
35
+ handle_valid_exception
36
+ set_access_token_for_client
37
+ end
38
+
39
+ def access_token
40
+ refresh_token if token_expired?
41
+ end
42
+
43
+ def token_expired?
44
+ raise NotImplementedError, "Subclasses must implement a token_expired? method"
45
+ end
46
+
47
+ def set_access_token_for_client(access_token_infos=nil)
48
+ token_infos = access_token_infos || http_get_access_token.result
49
+ client.access_token = token_infos["access_token"]
50
+ client.expired_at = Time.now.to_i + token_infos["expires_in"].to_i
51
+ end
52
+
53
+ def http_get_access_token
54
+ QyWechatApi.http_get_without_token("/gettoken", authenticate_headers)
55
+ end
56
+
57
+ def authenticate_headers
58
+ {corpid: client.corp_id, corpsecret: client.group_secret}
59
+ end
60
+
61
+ private
62
+
63
+ def handle_valid_exception
64
+ auth_result = authenticate
65
+ if !auth_result["valid"]
66
+ result_handler = auth_result["handler"]
67
+ raise Errors::ValidAccessTokenException, result_handler.full_error_message
68
+ end
69
+ end
70
+
71
+ def weixin_redis
72
+ QyWechatApi.weixin_redis
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,3 @@
1
+ module QyWechatApi
2
+ VERSION = "1.0.0.beta1"
3
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'qy_wechat_api/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "qy_wechat_api"
9
+ spec.version = QyWechatApi::VERSION
10
+ spec.authors = ["lanrion"]
11
+ spec.email = ["huaitao-deng@foxmail.com"]
12
+ spec.summary = %q{企业微信高级API Ruby版本}
13
+ spec.description = %q{企业微信高级API Ruby版本}
14
+ spec.homepage = "https://github.com/lanrion/qy_wechat_api"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "rest-client", ">= 1.6.7"
23
+ # A streaming JSON parsing and encoding library for Ruby (C bindings to yajl)
24
+ # https://github.com/brianmario/yajl-ruby
25
+ spec.add_dependency "yajl-ruby", "~> 1.2.0"
26
+
27
+ spec.add_dependency "carrierwave", "~> 0.10.0"
28
+ spec.add_dependency 'mini_magick', '~> 3.7.0'
29
+
30
+ spec.add_development_dependency "bundler", "~> 1.6"
31
+ spec.add_development_dependency "rake"
32
+ end
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ describe QyWechatApi::Api::Media do
4
+
5
+ let(:image_jpg_path) do
6
+ "#{File.dirname(__FILE__)}/medias/ruby-logo.jpg"
7
+ end
8
+
9
+ let(:image_ico_path) do
10
+ "#{File.dirname(__FILE__)}/medias/favicon.ico"
11
+ end
12
+
13
+ let(:image_jpg_file) do
14
+ File.new(image_jpg_path)
15
+ end
16
+
17
+ let(:image_ico_file) do
18
+ File.new(image_ico_path)
19
+ end
20
+
21
+ let(:remote_png_path) do
22
+ "https://ruby-china-files.b0.upaiyun.com/user/big_avatar/273.jpg"
23
+ end
24
+
25
+ let(:remote_jpg_path) do
26
+ "http://g.hiphotos.baidu.com/baike/c0%3Dbaike80%2C5%2C5%2C80%2C26/sign=ce55457e4334970a537e187df4a3baad/03087bf40ad162d99455ef4d13dfa9ec8b13632762d0ed14.jpg"
27
+ end
28
+
29
+ it "can upload a jpg File image" do
30
+ response = $client.media.upload(image_jpg_file, "image")
31
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
32
+ expect(response.result.keys).to eq(["type", "media_id", "created_at"])
33
+ end
34
+
35
+ it "can upload a ico File image" do
36
+ response = $client.media.upload(image_ico_file, "image")
37
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
38
+ expect(response.result.keys).to eq(["type", "media_id", "created_at"])
39
+ end
40
+
41
+ it "can upload a local image" do
42
+ response = $client.media.upload(image_jpg_path, "image")
43
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
44
+ expect(response.result.keys).to eq(["type", "media_id", "created_at"])
45
+ end
46
+
47
+ it "can upload a local ico image" do
48
+ response = $client.media.upload(image_ico_path, "image")
49
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
50
+ expect(response.result.keys).to eq(["type", "media_id", "created_at"])
51
+ end
52
+
53
+ it "can upload a remote png image" do
54
+ response = $client.media.upload(remote_png_path, "image")
55
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
56
+ expect(response.result.keys).to eq(["type", "media_id", "created_at"])
57
+ end
58
+
59
+ it "can upload a remote jpg image" do
60
+ response = $client.media.upload(remote_jpg_path, "image")
61
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
62
+ expect(response.result.keys).to eq(["type", "media_id", "created_at"])
63
+ end
64
+
65
+ # it "#download_media_url return a String url" do
66
+ # image = $client.media.upload(image_ico_path, "image")
67
+ # media_id = image.result["media_id"]
68
+ # image_url = $client.download_media_url(media_id)
69
+ # expect(image_url.class).to eq(String)
70
+ # end
71
+
72
+ end
@@ -0,0 +1,60 @@
1
+ require "spec_helper"
2
+
3
+ describe QyWechatApi::Api::Menu do
4
+ let(:menu_string) do
5
+ '{
6
+ "button": [
7
+ {
8
+ "name": "扫码",
9
+ "sub_button": [
10
+ {
11
+ "type": "scancode_waitmsg",
12
+ "name": "扫码带提示",
13
+ "key": "rselfmenu_0_0",
14
+ "sub_button": [ ]
15
+ },
16
+ {
17
+ "type": "scancode_push",
18
+ "name": "扫码推事件",
19
+ "key": "rselfmenu_0_1",
20
+ "sub_button": [ ]
21
+ }
22
+ ]
23
+ },
24
+ {
25
+ "name": "发图",
26
+ "sub_button": [
27
+ {
28
+ "type": "pic_sysphoto",
29
+ "name": "系统拍照发图",
30
+ "key": "rselfmenu_1_0",
31
+ "sub_button": [ ]
32
+ },
33
+ {
34
+ "type": "pic_photo_or_album",
35
+ "name": "拍照或者相册发图",
36
+ "key": "rselfmenu_1_1",
37
+ "sub_button": [ ]
38
+ },
39
+ {
40
+ "type": "pic_weixin",
41
+ "name": "微信相册发图",
42
+ "key": "rselfmenu_1_2",
43
+ "sub_button": [ ]
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "name": "发送位置",
49
+ "type": "location_select",
50
+ "key": "rselfmenu_2_0"
51
+ }
52
+ ]
53
+ }'
54
+ end
55
+
56
+ it "#create" do
57
+ response = $client.menu.create(menu_string, 1)
58
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
59
+ end
60
+ end
@@ -0,0 +1,50 @@
1
+ require "spec_helper"
2
+ describe QyWechatApi::Api::Message do
3
+ let(:text_message) do
4
+ "text message"
5
+ end
6
+
7
+ let(:image_path) do
8
+ "#{File.dirname(__FILE__)}/medias/ruby-logo.jpg"
9
+ end
10
+
11
+ let(:image_file) do
12
+ File.new(image_path)
13
+ end
14
+
15
+ it "#send_text_message" do
16
+ response = $client.message.send_text("@all", "@all", "@all", 1, text_message)
17
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
18
+ end
19
+
20
+ it "#send_image_message" do
21
+ response = $client.media.upload(image_path, "image").result
22
+ response = $client.message.send_image("@all", "@all", "@all", 1, response["media_id"])
23
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
24
+ end
25
+
26
+
27
+ it "#send_news" do
28
+ articles = [{
29
+ "title" => "Happy Day",
30
+ "description" => "Is Really A Happy Day",
31
+ "url" => "http://www.baidu.com",
32
+ "picurl" => "http://www.baidu.com/img/bdlogo.gif"
33
+ },
34
+ {
35
+ "title" => "Happy Day",
36
+ "description" => "Is Really A Happy Day",
37
+ "url" => "http://www.baidu.com",
38
+ "picurl"=> "http://www.baidu.com/img/bdlogo.gif"
39
+ }]
40
+ response = $client.message.send_news("@all", "@all", "@all", 1, articles)
41
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
42
+ end
43
+
44
+ it "#send_file" do
45
+ response = $client.media.upload(image_path, "image").result
46
+ response = $client.message.send_file("@all", "@all", "@all", 1, response["media_id"])
47
+ expect(response.code).to eq(QyWechatApi::OK_CODE)
48
+ end
49
+
50
+ end
@@ -0,0 +1,115 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, consider making
10
+ # a separate helper file that requires the additional dependencies and performs
11
+ # the additional setup, and require it from the spec files that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ require "redis-namespace"
18
+ require "redis"
19
+
20
+ require "qy_wechat_api"
21
+ require "pry-rails"
22
+ corpid = "wxb9ce1d023fe6eb69"
23
+ corpsecret = "UOofFIah4PVLmkG8xMH3lpDxj6NTnQSKMrFt-HubiPB4kjB09EmTVcUjgNeermps"
24
+
25
+
26
+ # Comment to test for ClientStorage
27
+ redis = Redis.new(:host => "127.0.0.1",:port => "6379")
28
+
29
+ namespace = "qy_wechat_api:redis_storage"
30
+
31
+ # cleanup keys in the current namespace when restart server everytime.
32
+ exist_keys = redis.keys("#{namespace}:*")
33
+ exist_keys.each{|key|redis.del(key)}
34
+
35
+ redis_with_ns = Redis::Namespace.new("#{namespace}", :redis => redis)
36
+
37
+ QyWechatApi.configure do |config|
38
+ config.redis = redis_with_ns
39
+ end
40
+
41
+ $client = QyWechatApi::Client.new(corpid, corpsecret)
42
+
43
+ RSpec.configure do |config|
44
+ # rspec-expectations config goes here. You can use an alternate
45
+ # assertion/expectation library such as wrong or the stdlib/minitest
46
+ # assertions if you prefer.
47
+ config.expect_with :rspec do |expectations|
48
+ # This option will default to `true` in RSpec 4. It makes the `description`
49
+ # and `failure_message` of custom matchers include text for helper methods
50
+ # defined using `chain`, e.g.:
51
+ # be_bigger_than(2).and_smaller_than(4).description
52
+ # # => "be bigger than 2 and smaller than 4"
53
+ # ...rather than:
54
+ # # => "be bigger than 2"
55
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
56
+ end
57
+
58
+ # rspec-mocks config goes here. You can use an alternate test double
59
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
60
+ config.mock_with :rspec do |mocks|
61
+ # Prevents you from mocking or stubbing a method that does not exist on
62
+ # a real object. This is generally recommended, and will default to
63
+ # `true` in RSpec 4.
64
+ mocks.verify_partial_doubles = true
65
+ end
66
+
67
+ # The settings below are suggested to provide a good initial experience
68
+ # with RSpec, but feel free to customize to your heart's content.
69
+ =begin
70
+ # These two settings work together to allow you to limit a spec run
71
+ # to individual examples or groups you care about by tagging them with
72
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
73
+ # get run.
74
+ config.filter_run :focus
75
+ config.run_all_when_everything_filtered = true
76
+
77
+ # Limits the available syntax to the non-monkey patched syntax that is recommended.
78
+ # For more details, see:
79
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
80
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
81
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
82
+ config.disable_monkey_patching!
83
+
84
+ # This setting enables warnings. It's recommended, but in some cases may
85
+ # be too noisy due to issues in dependencies.
86
+ config.warnings = true
87
+
88
+ # Many RSpec users commonly either run the entire suite or an individual
89
+ # file, and it's useful to allow more verbose output when running an
90
+ # individual spec file.
91
+ if config.files_to_run.one?
92
+ # Use the documentation formatter for detailed output,
93
+ # unless a formatter has already been configured
94
+ # (e.g. via a command-line flag).
95
+ config.default_formatter = 'doc'
96
+ end
97
+
98
+ # Print the 10 slowest examples and example groups at the
99
+ # end of the spec run, to help surface which specs are running
100
+ # particularly slow.
101
+ config.profile_examples = 10
102
+
103
+ # Run specs in random order to surface order dependencies. If you find an
104
+ # order dependency and want to debug it, you can fix the order by providing
105
+ # the seed, which is printed after each run.
106
+ # --seed 1234
107
+ config.order = :random
108
+
109
+ # Seed global randomization in this process using the `--seed` CLI option.
110
+ # Setting this allows you to use `--seed` to deterministically reproduce
111
+ # test failures related to randomization by passing the same `--seed` value
112
+ # as the one that triggered the failure.
113
+ Kernel.srand config.seed
114
+ =end
115
+ end