wei-backend 0.0.1

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