we_chat 0.5.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +26 -0
  4. data/app/assets/javascripts/we_chat/application.js +13 -0
  5. data/app/assets/stylesheets/we_chat/application.css +15 -0
  6. data/app/controllers/we_chat/application_controller.rb +4 -0
  7. data/app/helpers/we_chat/application_helper.rb +4 -0
  8. data/app/views/layouts/we_chat/application.html.erb +14 -0
  9. data/config/routes.rb +4 -0
  10. data/lib/tasks/we_chat/access_token.rake +26 -0
  11. data/lib/we_chat/access_token.rb +56 -0
  12. data/lib/we_chat/acts_as_we_chat_entity.rb +72 -0
  13. data/lib/we_chat/category.rb +19 -0
  14. data/lib/we_chat/category_property.rb +20 -0
  15. data/lib/we_chat/category_property_item.rb +17 -0
  16. data/lib/we_chat/engine.rb +5 -0
  17. data/lib/we_chat/entity/base.rb +62 -0
  18. data/lib/we_chat/entity/creator.rb +67 -0
  19. data/lib/we_chat/entity/destroyer.rb +16 -0
  20. data/lib/we_chat/entity/identity_entity.rb +21 -0
  21. data/lib/we_chat/entity/named_entity.rb +21 -0
  22. data/lib/we_chat/entity/retriever.rb +21 -0
  23. data/lib/we_chat/error.rb +105 -0
  24. data/lib/we_chat/headers.rb +15 -0
  25. data/lib/we_chat/menu.rb +36 -0
  26. data/lib/we_chat/messaging/message.rb +46 -0
  27. data/lib/we_chat/messaging/message_validator.rb +15 -0
  28. data/lib/we_chat/messaging/responder.rb +92 -0
  29. data/lib/we_chat/multipart_ext.rb +12 -0
  30. data/lib/we_chat/product.rb +119 -0
  31. data/lib/we_chat/product_attribute.rb +23 -0
  32. data/lib/we_chat/rest/api.rb +17 -0
  33. data/lib/we_chat/rest/client.rb +41 -0
  34. data/lib/we_chat/rest/media.rb +18 -0
  35. data/lib/we_chat/rest/menu.rb +21 -0
  36. data/lib/we_chat/rest/request.rb +103 -0
  37. data/lib/we_chat/rest/store.rb +172 -0
  38. data/lib/we_chat/rest/template_message.rb +14 -0
  39. data/lib/we_chat/rest/user.rb +20 -0
  40. data/lib/we_chat/rest/utils.rb +27 -0
  41. data/lib/we_chat/sku.rb +55 -0
  42. data/lib/we_chat/sku_definition.rb +21 -0
  43. data/lib/we_chat/sku_definition_item.rb +17 -0
  44. data/lib/we_chat/subscriber.rb +32 -0
  45. data/lib/we_chat/symbolizable.rb +18 -0
  46. data/lib/we_chat/version.rb +3 -0
  47. data/lib/we_chat.rb +46 -0
  48. metadata +257 -0
