qy_wechat 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e0406f31fb56e074f7cf19633c0430a2a13dbedd
4
+ data.tar.gz: 44e9e8ae54ef833ab1ba3ba9c72ddb1f07d56d1b
5
+ SHA512:
6
+ metadata.gz: 1e1f81653a492b0b5d755a7e3b1e548e95313a38232568e4725127ac4c3ea395fae18d9c8f1b18cecc6c8580562376dabb77b70c4a4e007a6d8345f9a1f7fb3d
7
+ data.tar.gz: e95dddb6e3f94d51cd24cb086fdc6f7c1db1a8e8a199ad58291805752729e44b69a44bfd312be5de43e8b8bb6a5be55e5bee0368ee0c1852510bb2479ac4d5e8
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2014 lanrion
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.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
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 = 'QyWechat'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+ module QyWechat
3
+ class QyWechatController < ActionController::Base
4
+
5
+ include ReplyMessageHelper
6
+
7
+ skip_before_filter :verify_authenticity_token, only: :reply
8
+ before_action :setup_qy_account, only: [:verify_url, :reply]
9
+ before_action :setup_wechat_message, only: :reply
10
+
11
+ # 验证URL有效性
12
+ def verify_url
13
+ raise "Not Match" if not valid_msg_signature(params)
14
+ params.delete(:qy_secret_key)
15
+ content = Prpcrypt.decrypt(aes_key, params[:echostr], corp_id)
16
+ render text: content
17
+ end
18
+
19
+ def reply;end
20
+
21
+ private
22
+
23
+ def setup_wechat_message
24
+ param_xml = request.body.read
25
+ hash = MultiXml.parse(param_xml)['xml']
26
+ @body_xml = OpenStruct.new(hash)
27
+ hash = MultiXml.parse(Prpcrypt.decrypt(aes_key, @body_xml.Encrypt, corp_id))["xml"]
28
+ @weixin_message = Message.factory(hash)
29
+ @keyword = @weixin_message.Content
30
+ end
31
+
32
+ def encoding_aes_key
33
+ @qy_account.encoding_aes_key
34
+ end
35
+
36
+ def qy_token
37
+ @qy_account.qy_token
38
+ end
39
+
40
+ def aes_key
41
+ Base64.decode64(@qy_account.encoding_aes_key + "=")
42
+ end
43
+
44
+ def corp_id
45
+ @qy_account.corp_id
46
+ end
47
+
48
+ # String signature = SHA1.getSHA1(token, timeStamp, nonce, echoStr);
49
+ def valid_msg_signature(params)
50
+ timestamp = params[:timestamp]
51
+ nonce = params[:nonce]
52
+ echo_str = params[:echostr]
53
+ msg_signature = params[:msg_signature]
54
+ sort_params = [qy_token, timestamp, nonce, echo_str].sort.join
55
+ current_signature = Digest::SHA1.hexdigest(sort_params)
56
+ Rails.logger.info("current_signature: #{current_signature} " )
57
+ current_signature == msg_signature
58
+ end
59
+
60
+ def setup_qy_account
61
+ @qy_account ||= QyWechat.qy_model.find_by(qy_secret_key: params[:qy_secret_key])
62
+ end
63
+ end
64
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ QyWechat::Engine.routes.draw do
2
+ get "qy_wechat/:qy_secret_key", to: "qy_wechat#verify_url", as: :qy_verify
3
+ post "qy_wechat/:qy_secret_key", to: "qy_wechat#reply", as: :qy_reply_reply
4
+ end
@@ -0,0 +1,35 @@
1
+ # Rails::Generators::Base dont need a name
2
+ # Rails::Generators::NamedBase need a name
3
+ module QyWechat
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ source_root File.expand_path('../../templates', __FILE__)
7
+
8
+ desc 'Creates a QyWechat initializer for your application.'
9
+
10
+ def install
11
+ route 'mount QyWechat::Engine, at: "/"'
12
+ end
13
+
14
+ def copy_initializer
15
+ template 'qy_wechat_config.rb', 'config/initializers/qy_wechat_config.rb'
16
+ end
17
+
18
+ def configure_application
19
+ application <<-APP
20
+ config.to_prepare do
21
+ # Load application's model / class decorators
22
+ Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
23
+ Rails.configuration.cache_classes ? require(c) : load(c)
24
+ end
25
+ end
26
+ APP
27
+ end
28
+
29
+ def copy_decorators
30
+ template 'qy_wechat_controller.rb', 'app/decorators/controllers/qy_wechat/qy_wechat_controller_decorator.rb'
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ require 'rails/generators/active_record'
2
+
3
+ module QyWechat
4
+ module Generators
5
+ class MigrationGenerator < ActiveRecord::Generators::Base
6
+ source_root File.expand_path('../../templates', __FILE__)
7
+
8
+ desc 'Adds a qy qy_token, encoding_aes_key, corp_id for your application.'
9
+ def create_migration_file
10
+ if !migration_exists?(table_name)
11
+ migration_template "add_qy_wechat_columns.rb", "db/migrate/add_qy_wechat_columns_to_#{plural_name}.rb"
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def model_exists?
18
+ File.exists?(File.join(destination_root, model_path))
19
+ end
20
+
21
+ def model_path
22
+ @model_path ||= File.join("app", "models", "#{file_path}.rb")
23
+ end
24
+
25
+ def migration_exists?(table_name)
26
+ Dir.glob("#{File.join(destination_root, migration_path)}/[0-9]*_*.rb").grep(/\d+_add_qy_wechat_columns_to_#{table_name}.rb/).first
27
+ end
28
+
29
+ def migration_path
30
+ @migration_path ||= File.join("db", "migrate")
31
+ end
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ class AddQyWechatColumnsTo<%= table_name.camelize %> < ActiveRecord::Migration
2
+ def self.up
3
+ create_table(:<%= table_name %>) do |t|
4
+ t.string :qy_token
5
+ t.string :encoding_aes_key
6
+ t.string :corp_id
7
+ t.string :qy_secret_key
8
+ end
9
+ add_index :<%= table_name %>, :qy_token
10
+ add_index :<%= table_name %>, :encoding_aes_key
11
+ add_index :<%= table_name %>, :corp_id
12
+ add_index :<%= table_name %>, :qy_secret_key
13
+ end
14
+
15
+ def self.down
16
+ # By default, we don't want to make any assumption about how to roll back a migration when your
17
+ # model already existed. Please edit below which fields you would like to remove in this migration.
18
+ raise ActiveRecord::IrreversibleMigration
19
+ end
20
+ end
@@ -0,0 +1,4 @@
1
+ QyWechat.configure do |config|
2
+ # 配置保存你企业信息的Model
3
+ # config.qy_account = "QyAccount"
4
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+ QyWechat::QyWechatController.class_eval do
3
+
4
+ def reply
5
+ render xml: send("response_#{@weixin_message.MsgType}_message", {})
6
+ end
7
+
8
+ private
9
+
10
+ # text消息
11
+ def response_text_message(options={})
12
+ generate_text_message("Your Message: #{@keyword}")
13
+ end
14
+
15
+ # image消息
16
+ def response_image_message(options={})
17
+ generate_image_message(new_image(@weixin_message.MediaId))
18
+ end
19
+
20
+ # voice消息
21
+ def response_voice_message(options={})
22
+ generate_voice_message(new_voice(@weixin_message.MediaId))
23
+ end
24
+
25
+ # video消息
26
+ def response_video_message(options={})
27
+ generate_video_message(new_video(@weixin_message.MediaId, "desc", "title"))
28
+ end
29
+
30
+ end
@@ -0,0 +1,31 @@
1
+ module QyWechat
2
+
3
+ class << self
4
+
5
+ attr_accessor :configuration
6
+
7
+ def config
8
+ self.configuration ||= Configuration.new
9
+ end
10
+
11
+ def configure
12
+ yield config if block_given?
13
+ end
14
+
15
+ def qy_model_name
16
+ @qy_model_name ||= QyWechat.config.qy_account
17
+ end
18
+
19
+ def qy_model
20
+ if qy_model_name.blank?
21
+ raise "You need to config `qy_account` in 'config/initializers/qy_wechat_config.rb'"
22
+ end
23
+ @qy_model ||= qy_model_name.to_s.constantize
24
+ end
25
+
26
+ end
27
+
28
+ class Configuration
29
+ attr_accessor :qy_account
30
+ end
31
+ end
@@ -0,0 +1,6 @@
1
+ module QyWechat
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace QyWechat
4
+ engine_name :qy_wechat_engine
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ module QyWechat
2
+ module PKCS7Encoder
3
+ extend self
4
+
5
+ BLOCK_SIZE = 32
6
+
7
+ def decode(text)
8
+ pad = text[-1].ord
9
+ pad = 0 if (pad < 1 || pad > BLOCK_SIZE)
10
+ size = text.size - pad
11
+ text[0...size]
12
+ end
13
+
14
+ # 对需要加密的明文进行填充补位
15
+ # 返回补齐明文字符串
16
+ def encode(text)
17
+ # 计算需要填充的位数
18
+ amount_to_pad = BLOCK_SIZE - (text.length % BLOCK_SIZE)
19
+ amount_to_pad = BLOCK_SIZE if amount_to_pad == 0
20
+ # 获得补位所用的字符
21
+ pad_chr = amount_to_pad.chr
22
+ "#{text}#{pad_chr * amount_to_pad}"
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ module QyWechat
3
+ module Prpcrypt
4
+ extend self
5
+
6
+ # 对密文进行解密.
7
+ # text 需要解密的密文
8
+ def decrypt(aes_key, text, corpid)
9
+ text = Base64.decode64(text)
10
+ text = handle_cipher(:decrypt, aes_key, text)
11
+ result = PKCS7Encoder.decode(text)
12
+ content = result[16...result.length]
13
+ len_list = content[0...4].unpack("N")
14
+ xml_len = len_list[0]
15
+ xml_content = content[4...4 + xml_len]
16
+ from_corpid = content[xml_len+4...content.size]
17
+ raise "UnMatch corpid" if corpid != from_corpid
18
+ xml_content
19
+ end
20
+
21
+ # 加密
22
+ def encrypt(aes_key, text, corpid)
23
+ text = text.force_encoding("ASCII-8BIT")
24
+ random = SecureRandom.hex(8)
25
+ msg_len = [text.length].pack("N")
26
+ text = "#{random}#{msg_len}#{text}#{corpid}"
27
+ text = PKCS7Encoder.encode(text)
28
+ text = handle_cipher(:encrypt, aes_key, text)
29
+ Base64.encode64(text)
30
+ end
31
+
32
+ private
33
+ def handle_cipher(action, aes_key, text)
34
+ cipher = OpenSSL::Cipher.new('AES-256-CBC')
35
+ cipher.send(action)
36
+ cipher.padding = 0
37
+ cipher.key = aes_key
38
+ cipher.iv = aes_key[0...16]
39
+ cipher.update(text) + cipher.final
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,117 @@
1
+ module QyWechat
2
+ module ReplyMessageHelper
3
+
4
+ # e.g.
5
+ # generate_text_message(@weixin_message.ToUserName, @weixin_message.FromUserName, "Your Message: #{@weixin_message.Content}")
6
+ # Or generate_text_message("Your Message: #{@weixin_message.Content}")
7
+ def generate_text_message(from=nil, to=nil, content)
8
+ message = TextResponseMessage.new
9
+ message.FromUserName = from || @weixin_message.ToUserName
10
+ message.ToUserName = to || @weixin_message.FromUserName
11
+ message.Content = content
12
+ encrypt_message(message.to_xml)
13
+ end
14
+
15
+ def new_article(title, desc, pic_url, link_url)
16
+ item = Article.new
17
+ item.Title = title
18
+ item.Description = desc
19
+ item.PicUrl = pic_url
20
+ item.Url = link_url
21
+ item
22
+ end
23
+
24
+ # articles = [new_article]
25
+ def generate_news_message(from=nil, to=nil, articles)
26
+ message = NewsResponseMessage.new
27
+ message.FromUserName = from || @weixin_message.ToUserName
28
+ message.ToUserName = to || @weixin_message.FromUserName
29
+ message.Articles = articles
30
+ message.ArticleCount = articles.count
31
+ encrypt_message message.to_xml
32
+ end
33
+
34
+ def new_video(media_id, desc, title)
35
+ video = Video.new
36
+ video.MediaId = media_id
37
+ video.Title = title
38
+ video.Description = desc
39
+ video
40
+ end
41
+
42
+ # <xml>
43
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
44
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
45
+ # <CreateTime>12345678</CreateTime>
46
+ # <MsgType><![CDATA[video]]></MsgType>
47
+ # <Video>
48
+ # <MediaId><![CDATA[media_id]]></MediaId>
49
+ # <Title><![CDATA[title]]></Title>
50
+ # <Description><![CDATA[description]]></Description>
51
+ # </Video>
52
+ # </xml>
53
+
54
+ def generate_video_message(from=nil, to=nil, video)
55
+ message = VideoResponseMessage.new
56
+ message.FromUserName = from || @weixin_message.ToUserName
57
+ message.ToUserName = to || @weixin_message.FromUserName
58
+ message.Video = video
59
+ encrypt_message message.to_xml
60
+ end
61
+
62
+ def new_voice(media_id)
63
+ voice = Voice.new
64
+ voice.MediaId = media_id
65
+ voice
66
+ end
67
+
68
+ def generate_voice_message(from=nil, to=nil, voice)
69
+ message = VoiceResponseMessage.new
70
+ message.FromUserName = from || @weixin_message.ToUserName
71
+ message.ToUserName = to || @weixin_message.FromUserName
72
+ message.Voice = voice
73
+ encrypt_message message.to_xml
74
+ end
75
+
76
+ def new_image(media_id)
77
+ image = Image.new
78
+ image.MediaId = media_id
79
+ image
80
+ end
81
+
82
+ def generate_image_message(from=nil, to=nil, image)
83
+ message = ImageResponseMessage.new
84
+ message.FromUserName = from || @weixin_message.ToUserName
85
+ message.ToUserName = to || @weixin_message.FromUserName
86
+ message.Image = image
87
+ encrypt_message message.to_xml
88
+ end
89
+
90
+ private
91
+
92
+ def encrypt_message(msg_xml)
93
+ # 加密回复的XML
94
+ encrypt_xml = Prpcrypt.encrypt(aes_key, msg_xml, corp_id).gsub("\n","")
95
+ # 标准的回包
96
+ generate_encrypt_message(encrypt_xml)
97
+ end
98
+
99
+ def generate_encrypt_message(encrypt_xml)
100
+ msg = EncryptMessage.new
101
+ msg.Encrypt = encrypt_xml
102
+ msg.TimeStamp = Time.now.to_i.to_s
103
+ msg.Nonce = "123"
104
+ msg.MsgSignature = generate_msg_signature(encrypt_xml, msg)
105
+ msg.to_xml
106
+ end
107
+
108
+ # dev_msg_signature=sha1(sort(token、timestamp、nonce、msg_encrypt))
109
+ # 生成企业签名
110
+ # $array = array($encrypt_msg, $token, $timestamp, $nonce);
111
+ def generate_msg_signature(encrypt_msg, msg)
112
+ sort_params = [encrypt_msg, qy_token, msg.TimeStamp, msg.Nonce].sort.join
113
+ Digest::SHA1.hexdigest(sort_params)
114
+ end
115
+
116
+ end
117
+ end
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+ # 标准的回包
3
+ # <xml>
4
+ # <Encrypt><![CDATA[msg_encrypt]]></Encrypt>
5
+ # <MsgSignature><![CDATA[msg_signature]]></MsgSignature>
6
+ # <TimeStamp>timestamp</TimeStamp>
7
+ # <Nonce><![CDATA[nonce]]></Nonce>
8
+ # </xml>
9
+
10
+ module QyWechat
11
+ class EncryptMessage
12
+ include ROXML
13
+ xml_name :xml
14
+
15
+ xml_accessor :Encrypt, :cdata => true
16
+ xml_accessor :Nonce, :cdata => true
17
+ xml_accessor :TimeStamp, :as => Integer
18
+ xml_accessor :MsgSignature, :cdata => true
19
+
20
+ def to_xml
21
+ super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,171 @@
1
+ # encoding: utf-8
2
+ # ref: https://github.com/wolfg1969/rack-weixin/lib/weixin/model.rb
3
+ require 'roxml'
4
+ require 'multi_xml'
5
+ require 'ostruct'
6
+
7
+ # multi_xml will use Nokogiri if it is available
8
+ MultiXml.parser = :nokogiri
9
+
10
+ module QyWechat
11
+
12
+ class Message
13
+
14
+ def initialize(hash)
15
+ @source = OpenStruct.new(hash)
16
+ end
17
+
18
+ def method_missing(method, *args, &block)
19
+ @source.send(method, *args, &block)
20
+ end
21
+
22
+ def CreateTime
23
+ @source.CreateTime.to_i
24
+ end
25
+
26
+ def MsgId
27
+ @source.MsgId.to_i
28
+ end
29
+
30
+ def corp_id
31
+ @source.FromUserName
32
+ end
33
+
34
+ # 应用ID
35
+ def agent_id
36
+ @source.AgentID
37
+ end
38
+
39
+ def self.factory(hash)
40
+ case hash['MsgType']
41
+ when 'text'
42
+ TextMessage.new(hash)
43
+ when 'image'
44
+ ImageMessage.new(hash)
45
+ when 'location'
46
+ LocationMessage.new(hash)
47
+ when 'link'
48
+ LinkMessage.new(hash)
49
+ when 'event'
50
+ EventMessage.new(hash)
51
+ when 'voice'
52
+ VoiceMessage.new(hash)
53
+ when 'video'
54
+ VideoMessage.new(hash)
55
+ else
56
+ raise ArgumentError, 'Unknown Message'
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ # <xml>
63
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
64
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
65
+ # <CreateTime>1348831860</CreateTime>
66
+ # <MsgType><![CDATA[text]]></MsgType>
67
+ # <Content><![CDATA[this is a test]]></Content>
68
+ # <MsgId>1234567890123456</MsgId>
69
+ # </xml>
70
+ TextMessage = Class.new(Message)
71
+
72
+ # <xml>
73
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
74
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
75
+ # <CreateTime>1348831860</CreateTime>
76
+ # <MsgType><![CDATA[image]]></MsgType>
77
+ # <PicUrl><![CDATA[this is a url]]></PicUrl>
78
+ # <MsgId>1234567890123456</MsgId>
79
+ # </xml>
80
+ ImageMessage = Class.new(Message)
81
+
82
+ # <xml>
83
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
84
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
85
+ # <CreateTime>1351776360</CreateTime>
86
+ # <MsgType><![CDATA[link]]></MsgType>
87
+ # <Title><![CDATA[公众平台官网链接]]></Title>
88
+ # <Description><![CDATA[公众平台官网链接]]></Description>
89
+ # <Url><![CDATA[url]]></Url>
90
+ # <MsgId>1234567890123456</MsgId>
91
+ # </xml>
92
+ LinkMessage = Class.new(Message)
93
+
94
+ # <xml>
95
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
96
+ # <FromUserName><![CDATA[FromUser]]></FromUserName>
97
+ # <CreateTime>123456789</CreateTime>
98
+ # <MsgType><![CDATA[event]]></MsgType>
99
+ # <Event><![CDATA[EVENT]]></Event>
100
+ # <EventKey><![CDATA[EVENTKEY]]></EventKey>
101
+ # </xml>
102
+ EventMessage = Class.new(Message)
103
+
104
+ # <xml>
105
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
106
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
107
+ # <CreateTime>1351776360</CreateTime>
108
+ # <MsgType><![CDATA[location]]></MsgType>
109
+ # <Location_X>23.134521</Location_X>
110
+ # <Location_Y>113.358803</Location_Y>
111
+ # <Scale>20</Scale>
112
+ # <Label><![CDATA[位置信息]]></Label>
113
+ # <MsgId>1234567890123456</MsgId>
114
+ # </xml>
115
+ class LocationMessage < Message
116
+
117
+ def Location_X
118
+ @source.Location_X.to_f
119
+ end
120
+
121
+ def Location_Y
122
+ @source.Location_Y.to_f
123
+ end
124
+
125
+ def Scale
126
+ @source.Scale.to_i
127
+ end
128
+ end
129
+
130
+ # <xml>
131
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
132
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
133
+ # <CreateTime>1376632760</CreateTime>
134
+ # <MsgType><![CDATA[voice]]></MsgType>
135
+ # <MediaId><![CDATA[Qyb0tgux6QLjhL6ipvFZJ-kUt2tcQtkn0BU365Vt3wUAtqfGam4QpZU35RXVhv6G]]></MediaId>
136
+ # <Format><![CDATA[amr]]></Format>
137
+ # <MsgId>5912592682802219078</MsgId>
138
+ # <Recognition><![CDATA[]]></Recognition>
139
+ # </xml>
140
+ class VoiceMessage < Message
141
+
142
+ def MediaId
143
+ @source.MediaId
144
+ end
145
+
146
+ def Format
147
+ @source.Format
148
+ end
149
+ end
150
+
151
+ # <xml>
152
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
153
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
154
+ # <CreateTime>1376632994</CreateTime>
155
+ # <MsgType><![CDATA[video]]></MsgType>
156
+ # <MediaId><![CDATA[TAAGb6iS5LcZR1d5ICiZTWGWi6-Upic9tlWDpAKcNJA]]></MediaId>
157
+ # <ThumbMediaId><![CDATA[U-xulPW4kq6KKMWFNaBSPc65Bcgr7Qopwex0DfCeyQs]]></ThumbMediaId>
158
+ # <MsgId>5912593687824566343</MsgId>
159
+ # </xml>
160
+ class VideoMessage < Message
161
+
162
+ def MediaId
163
+ @source.MediaId
164
+ end
165
+
166
+ def ThumbMediaId
167
+ @source.ThumbMediaId
168
+ end
169
+ end
170
+
171
+ end
@@ -0,0 +1,152 @@
1
+ # encoding: utf-8
2
+ # ref: https://github.com/wolfg1969/rack-weixin/lib/weixin/model.rb
3
+ require 'roxml'
4
+
5
+ module QyWechat
6
+
7
+ class ResponseMessage
8
+ include ROXML
9
+ xml_name :xml
10
+
11
+ xml_accessor :ToUserName, :cdata => true
12
+ xml_accessor :FromUserName, :cdata => true
13
+ xml_reader :CreateTime, :as => Integer
14
+ xml_reader :MsgType, :cdata => true
15
+
16
+ def initialize
17
+ @CreateTime = Time.now.to_i
18
+ end
19
+
20
+ def to_xml
21
+ super.to_xml(:encoding => 'UTF-8', :indent => 0, :save_with => 0)
22
+ end
23
+ end
24
+
25
+ # <xml>
26
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
27
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
28
+ # <CreateTime>12345678</CreateTime>
29
+ # <MsgType><![CDATA[text]]></MsgType>
30
+ # <Content><![CDATA[Hello]]></Content>
31
+ # </xml>
32
+
33
+ class TextResponseMessage < ResponseMessage
34
+ xml_accessor :Content, :cdata => true
35
+ def initialize
36
+ super
37
+ @MsgType = 'text'
38
+ end
39
+ end
40
+
41
+ class Article
42
+ include ROXML
43
+ xml_accessor :Title, :cdata => true
44
+ xml_accessor :Description, :cdata => true
45
+ xml_accessor :PicUrl, :cdata => true
46
+ xml_accessor :Url, :cdata => true
47
+ end
48
+
49
+ # <xml>
50
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
51
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
52
+ # <CreateTime>12345678</CreateTime>
53
+ # <MsgType><![CDATA[news]]></MsgType>
54
+ # <ArticleCount>2</ArticleCount>
55
+ # <Articles>
56
+ # <item>
57
+ # <Title><![CDATA[title1]]></Title>
58
+ # <Description><![CDATA[description1]]></Description>
59
+ # <PicUrl><![CDATA[picurl]]></PicUrl>
60
+ # <Url><![CDATA[url]]></Url>
61
+ # </item>
62
+ # <item>
63
+ # <Title><![CDATA[title]]></Title>
64
+ # <Description><![CDATA[description]]></Description>
65
+ # <PicUrl><![CDATA[picurl]]></PicUrl>
66
+ # <Url><![CDATA[url]]></Url>
67
+ # </item>
68
+ # </Articles>
69
+ # </xml>
70
+
71
+ class NewsResponseMessage < ResponseMessage
72
+ xml_accessor :ArticleCount, :as => Integer
73
+ xml_accessor :Articles, :as => [Article], :in => 'Articles', :from => 'item'
74
+ def initialize
75
+ super
76
+ @MsgType = 'news'
77
+ end
78
+ end
79
+
80
+ # <xml>
81
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
82
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
83
+ # <CreateTime>12345678</CreateTime>
84
+ # <MsgType><![CDATA[video]]></MsgType>
85
+ # <Video>
86
+ # <MediaId><![CDATA[media_id]]></MediaId>
87
+ # <Title><![CDATA[title]]></Title>
88
+ # <Description><![CDATA[description]]></Description>
89
+ # </Video>
90
+ # </xml>
91
+
92
+ class Video
93
+ include ROXML
94
+ xml_accessor :MediaId, :cdata => true
95
+ xml_accessor :Description, :cdata => true
96
+ xml_accessor :Title, :cdata => true
97
+ end
98
+
99
+ class VideoResponseMessage < ResponseMessage
100
+ xml_accessor :Video, :as => Video
101
+ def initialize
102
+ super
103
+ @MsgType = 'video'
104
+ end
105
+ end
106
+
107
+ # <xml>
108
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
109
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
110
+ # <CreateTime>12345678</CreateTime>
111
+ # <MsgType><![CDATA[voice]]></MsgType>
112
+ # <Voice>
113
+ # <MediaId><![CDATA[media_id]]></MediaId>
114
+ # </Voice>
115
+ # </xml>
116
+ class Voice
117
+ include ROXML
118
+ xml_accessor :MediaId, :cdata => true
119
+ end
120
+
121
+ class VoiceResponseMessage < ResponseMessage
122
+ xml_accessor :Voice, :as => Voice
123
+ def initialize
124
+ super
125
+ @MsgType = 'voice'
126
+ end
127
+ end
128
+
129
+ # <xml>
130
+ # <ToUserName><![CDATA[toUser]]></ToUserName>
131
+ # <FromUserName><![CDATA[fromUser]]></FromUserName>
132
+ # <CreateTime>12345678</CreateTime>
133
+ # <MsgType><![CDATA[image]]></MsgType>
134
+ # <Image>
135
+ # <MediaId><![CDATA[media_id]]></MediaId>
136
+ # </Image>
137
+ # </xml>
138
+
139
+ class Image
140
+ include ROXML
141
+ xml_accessor :MediaId, :cdata => true
142
+ end
143
+
144
+ class ImageResponseMessage < ResponseMessage
145
+ xml_accessor :Image, :as => Image
146
+ def initialize
147
+ super
148
+ @MsgType = 'image'
149
+ end
150
+ end
151
+
152
+ end
@@ -0,0 +1,3 @@
1
+ module QyWechat
2
+ VERSION = "1.0.0.beta1"
3
+ end
data/lib/qy_wechat.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'roxml'
2
+ require 'multi_xml'
3
+ require 'ostruct'
4
+
5
+ require "qy_wechat/configuration"
6
+ require "qy_wechat/engine"
7
+ require "qy_wechat/message/encrypt_message"
8
+ require "qy_wechat/message/message"
9
+ require "qy_wechat/message/response_message"
10
+
11
+ require "qy_wechat/helpers/pkcs7_encoder"
12
+ require "qy_wechat/helpers/prpcrypt"
13
+ require "qy_wechat/helpers/reply_message_helper"
14
+
15
+ module QyWechat
16
+ end
metadata ADDED
@@ -0,0 +1,134 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: qy_wechat
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0.beta1
5
+ platform: ruby
6
+ authors:
7
+ - lanrion
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-11-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.6.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 1.6.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '3.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '3.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: multi_xml
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 0.5.2
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 0.5.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: roxml
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description: "微信企业版本,应答API集成"
84
+ email:
85
+ - huaitao-deng@foxmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - MIT-LICENSE
91
+ - Rakefile
92
+ - app/controllers/qy_wechat/qy_wechat_controller.rb
93
+ - config/routes.rb
94
+ - lib/generators/qy_wechat/install_generator.rb
95
+ - lib/generators/qy_wechat/migration_generator.rb
96
+ - lib/generators/templates/add_qy_wechat_columns.rb
97
+ - lib/generators/templates/qy_wechat_config.rb
98
+ - lib/generators/templates/qy_wechat_controller.rb
99
+ - lib/qy_wechat.rb
100
+ - lib/qy_wechat/configuration.rb
101
+ - lib/qy_wechat/engine.rb
102
+ - lib/qy_wechat/helpers/pkcs7_encoder.rb
103
+ - lib/qy_wechat/helpers/prpcrypt.rb
104
+ - lib/qy_wechat/helpers/reply_message_helper.rb
105
+ - lib/qy_wechat/message/encrypt_message.rb
106
+ - lib/qy_wechat/message/message.rb
107
+ - lib/qy_wechat/message/response_message.rb
108
+ - lib/qy_wechat/version.rb
109
+ homepage: https://github.com/lanrion/qy_wechat
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">"
125
+ - !ruby/object:Gem::Version
126
+ version: 1.3.1
127
+ requirements: []
128
+ rubyforge_project:
129
+ rubygems_version: 2.2.2
130
+ signing_key:
131
+ specification_version: 4
132
+ summary: "微信企业版本"
133
+ test_files: []
134
+ has_rdoc: