wei-backend 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 89c1731dc45e222e2af055b01d36d25ad34cfa71
4
+ data.tar.gz: 0d958cec757c981191d472fc29aebdac9bc42c43
5
+ SHA512:
6
+ metadata.gz: bd00720f925a9843733ac78cd680b55c2846a6d9d72555d7a3e7e40b814cb12ae468494bdb37c7d8eec5d177bd137e4b8f5db04ada1fa247616ec6be3dd34f16
7
+ data.tar.gz: 8c680f884c42ab2c0840bb53ef53b093566f95dbb2d761aacae32755c7ac9f2cfa8c93cf6989b0c81be0a506cb03402626413c27cae3f685f887a6724d8a623f
data/Gemfile ADDED
@@ -0,0 +1,21 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'json'
4
+ gem 'haml'
5
+ gem 'sinatra'
6
+ gem 'nokogiri'
7
+ gem 'capistrano'
8
+ gem 'httparty'
9
+ gem 'thin'
10
+ gem 'hash_validator'
11
+
12
+ group :test do
13
+ gem 'rspec'
14
+ gem 'rspec-html-matchers'
15
+ gem 'rack-test'
16
+ end
17
+
18
+ group :development do
19
+ gem 'rerun'
20
+ gem 'shotgun'
21
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,90 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ capistrano (3.0.1)
5
+ i18n
6
+ rake (>= 10.0.0)
7
+ sshkit (>= 0.0.23)
8
+ daemons (1.1.9)
9
+ diff-lcs (1.2.5)
10
+ eventmachine (1.0.3)
11
+ ffi (1.9.3)
12
+ haml (4.0.4)
13
+ tilt
14
+ hash_validator (0.2.7)
15
+ httparty (0.12.0)
16
+ json (~> 1.8)
17
+ multi_xml (>= 0.5.2)
18
+ i18n (0.6.5)
19
+ json (1.8.1)
20
+ listen (1.0.3)
21
+ rb-fsevent (>= 0.9.3)
22
+ rb-inotify (>= 0.9)
23
+ rb-kqueue (>= 0.2)
24
+ mini_portile (0.5.2)
25
+ multi_xml (0.5.5)
26
+ net-scp (1.1.2)
27
+ net-ssh (>= 2.6.5)
28
+ net-ssh (2.7.0)
29
+ nokogiri (1.6.0)
30
+ mini_portile (~> 0.5.0)
31
+ rack (1.5.2)
32
+ rack-protection (1.5.1)
33
+ rack
34
+ rack-test (0.6.2)
35
+ rack (>= 1.0)
36
+ rake (10.1.0)
37
+ rb-fsevent (0.9.3)
38
+ rb-inotify (0.9.2)
39
+ ffi (>= 0.5.0)
40
+ rb-kqueue (0.2.0)
41
+ ffi (>= 0.5.0)
42
+ rerun (0.8.2)
43
+ listen (~> 1.0.3)
44
+ rspec (2.14.1)
45
+ rspec-core (~> 2.14.0)
46
+ rspec-expectations (~> 2.14.0)
47
+ rspec-mocks (~> 2.14.0)
48
+ rspec-core (2.14.7)
49
+ rspec-expectations (2.14.4)
50
+ diff-lcs (>= 1.1.3, < 2.0)
51
+ rspec-html-matchers (0.4.3)
52
+ nokogiri (>= 1.4.4)
53
+ rspec (>= 2.0.0)
54
+ rspec-mocks (2.14.4)
55
+ shotgun (0.9)
56
+ rack (>= 1.0)
57
+ sinatra (1.4.4)
58
+ rack (~> 1.4)
59
+ rack-protection (~> 1.4)
60
+ tilt (~> 1.3, >= 1.3.4)
61
+ sshkit (1.2.0)
62
+ net-scp (>= 1.1.2)
63
+ net-ssh
64
+ term-ansicolor
65
+ term-ansicolor (1.2.2)
66
+ tins (~> 0.8)
67
+ thin (1.6.1)
68
+ daemons (>= 1.0.9)
69
+ eventmachine (>= 1.0.0)
70
+ rack (>= 1.0.0)
71
+ tilt (1.4.1)
72
+ tins (0.13.1)
73
+
74
+ PLATFORMS
75
+ ruby
76
+
77
+ DEPENDENCIES
78
+ capistrano
79
+ haml
80
+ hash_validator
81
+ httparty
82
+ json
83
+ nokogiri
84
+ rack-test
85
+ rerun
86
+ rspec
87
+ rspec-html-matchers
88
+ shotgun
89
+ sinatra
90
+ thin
data/README.md ADDED
@@ -0,0 +1,23 @@
1
+ 微信公众平台后台框架
2
+ ========
3
+ ## Ruby version:
4
+ **ruby-2.0.0-p247**
5
+
6
+ 可以使用[rvm](http://rvm.io)安装:
7
+
8
+ rvm install ruby-2.0.0-p247
9
+
10
+ ## 如何定制
11
+ * 启动
12
+
13
+ git clone https://github.com/charleyw/weixin-sinatra.git
14
+ bundle install
15
+ rake dev:start
16
+ * 所有的业务逻辑可以查看`lib/wei_xin_request_handler`
17
+
18
+ 访问`http://localhost:9393?echostr=test`,页面会显示**echostr**的值
19
+
20
+ ## 如何部署
21
+ git clone https://github.com/charleyw/weixin-sinatra.git
22
+ bundle install
23
+ rake prod:start
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ namespace :dev do
2
+ desc 'start application in local env'
3
+ task :start do
4
+ sh 'bundle exec shotgun config.ru'
5
+ end
6
+ end
7
+
8
+ namespace :prod do
9
+ desc 'start production'
10
+ task :start do
11
+ sh 'bundle exec thin -d -p 80 start'
12
+ end
13
+
14
+ desc 'stop production'
15
+ task :stop do
16
+ sh 'bundle exec thin stop'
17
+ end
18
+
19
+ desc 'restart production'
20
+ task :restart do
21
+ sh 'bundle exec thin restart'
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'httparty'
2
+ require 'uri'
3
+
4
+ class AiBangClient
5
+ def initialize(api_base_url, api_key)
6
+ %w(lines transfer stats).each do |category|
7
+ instance_variable_set("@#{category}_api_url", api_base_url + "/#{category}?app_key=#{api_key}&alt=json")
8
+ end
9
+ end
10
+ def bus_lines(city, query)
11
+ encoded_city = URI.encode city
12
+ encoded_query = URI.encode query
13
+ api_url = "#{@lines_api_url}&city=#{encoded_city}&q=#{encoded_query}"
14
+ result = HTTParty.get(api_url).parsed_response["lines"]
15
+ result.has_key?('line') ? result['line'] : []
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ require 'yaml'
2
+
3
+ require './ai_bang_client'
4
+ require './bus_helper'
5
+ require_relative '../../lib/wei-backend'
6
+
7
+ CONFIG = YAML.load_file("./config/#{ENV['RACK_ENV']}.yml")
8
+
9
+ on_text do
10
+ aibang_client = AiBangClient.new CONFIG[:ai_bang_api], CONFIG[:ai_bang_api_key]
11
+ bus_helper = BusHelper.new aibang_client
12
+ bus_helper.bus_lines_running_time(params[:Content])
13
+ end
14
+
15
+ on_event do
16
+ if params[:Content].eql? 'subscribe'
17
+ CONFIG[:subscribe_message]
18
+ else
19
+ CONFIG[:unsubscribe_message]
20
+ end
21
+ end
22
+
23
+ on_voice do
24
+ puts 'on voice message'
25
+ end
@@ -0,0 +1,19 @@
1
+ class BusHelper
2
+ def initialize(ai_bang_client)
3
+ @ai_bang_client = ai_bang_client
4
+ end
5
+
6
+ def bus_lines_running_time(query)
7
+ lines = query.scan(/\w*\d+/)
8
+ bus_num = lines[0].strip if lines.length > 0
9
+ city = query.sub(/#{bus_num}.*/, '').strip
10
+ city = "西安" if city.empty?
11
+ bus_lines_results = @ai_bang_client.bus_lines(city, bus_num)
12
+ result = "";
13
+ bus_lines_results.each do |line|
14
+ running_time = line["info"].scan(/\d{1,2}[::]\d{1,2}-{1,2}\d{1,2}[::]\d{1,2}/)[0]
15
+ result += line["name"] + " " + running_time + "\n\n" if !running_time.nil?
16
+ end
17
+ result
18
+ end
19
+ end
@@ -0,0 +1,12 @@
1
+ require './app'
2
+
3
+ ENV['RACK_ENV'] ||= development
4
+
5
+ root_dir = File.dirname(__FILE__)
6
+
7
+ set :environment, ENV['RACK_ENV'].to_sym
8
+ set :root, root_dir
9
+ set :app_file, File.join(root_dir, 'app.rb')
10
+ disable :run
11
+
12
+ run Sinatra::Application
@@ -0,0 +1,7 @@
1
+ :ai_bang_api: http://openapi.aibang.com/bus
2
+
3
+ :ai_bang_api_key: 517dead47985e41ec11baa38362be69d
4
+
5
+ :subscribe_message: 感谢关注等公交,现在等公交支持查询公交线路的运行时间,发送:城市名公交号,如:西安710,就可以获取到公交的运行时间
6
+
7
+ :unsubscribe_message: 感谢您对等公交的关注,希望将来能再遇到您!
@@ -0,0 +1,7 @@
1
+ :ai_bang_api: http://openapi.aibang.com/bus
2
+
3
+ :ai_bang_api_key: 517dead47985e41ec11baa38362be69d
4
+
5
+ :subscribe_message: 感谢关注等公交,现在等公交支持查询公交线路的运行时间,发送:城市名公交号,如:西安710,就可以获取到公交的运行时间
6
+
7
+ :unsubscribe_message: 感谢您对等公交的关注,希望将来能再遇到您!
@@ -0,0 +1,7 @@
1
+ :ai_bang_api: http://openapi.aibang.com/bus
2
+
3
+ :ai_bang_api_key: 517dead47985e41ec11baa38362be69d
4
+
5
+ :subscribe_message: 感谢关注等公交,现在等公交支持查询公交线路的运行时间,发送:城市名公交号,如:西安710,就可以获取到公交的运行时间
6
+
7
+ :unsubscribe_message: 感谢您对等公交的关注,希望将来能再遇到您!
@@ -0,0 +1,22 @@
1
+ require 'rspec'
2
+ require_relative '../ai_bang_client'
3
+
4
+ class MockResponse
5
+ def parsed_response
6
+ JSON.parse IO.read('spec/fixtures/bus_lines_response.json')
7
+ end
8
+ end
9
+ module HTTParty
10
+ def self.get(*args, &block)
11
+ MockResponse.new
12
+ end
13
+ end
14
+
15
+ describe 'aibang api client' do
16
+
17
+ it 'should bus lines as json object when search for line 6' do
18
+ aibang_client = AiBangClient.new 'http://localhost/bus', 'api_key'
19
+ aibang_client.bus_lines("city", "query").to_s.should include "6\\u8DEF(\\u6021\\u56ED\\u8DEF\\u5317\\u53E3-\\u706B\\u8F66\\u7AD9\\u897F)"
20
+ end
21
+
22
+ end
@@ -0,0 +1,19 @@
1
+ require 'rspec'
2
+ require_relative '../ai_bang_client'
3
+ require_relative '../bus_helper'
4
+
5
+ describe "bus helper" do
6
+
7
+ it "should return bus running time when user search for line 6 running time and city is xi'an" do
8
+ aibang_client = double(AiBangClient, :bus_lines => (JSON.parse IO.read('spec/fixtures/bus_lines_response.json'))["lines"]["line"])
9
+ bus_helper = BusHelper.new aibang_client
10
+ bus_helper.bus_lines_running_time("西安6路").should include "6\u8def(\u706b\u8f66\u7ad9\u897f-\u6021\u56ed\u8def\u5317\u53e3) 6:00-20:30"
11
+ end
12
+
13
+ it "should return bus running time when user search for line k700 running time and city is xi'an" do
14
+ aibang_client = double(AiBangClient, :bus_lines => (JSON.parse IO.read('spec/fixtures/bus_lines_response.json'))["lines"]["line"])
15
+ bus_helper = BusHelper.new aibang_client
16
+ bus_helper.bus_lines_running_time("西安k700路").should include "6\u8def(\u706b\u8f66\u7ad9\u897f-\u6021\u56ed\u8def\u5317\u53e3) 6:00-20:30"
17
+ end
18
+
19
+ end
@@ -0,0 +1,44 @@
1
+ {
2
+ "result_num": "6",
3
+ "web_url": "http:\/\/bus.aibang.com",
4
+ "wap_url": "http:\/\/wap.aibang.com",
5
+ "lines": {
6
+ "line": [{
7
+ "name": "6\u8def(\u6021\u56ed\u8def\u5317\u53e3-\u706b\u8f66\u7ad9\u897f)",
8
+ "info": "\u5e02\u533a\u7ebf\u8def; \u706b\u8f66\u7ad9\u897f-\u6021\u56ed\u8def\u5317\u53e3 6:00-20:30,\u6021\u56ed\u8def\u5317\u53e3\u2014\u706b\u8f66\u7ad9\u897f 6:00-20:30; 1\u5143\u4e00\u7968\u5236 \u666e\u901a\u53610.5\u5143 \u5b66\u751f\u53610.3\u5143\u3002",
9
+ "stats": "\u6021\u56ed\u8def\u5317\u53e3;\u60a6\u56ed\u8def\u5317\u53e3;\u9526\u4e1a\u4e8c\u8def;\u4e2d\u5174\u901a\u8baf;\u5357\u4e09\u73af;\u9526\u4e1a\u8def;\u7701\u6e38\u6cf3\u4e2d\u5fc3;\u897f\u4e07\u8def\u53e3;\u6728\u5854\u5be8;\u5e02\u5efa\u56db\u516c\u53f8;\u7535\u5b50\u4e8c\u8def\u897f\u53e3;\u7535\u5b50\u5546\u57ce;\u6c99\u4e95\u6751;\u897f\u659c\u4e03\u8def;\u516c\u4ea4\u4e94\u516c\u53f8;\u897f\u659c\u516d\u8def;\u592a\u767d\u8def\u7acb\u4ea4;\u8fb9\u5bb6\u6751;\u9ec4\u96c1\u6751;\u5f20\u5bb6\u6751;\u542b\u5149\u95e8;\u5c0f\u5357\u95e8(\u4e34\u65f6\u53d6\u6d88);\u6731\u96c0\u95e8(\u4e34\u65f6\u53d6\u6d88);\u5357\u95e8(\u4e34\u65f6\u53d6\u6d88);\u949f\u697c;\u5317\u5927\u8857;\u5317\u95e8;\u897f\u95f8\u53e3\u5357\u53e3;\u706b\u8f66\u7ad9\u897f",
10
+ "stat_xys": "",
11
+ "xys": ""
12
+ }, {
13
+ "name": "6\u8def(\u706b\u8f66\u7ad9\u897f-\u6021\u56ed\u8def\u5317\u53e3)",
14
+ "info": "\u5e02\u533a\u7ebf\u8def; \u706b\u8f66\u7ad9\u897f-\u6021\u56ed\u8def\u5317\u53e3 6:00-20:30,\u6021\u56ed\u8def\u5317\u53e3\u2014\u706b\u8f66\u7ad9\u897f 6:00-20:30; 1\u5143\u4e00\u7968\u5236 \u666e\u901a\u53610.5\u5143 \u5b66\u751f\u53610.3\u5143\u3002",
15
+ "stats": "\u706b\u8f66\u7ad9\u897f;\u897f\u95f8\u53e3\u5357\u53e3;\u5317\u95e8;\u5317\u5927\u8857;\u949f\u697c;\u5357\u95e8(\u4e34\u65f6\u53d6\u6d88);\u6731\u96c0\u95e8(\u4e34\u65f6\u53d6\u6d88);\u5c0f\u5357\u95e8(\u4e34\u65f6\u53d6\u6d88);\u542b\u5149\u95e8;\u5f20\u5bb6\u6751;\u9ec4\u96c1\u6751;\u8fb9\u5bb6\u6751;\u592a\u767d\u8def\u7acb\u4ea4;\u897f\u659c\u516d\u8def;\u516c\u4ea4\u4e94\u516c\u53f8;\u897f\u659c\u4e03\u8def;\u6c99\u4e95\u6751;\u7535\u5b50\u5546\u57ce;\u7535\u5b50\u4e8c\u8def\u897f\u53e3;\u5e02\u5efa\u56db\u516c\u53f8;\u6728\u5854\u5be8;\u897f\u4e07\u8def\u53e3;\u7701\u6e38\u6cf3\u4e2d\u5fc3;\u9526\u4e1a\u8def;\u5357\u4e09\u73af;\u4e2d\u5174\u901a\u8baf;\u9526\u4e1a\u4e8c\u8def;\u60a6\u56ed\u8def\u5317\u53e3;\u6021\u56ed\u8def\u5317\u53e3",
16
+ "stat_xys": "",
17
+ "xys": ""
18
+ }, {
19
+ "name": "\u6e386(\u5510\u82d1-\u706b\u8f66\u7ad9)",
20
+ "info": "\u65c5\u6e38\u7ebf\u8def; \u706b\u8f66\u7ad9\u4e1c\u5e7f\u573a--\u5510\u82d1 6:40--19:30; \u8d77\u6b655\u89d2\uff0c5\u89d2\u8fdb\u4f4d\uff0c\u5168\u7a0b3.5\u5143\u3002",
21
+ "stats": "\u5510\u82d1;\u6797\u5e26\u8def\u91c7\u6458\u56ed;\u66f2\u6c5f\u751f\u6001\u82b1\u56ed;\u9ec4\u6e20\u5934\u6751;\u77f3\u7f8a\u519c\u5e84;\u5b5f\u6751;\u7406\u5de5\u5927\u66f2\u6c5f\u6821\u533a;\u9752\u9f99\u5bfa;\u94c1\u7089\u5e99;\u65b0\u7586\u4e09\u6240;\u89c2\u97f3\u5e99;\u5317\u6c60\u5934;\u897f\u5f71\u8def;\u5e02\u59d4\u515a\u6821;\u5927\u96c1\u5854;\u7fe0\u534e\u8def;\u5c0f\u5be8;\u5927\u5174\u5584\u5bfa(\u53d6\u6d88);\u957f\u5b89\u7acb\u4ea4(\u897f\u5b89\u97f3\u4e50\u5b66\u9662);\u7701\u4f53\u80b2\u573a(\u9655\u897f\u7701\u56fe\u4e66\u9986;\u8349\u573a\u5761;\u5357\u7a0d\u95e8;\u5357\u95e8;\u6587\u660c\u95e8;\u548c\u5e73\u95e8(\u53d6\u6d88);\u534e\u590f\u94f6\u884c;\u5927\u5dee\u5e02;\u6c11\u4e50\u56ed;\u4e94\u8def\u53e3;\u706b\u8f66\u7ad9",
22
+ "stat_xys": "",
23
+ "xys": ""
24
+ }, {
25
+ "name": "\u6e386(\u706b\u8f66\u7ad9-\u5510\u82d1)",
26
+ "info": "\u65c5\u6e38\u7ebf\u8def; \u706b\u8f66\u7ad9\u4e1c\u5e7f\u573a--\u5510\u82d1 6:40--19:30; \u8d77\u6b655\u89d2\uff0c5\u89d2\u8fdb\u4f4d\uff0c\u5168\u7a0b3.5\u5143\u3002",
27
+ "stats": "\u706b\u8f66\u7ad9;\u4e94\u8def\u53e3;\u6c11\u4e50\u56ed;\u5927\u5dee\u5e02;\u534e\u590f\u94f6\u884c;\u548c\u5e73\u95e8;\u6587\u660c\u95e8;\u5357\u95e8;\u5357\u7a0d\u95e8;\u8349\u573a\u5761;\u7701\u4f53\u80b2\u573a(\u9655\u897f\u7701\u56fe\u4e66\u9986;\u957f\u5b89\u7acb\u4ea4(\u897f\u5b89\u97f3\u4e50\u5b66\u9662);\u5927\u5174\u5584\u5bfa(\u53d6\u6d88);\u5c0f\u5be8;\u7fe0\u534e\u8def;\u5927\u96c1\u5854;\u5927\u96c1\u5854\u5357\u5e7f\u573a;\u96c1\u5f15\u8def;\u5e02\u59d4\u515a\u6821;\u897f\u5f71\u8def;\u5317\u6c60\u5934;\u89c2\u97f3\u5e99;\u65b0\u7586\u4e09\u6240;\u94c1\u7089\u5e99;\u9752\u9f99\u5bfa;\u7406\u5de5\u5927\u66f2\u6c5f\u6821\u533a;\u5b5f\u6751;\u77f3\u7f8a\u519c\u5e84;\u9ec4\u6e20\u5934\u6751;\u66f2\u6c5f\u751f\u6001\u82b1\u56ed;\u6797\u5e26\u8def\u91c7\u6458\u56ed;\u5510\u82d1",
28
+ "stat_xys": "",
29
+ "xys": ""
30
+ }, {
31
+ "name": "\u673a\u573a\u5927\u5df46\u53f7\u7ebf(\u54b8\u9633\u673a\u573a-\u5f69\u8679\u5bbe\u9986)",
32
+ "info": "\u673a\u573a:09:00-21:00;\u5f69\u8679\u5bbe\u9986:07:00-19:00;\u8d39\u7528:15\u5143+1\u5143\uff08\u71c3\u6cb9\u9644\u52a0\u8d39\uff09\/\u4eba;",
33
+ "stats": "\u54b8\u9633\u673a\u573a;\u54b8\u9633\u706b\u8f66\u7ad9;\u6e2d\u57ce\u4e2d\u5b66;\u54b8\u9633\u5e02\u653f\u5e9c;\u6c11\u751f\u5546\u53a6;\u5f69\u8679\u5bbe\u9986",
34
+ "stat_xys": "",
35
+ "xys": ""
36
+ }, {
37
+ "name": "\u673a\u573a\u5927\u5df46\u53f7\u7ebf(\u5f69\u8679\u5bbe\u9986-\u54b8\u9633\u673a\u573a)",
38
+ "info": "\u673a\u573a:09:00-21:00;\u5f69\u8679\u5bbe\u9986:07:00-19:00;\u8d39\u7528:15\u5143+1\u5143\uff08\u71c3\u6cb9\u9644\u52a0\u8d39\uff09\/\u4eba;",
39
+ "stats": "\u5f69\u8679\u5bbe\u9986;\u6c11\u751f\u5546\u53a6;\u54b8\u9633\u5e02\u653f\u5e9c;\u6e2d\u57ce\u4e2d\u5b66;\u54b8\u9633\u706b\u8f66\u7ad9;\u54b8\u9633\u673a\u573a",
40
+ "stat_xys": "",
41
+ "xys": ""
42
+ }]
43
+ }
44
+ }
@@ -0,0 +1,3 @@
1
+ require 'wei-backend/base'
2
+ require 'wei-backend/main'
3
+ require 'wei-backend/utils'
@@ -0,0 +1,76 @@
1
+ module WeiBackend
2
+ class MessageDispatcher
3
+ attr_accessor :params
4
+
5
+ def on message_type, params
6
+ @params = params
7
+ results = send(:"handle_#{message_type.downcase}_message")
8
+ create_model results
9
+ end
10
+
11
+ def create_model data
12
+ data.is_a?(Hash) || data.is_a?(Array) ? image_text_message(data) : text_message(data)
13
+ end
14
+
15
+ def text_message(data)
16
+ {
17
+ :format => 'text',
18
+ :model => {:content => data}.merge(account_info)
19
+ }
20
+ end
21
+
22
+ def image_text_message model
23
+ {
24
+ :format => 'image_text',
25
+ :model => {
26
+ :article_count => model.is_a?(Array) ? model.length : 1,
27
+ :articles => model.is_a?(Array) ? model : [model]
28
+ }.merge(account_info)
29
+ }
30
+ end
31
+
32
+ def account_info
33
+ {
34
+ :myAccount => params[:ToUserName],
35
+ :userAccount => params[:FromUserName],
36
+ }
37
+ end
38
+
39
+ def self.on_text &block
40
+ define_method(:handle_text_message, &block)
41
+ end
42
+
43
+ def self.on_event &block
44
+ define_method(:handle_event_message, &block)
45
+ end
46
+
47
+ def self.on_voice &block
48
+ define_method(:handle_voice_message, &block)
49
+ end
50
+
51
+ def self.on_location &block
52
+ define_method(:handle_location_message, &block)
53
+ end
54
+
55
+
56
+ end
57
+
58
+ module Delegator
59
+ def self.delegate(*methods)
60
+ methods.each do |method_name|
61
+ define_method(method_name) do |*args, &block|
62
+ Delegator.target.send(method_name, *args, &block)
63
+ end
64
+ private method_name
65
+ end
66
+ end
67
+
68
+ delegate :on_text, :on_event, :on_voice, :on_location
69
+
70
+ class << self
71
+ attr_accessor :target
72
+ end
73
+
74
+ self.target = MessageDispatcher
75
+ end
76
+ end
@@ -0,0 +1,23 @@
1
+ require 'sinatra'
2
+ require 'nokogiri'
3
+
4
+ get '/' do
5
+ params[:echostr]
6
+ end
7
+
8
+ post '/' do
9
+ request.body.rewind
10
+ weixin_params = WeiBackend::Utils.parse_params request.body.read
11
+ handler = WeiBackend::MessageDispatcher.new
12
+ results = handler.on weixin_params[:MsgType], weixin_params
13
+
14
+ haml results[:format].to_sym, :views => File.dirname(__FILE__)+'/wei-templates', :locals => results[:model]
15
+ end
16
+
17
+ helpers do
18
+ def cdata content
19
+ "<![CDATA[#{content}]]>"
20
+ end
21
+ end
22
+
23
+ extend WeiBackend::Delegator
@@ -0,0 +1,12 @@
1
+ module WeiBackend
2
+ module Utils
3
+ def self.parse_params(request_body)
4
+ doc = Nokogiri::XML::Document.parse request_body
5
+ result = {}
6
+ doc.at_css('xml').element_children.each do |child|
7
+ result[child.name.to_sym] = child.child.text
8
+ end
9
+ result
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module WeiBackend
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,13 @@
1
+ %xml
2
+ %ToUserName= cdata userAccount
3
+ %FromUserName= cdata myAccount
4
+ %CreateTime= cdata Time.now.to_i
5
+ %MsgType= cdata 'news'
6
+ %ArticleCount= article_count
7
+ %Articles
8
+ - articles.each do |articles|
9
+ %item
10
+ %Title= cdata articles[:title]
11
+ %Description= cdata articles[:description]
12
+ %PicUrl= cdata articles[:picture_url]
13
+ %Url= cdata articles[:url]
@@ -0,0 +1,6 @@
1
+ %xml
2
+ %ToUserName= cdata userAccount
3
+ %FromUserName= cdata myAccount
4
+ %CreateTime= cdata Time.now.to_i
5
+ %MsgType= cdata 'text'
6
+ %Content= cdata content
@@ -0,0 +1,29 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ require 'rack/test'
4
+ require 'rack'
5
+ require 'rack-protection'
6
+ require 'rspec'
7
+ require 'rspec-html-matchers'
8
+ require './lib/wei-backend'
9
+
10
+ TEXT_MESSAGE_REQUEST='<xml><ToUserName><![CDATA[toUser]]></ToUserName><FromUserName>'+
11
+ '<![CDATA[fromUser]]></FromUserName><CreateTime>1348831860</CreateTime><MsgType><![CDATA[text]]></MsgType>'+
12
+ '<Content><![CDATA[this is a test]]></Content><MsgId>1234567890123456</MsgId></xml>'
13
+
14
+ EVENT_MESSAGE_REQUEST='<xml><ToUserName><![CDATA[toUser]]></ToUserName>'+
15
+ '<FromUserName><![CDATA[fromUser]]></FromUserName><CreateTime>123456789</CreateTime>'+
16
+ '<MsgType><![CDATA[event]]></MsgType><Event><![CDATA[subscribe]]></Event></xml>'
17
+
18
+ PARSED_PARAMS={
19
+ :ToUserName => 'toUser',
20
+ :FromUserName => 'fromUser',
21
+ :CreateTime => '1348831860',
22
+ :MsgType => 'text',
23
+ :Content => 'this is a test',
24
+ :MsgId => '1234567890123456'
25
+ }
26
+
27
+ def app
28
+ Sinatra::Application
29
+ end
@@ -0,0 +1,52 @@
1
+ require_relative 'spec_helper'
2
+ describe 'app' do
3
+ include Rack::Test::Methods
4
+
5
+ it 'should echo string when request with echostr parameter' do
6
+ get '/?echostr=test'
7
+ last_response.body.should include 'test'
8
+ end
9
+
10
+ it 'should return user defined text message when receive a text request message' do
11
+ WeiBackend::MessageDispatcher.on_text do
12
+ 'hello world'
13
+ end
14
+
15
+ post '/', TEXT_MESSAGE_REQUEST, 'CONTENT_TYPE' => 'text/xml'
16
+ last_response.body.should include '<ToUserName><![CDATA[fromUser]]></ToUserName>'
17
+ last_response.body.should include '<Content><![CDATA[hello world]]></Content>'
18
+ end
19
+
20
+ it 'should return user defined news message when receive a text request message' do
21
+ WeiBackend::MessageDispatcher.on_text do
22
+ {:title => 'title', :description => 'desc', :picture_url => 'pic url', :url => 'url'}
23
+ end
24
+
25
+ post '/', TEXT_MESSAGE_REQUEST, 'CONTENT_TYPE' => 'text/xml'
26
+ last_response.body.should include '<ToUserName><![CDATA[fromUser]]></ToUserName>'
27
+ last_response.body.should include '<Title><![CDATA[title]]></Title>'
28
+ end
29
+
30
+ it 'should return news message when receive a text request message and user defined a multi-news' do
31
+ WeiBackend::MessageDispatcher.on_text do
32
+ [{:title => 'title', :description => 'desc', :picture_url => 'pic url', :url => 'url'},
33
+ {:title => 'title1', :description => 'desc1', :picture_url => 'pic url1', :url => 'url1'}]
34
+ end
35
+
36
+ post '/', TEXT_MESSAGE_REQUEST, 'CONTENT_TYPE' => 'text/xml'
37
+ last_response.body.should include '<ToUserName><![CDATA[fromUser]]></ToUserName>'
38
+ last_response.body.should include '<Title><![CDATA[title]]></Title>'
39
+ last_response.body.should include '<Title><![CDATA[title1]]></Title>'
40
+ end
41
+
42
+ it 'should echo event request message when receive a event message' do
43
+ WeiBackend::MessageDispatcher.on_event do
44
+ 'hello event'
45
+ end
46
+
47
+ post '/', EVENT_MESSAGE_REQUEST, 'CONTENT_TYPE' => 'text/xml'
48
+ last_response.body.should include '<ToUserName><![CDATA[fromUser]]></ToUserName>'
49
+ last_response.body.should include '<Content><![CDATA[hello event]]></Content>'
50
+ end
51
+
52
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe 'weixin backend utils' do
4
+ it 'should parse params from request' do
5
+ WeiBackend::Utils.parse_params(TEXT_MESSAGE_REQUEST).should == PARSED_PARAMS
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ require_relative 'spec_helper'
2
+ require_relative '../lib/wei-backend/base'
3
+
4
+ describe 'weixin message handler' do
5
+ it 'should call text message handler when invoke handle with text message' do
6
+ WeiBackend::MessageDispatcher.any_instance.should_receive(:handle_text_message) { 'results' }
7
+ dispatcher = WeiBackend::MessageDispatcher.new
8
+ dispatcher.on('text', PARSED_PARAMS).should == {:format => 'text', :model => {:content => 'results', :myAccount=>"toUser", :userAccount=>"fromUser"}}
9
+ end
10
+
11
+ it 'should return text format result when model is a string' do
12
+ dispatcher = WeiBackend::MessageDispatcher.new
13
+ dispatcher.params=PARSED_PARAMS
14
+ dispatcher.create_model('text results').should == {:format => 'text', :model => {:content => 'text results', :myAccount=>"toUser", :userAccount=>"fromUser"}}
15
+ end
16
+
17
+ it 'should return image text format result when model is a hash' do
18
+ dispatcher = WeiBackend::MessageDispatcher.new
19
+ dispatcher.params=PARSED_PARAMS
20
+ dispatcher.create_model({:url => "http://adc/"}).should == {:format=>"image_text", :model=>{:article_count=>1, :articles=>[{:url=>"http://adc/"}], :myAccount=>"toUser", :userAccount=>"fromUser"}}
21
+ end
22
+
23
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wei-backend
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Wang Chao
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-12-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.4.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.4.4
27
+ description: wei-backend is a DSL for quickly creating weixin open platform backend
28
+ system.
29
+ email: cwang8023@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - Gemfile.lock
36
+ - README.md
37
+ - Rakefile
38
+ - examples/waiting_bus/ai_bang_client.rb
39
+ - examples/waiting_bus/app.rb
40
+ - examples/waiting_bus/bus_helper.rb
41
+ - examples/waiting_bus/config.ru
42
+ - examples/waiting_bus/config/development.yml
43
+ - examples/waiting_bus/config/production.yml
44
+ - examples/waiting_bus/config/test.yml
45
+ - examples/waiting_bus/spec/ai_bang_client_spec.rb
46
+ - examples/waiting_bus/spec/bus_helper_spec.rb
47
+ - examples/waiting_bus/spec/fixtures/bus_lines_response.json
48
+ - lib/wei-backend.rb
49
+ - lib/wei-backend/base.rb
50
+ - lib/wei-backend/main.rb
51
+ - lib/wei-backend/utils.rb
52
+ - lib/wei-backend/version.rb
53
+ - lib/wei-backend/wei-templates/image_text.haml
54
+ - lib/wei-backend/wei-templates/text.haml
55
+ - spec/spec_helper.rb
56
+ - spec/wei_backend_spec.rb
57
+ - spec/wei_backend_utils_spec.rb
58
+ - spec/weixin_message_handler_spec.rb
59
+ homepage:
60
+ licenses:
61
+ - MIT
62
+ metadata: {}
63
+ post_install_message:
64
+ rdoc_options: []
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - '>='
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ requirements: []
78
+ rubyforge_project:
79
+ rubygems_version: 2.0.6
80
+ signing_key:
81
+ specification_version: 4
82
+ summary: Best DSL for weixin development
83
+ test_files:
84
+ - spec/wei_backend_spec.rb
85
+ - spec/wei_backend_utils_spec.rb
86
+ - spec/weixin_message_handler_spec.rb