@@ -0,0 +1,36 @@
1
+ require 'we_chat/entity/base'
2
+
3
+ module WeChat
4
+ module Menu
5
+ extend ActiveSupport::Concern
6
+ include WeChat::Entity::Base
7
+
8
+ def to_we_chat_data
9
+ result = {}
10
+ result[:name] = self.name
11
+ if self.top_node?
12
+ result[:sub_button] = children_to_we_chat_data
13
+ else
14
+ result[:type] = self.menu_type
15
+ if self.view?
16
+ result[:url] = self.menu_url
17
+ else
18
+ result[:key] = self.menu_key
19
+ end
20
+ end
21
+ result
22
+ end
23
+
24
+ def children_to_we_chat_data
25
+ self.children.inject([]) {|list, item| list << item.to_we_chat_data}
26
+ end
27
+
28
+ included do
29
+
30
+ end
31
+
32
+ module ClassMethods
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,46 @@
1
+ require 'we_chat/symbolizable'
2
+
3
+ module WeChat
4
+ module Messaging
5
+ class Message
6
+ include WeChat::Symbolizable
7
+
8
+ attr_reader :source
9
+
10
+ def initialize(hash)
11
+ @source = symbolize_keys! hash
12
+ end
13
+
14
+ def create_time
15
+ return nil if source[:CreateTime].nil?
16
+ Time.at(source[:CreateTime].to_i)
17
+ end
18
+
19
+ def msg_id
20
+ source[:MsgId].to_i
21
+ end
22
+
23
+ def msg_type
24
+ source[:MsgType].to_sym
25
+ end
26
+
27
+ def method_missing(method_name, *args, &block)
28
+ key = method_name.to_s.gsub(/\=/, '').camelize.to_sym
29
+ if method_name.to_s =~ /.*=$/
30
+ @source[key] = args.first
31
+ else
32
+ return source[key]
33
+ end
34
+ end
35
+
36
+ def create_response(message_type = :text)
37
+ response = WeChat::Messaging::Message.new({})
38
+ response.to_user_name = self.from_user_name
39
+ response.from_user_name = self.to_user_name
40
+ response.msg_type = message_type.to_s
41
+ response.create_time = Time.now.to_i
42
+ response
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,15 @@
1
+
2
+ module WeChat
3
+ module Messaging
4
+ class MessageValidator
5
+
6
+ def self.is_valid?(signature, timestamp, nonce, token)
7
+ tmpArray = [token, timestamp, nonce].sort
8
+ tmpString = tmpArray.join
9
+ new_signature = Digest::SHA1.hexdigest tmpString
10
+ return false if signature.nil?
11
+ new_signature == signature
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,92 @@
1
+ require 'active_support/concern'
2
+ require 'we_chat/messaging/message'
3
+ require 'we_chat/messaging/message_validator'
4
+
5
+ module WeChat
6
+ module Messaging
7
+ module Responder
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ self.skip_before_filter :verify_authenticity_token
12
+ self.before_filter :verify_signature, only: [:show, :create]
13
+ end
14
+
15
+ def show
16
+ render :text => params[:echostr]
17
+ end
18
+
19
+ def create
20
+ request_message = WeChat::Messaging::Message.new(params[:xml] || post_data)
21
+ response = self.class.create_response(request_message)
22
+ if response.nil?
23
+ Rails.logger.debug 'response is null'
24
+ render nothing: true, status: 200, content_type: 'text/html'
25
+ else
26
+ render xml: response.source.to_xml(root: 'xml')
27
+ end
28
+ end
29
+
30
+ def verify_signature
31
+ array = [WeChat.wechat_token, params[:timestamp], params[:nonce]].compact.collect(&:to_s).sort
32
+ Rails.logger.debug Digest::SHA1.hexdigest(array.join)
33
+ render :text => "Forbidden", :status => 403 if params[:signature] != Digest::SHA1.hexdigest(array.join)
34
+ end
35
+
36
+ def post_data
37
+ data = Hash.from_xml(request.raw_post)
38
+ HashWithIndifferentAccess.new_from_hash_copying_default data.fetch('xml', {})
39
+ end
40
+
41
+ module ClassMethods
42
+ attr_reader :responders
43
+
44
+ def responds(message_type = :text, options={}, &block)
45
+ return unless block_given?
46
+ pattern = options.delete(:pattern) || /.*/
47
+ initialize_responders(message_type)
48
+ @responders[message_type] << { pattern: pattern, proc: block }
49
+ end
50
+
51
+ def create_response(message)
52
+ response_data = nil
53
+ responder_item = matched_responder(message)
54
+ return nil if responder_item.nil?
55
+
56
+ case message.msg_type
57
+ when :text
58
+ response_result = responder_item[:proc].call(message)
59
+ return nil if response_result.nil?
60
+ return response_result if response_result.is_a? WeChat::Messaging::Message
61
+ return create_text_message_response(message, response_result) if response_result.is_a? String
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def initialize_responders(message_type)
68
+ @responders ||= {}
69
+ @responders[message_type] ||= []
70
+ end
71
+
72
+ def matched_responder(message)
73
+ initialize_responders(message.msg_type)
74
+ responders[message.msg_type].each do |item|
75
+ if item[:pattern].is_a? Regexp
76
+ return item if message.content =~ item[:pattern]
77
+ else #String
78
+ return item if message.content.downcase == item[:pattern].downcase
79
+ end
80
+ end
81
+ nil
82
+ end
83
+
84
+ def create_text_message_response(message, reply_content)
85
+ response = message.create_response
86
+ response.content = reply_content
87
+ return response
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,12 @@
1
+ # HTTP::FormData::Multipart hacking
2
+ # Adding an additional CRLF at the end of request body
3
+
4
+ CRLF = "\r\n".freeze
5
+
6
+ HTTP::FormData::Multipart.class_eval do
7
+ alias_method :_real_tail, :tail
8
+
9
+ def tail
10
+ @tail ||= "#{CRLF}--#{@boundary}--#{CRLF}"
11
+ end
12
+ end
@@ -0,0 +1,119 @@
1
+ require 'we_chat/entity/base'
2
+ require 'we_chat/rest/client'
3
+
4
+ module WeChat
5
+ module Product
6
+ extend ActiveSupport::Concern
7
+
8
+ include WeChat::Entity::Base
9
+
10
+ CHILDREN = {
11
+ sku: :sku_list,
12
+ category: :category_id,
13
+ category_property_item: :property,
14
+ product_attribute: :attrext}.freeze
15
+
16
+ def to_we_chat_data
17
+ we_chat_data = {}
18
+ we_chat_data[:product_id] = self.we_chat_id
19
+ we_chat_data[:product_base] = {}
20
+ we_chat_data[:product_base][:name] = self.name
21
+ we_chat_data[:product_base][:buy_limit] = self.buy_limit
22
+ we_chat_data[:product_base][:main_img] = self.main_image_url
23
+ we_chat_data[:product_base][:detail_html] = self.description
24
+
25
+ we_chat_data[:product_base][:category_id] = []
26
+ self.categories.each { |category| we_chat_data[:product_base][:category_id] << category.we_chat_id }
27
+
28
+ we_chat_data[:product_base][:property] = []
29
+ self.category_property_items.each { |property_item| we_chat_data[:product_base][:property] << { id: property_item.category_property.we_chat_id, vid: property_item.we_chat_id } }
30
+
31
+ we_chat_data[:product_base][:sku_list] = []
32
+ we_chat_data[:product_base][:sku_info] = []
33
+ self.skus.each do |sku|
34
+ we_chat_data[:product_base][:sku_list] << sku.to_we_chat_data
35
+
36
+ if sku.sku_definition_items.length > 0
37
+ sku.sku_definition_items.each do |sku_definition_item|
38
+ index = we_chat_data[:product_base][:sku_info].index { |item| item[:id] == sku_definition_item.sku_definition.we_chat_id }
39
+ sku_info_data = {id: sku_definition_item.sku_definition.we_chat_id, vid: []}
40
+ if index && index >= 0
41
+ sku_info_data = we_chat_data[:product_base][:sku_info][index]
42
+ else
43
+ we_chat_data[:product_base][:sku_info] << sku_info_data
44
+ end
45
+
46
+ sku_info_data[:vid] << sku_definition_item.we_chat_id unless sku_info_data[:vid].include?(sku_definition_item.we_chat_id)
47
+ end
48
+ end
49
+ end
50
+
51
+ we_chat_data
52
+ end
53
+
54
+ def get_we_chat_id_from_data(data)
55
+ data[:product_id]
56
+ end
57
+
58
+ included do
59
+ end
60
+
61
+ module ClassMethods
62
+
63
+ def get_instance_data_from_reponse(response)
64
+ response[:product_info]
65
+ end
66
+
67
+ def prepare_instance_data(data)
68
+ result = {}
69
+ result[:we_chat_id] = data[:product_id]
70
+ result[:name] = data[:product_base][:name]
71
+ result[:buy_limit] = data[:product_base][:buy_limit]
72
+ result[:main_image_url] = data[:product_base][:main_img]
73
+ result[:description] = data[:product_base][:detail_html]
74
+ result
75
+ end
76
+
77
+ def prepare_one_type_of_children_data(data, child_type, definition)
78
+ if [:category, :category_property_item].include?(child_type)
79
+ data_key = definition[:data_key]
80
+ definition.merge(data: data[:product_base][data_key])
81
+ else
82
+ super(data, child_type, definition)
83
+ end
84
+ end
85
+
86
+ def prepare_one_type_of_children(instance, child_type, child_data)
87
+ case child_type
88
+ when :category
89
+ child_data[:data].each do |category_id|
90
+ child_instance = child_data[:klass].send(:find_by_we_chat_id, category_id)
91
+ instance.send(child_data[:method]) << child_instance if child_instance
92
+ end
93
+ when :category_property_item
94
+ child_data[:data].each do |item|
95
+ category_property_id = item[:id]
96
+ category_property_item_id = item[:vid]
97
+
98
+ child_instance = child_data[:klass].send(:find_by_we_chat_id, category_property_item_id)
99
+ instance.send(child_data[:method]) << child_instance if child_instance
100
+ end
101
+ when :product_attribute
102
+ child_data[:data].map do |key, value|
103
+ if key != :location
104
+ child_instance = child_data[:klass].send(:create_by_we_chat_data, { key: key.to_s.underscore, value: value, we_chat_id: key.to_s })
105
+ instance.send(child_data[:method]) << child_instance if child_instance
106
+ else
107
+ value.map do |k, v|
108
+ child_instance = child_data[:klass].send(:create_by_we_chat_data, { key: "location.#{k.to_s.underscore}", value: v, we_chat_id: k.to_s })
109
+ instance.send(child_data[:method]) << child_instance if child_instance
110
+ end
111
+ end
112
+ end
113
+ else
114
+ super(instance, child_type, child_data)
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,23 @@
1
+ require 'we_chat/entity/base'
2
+
3
+ module WeChat
4
+ module ProductAttribute
5
+ extend ActiveSupport::Concern
6
+ include WeChat::Entity::Base
7
+
8
+ included do
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ def prepare_instance_data(data)
14
+ result = super data
15
+ result[:key] = data[:key]
16
+ result[:value] = data[:value]
17
+ result[:we_chat_id] = data[:we_chat_id]
18
+ result
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'we_chat/rest/user'
2
+ require 'we_chat/rest/menu'
3
+ require 'we_chat/rest/store'
4
+ require 'we_chat/rest/media'
5
+ require 'we_chat/rest/template_message'
6
+
7
+ module WeChat
8
+ module REST
9
+ module API
10
+ include WeChat::REST::User
11
+ include WeChat::REST::Menu
12
+ include WeChat::REST::Store
13
+ include WeChat::REST::Media
14
+ include WeChat::REST::TemplateMessage
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,41 @@
1
+ require 'we_chat/rest/api'
2
+ require 'we_chat/access_token'
3
+
4
+ module WeChat
5
+ module REST
6
+ class Client
7
+ include WeChat::REST::API
8
+
9
+ attr_accessor :access_token
10
+
11
+ class << self
12
+ def default
13
+ new(access_token: WeChat::AccessToken.get_access_token)
14
+ end
15
+ end
16
+
17
+ def initialize(options = {})
18
+ options.each do |key, value|
19
+ instance_variable_set("@#{key}", value)
20
+ end
21
+ yield(self) if block_given?
22
+ end
23
+
24
+ def access_token?
25
+ !!access_token
26
+ end
27
+
28
+ # @return [Hash]
29
+ def credentials
30
+ {
31
+ access_token: access_token,
32
+ }
33
+ end
34
+
35
+ # @return [Boolean]
36
+ def credentials?
37
+ credentials.values.all?
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,18 @@
1
+ require 'we_chat/rest/utils'
2
+
3
+ module WeChat
4
+ module REST
5
+ module Media
6
+ include WeChat::REST::Utils
7
+
8
+ def upload(media, type)
9
+ perform_multipart_post('/media/upload', { key: 'media', file: media, params: { type: type } })
10
+ end
11
+
12
+ def upload_image(file_path)
13
+ upload(file_path, 'image')
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ require 'we_chat/rest/utils'
2
+
3
+ module WeChat
4
+ module REST
5
+ module Menu
6
+ include WeChat::REST::Utils
7
+
8
+ def menu_get
9
+ perform_get('/menu/get')
10
+ end
11
+
12
+ def menu_create(data)
13
+ perform_post('/menu/create', data)
14
+ end
15
+
16
+ def menu_delete
17
+ perform_get('/menu/delete')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,103 @@
1
+ require 'http'
2
+ require "addressable/template"
3
+ require 'we_chat/symbolizable'
4
+ require 'we_chat/multipart_ext'
5
+ require 'we_chat/headers'
6
+ require 'we_chat/error'
7
+
8
+ module WeChat
9
+ module REST
10
+ class Request
11
+ include WeChat::Symbolizable
12
+
13
+ attr_accessor :client, :headers, :options, :path,
14
+ :request_method, :uri
15
+ alias_method :verb, :request_method
16
+
17
+ def initialize(_client, request_method, path, options = {})
18
+ @client = _client
19
+ request_params = options.delete(:params) unless options[:params].nil?
20
+ request_params ||= {}
21
+ request_params[:access_token] = @client.access_token if @client.access_token?
22
+ template = ::Addressable::Template.new(path.start_with?('http') ? path : WeChat.wechat_api_base_url + path + "{?query*}")
23
+ @uri = template.expand({
24
+ "query" => request_params
25
+ })
26
+ set_multipart_options!(request_method, options)
27
+ @path = uri.path
28
+ @options = options
29
+ end
30
+
31
+ # @return [Array, Hash]
32
+ def perform
33
+ options_key = @request_method == :get ? :params : @request_method == :post ? :json : :form
34
+ response = HTTP.with(@headers).public_send(@request_method, @uri.to_s, options_key => @options)
35
+ fail_or_return_response_body(response)
36
+ end
37
+
38
+ private
39
+
40
+ def set_multipart_options!(request_method, options)
41
+ if request_method == :multipart_post
42
+ key, file = options.delete(:key), options.delete(:file)
43
+ options.merge!(key => HTTP::FormData::File.new(file, filename: File.basename(file), mime_type: mime_type(File.basename(file))))
44
+ # @request_method = :post
45
+ @headers = WeChat::Headers.new(@client, @request_method, @uri).request_headers
46
+ else
47
+ @request_method = request_method
48
+ @headers = WeChat::Headers.new(@client, @request_method, @uri, options).request_headers
49
+ end
50
+ end
51
+
52
+ def mime_type(basename)
53
+ case basename
54
+ when /\.gif$/i
55
+ 'image/gif'
56
+ when /\.jpe?g/i
57
+ 'image/jpeg'
58
+ when /\.png$/i
59
+ 'image/png'
60
+ else
61
+ 'application/octet-stream'
62
+ end
63
+ end
64
+
65
+ def fail_or_return_response_body(response)
66
+ response_body = symbolize_keys!(response.parse) if response.status.ok?
67
+ response_body ||= response.body.to_s
68
+ error = error(response, response_body, response.headers)
69
+ fail(error) if error
70
+ response_body
71
+ end
72
+
73
+ def error(response, body, headers)
74
+ error_entity = nil
75
+ if response.status.ok?
76
+ error_entity = WeChat::Error.from_response(body, headers) if contains_error?(body)
77
+ else
78
+ klass = WeChat::Error::ERRORS[response.code]
79
+ if klass == WeChat::Error::Forbidden
80
+ error_entity = forbidden_error(body, headers)
81
+ elsif !klass.nil?
82
+ error_entity = klass.from_response(nil, headers)
83
+ end
84
+ end
85
+ error_entity
86
+ end
87
+
88
+ def contains_error?(body)
89
+ body[:errcode] && body[:errcode] != 0
90
+ end
91
+
92
+ def forbidden_error(body, headers)
93
+ error = WeChat::Error::Forbidden.from_response(body, headers)
94
+ klass = WeChat::Error::FORBIDDEN_MESSAGES[error.message]
95
+ if klass
96
+ klass.from_response(body, headers)
97
+ else
98
+ error
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end