we_chat 0.5.15

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.
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