sinbotra 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,72 @@
1
+ module Sinbotra::Messenger
2
+ class Platform
3
+ attr_reader :recipient_id
4
+ attr_reader :client
5
+
6
+ def self.get_started(postback_id)
7
+ client = MessengerClient::Client.new(ENV.fetch("FACEBOOK_PAGE_TOKEN"))
8
+ client.get_started(postback_id)
9
+ end
10
+
11
+ def initialize(recipient_id)
12
+ @recipient_id = recipient_id
13
+ @client = MessengerClient::Client.new(ENV.fetch("FACEBOOK_PAGE_TOKEN"))
14
+ end
15
+
16
+ def say(text)
17
+ client.text(
18
+ recipient_id: recipient_id,
19
+ text: text
20
+ )
21
+ end
22
+ alias_method :text, :say
23
+
24
+ def quick_replies(text, replies)
25
+ client.qr(
26
+ recipient_id: recipient_id,
27
+ text: text,
28
+ replies: replies
29
+ )
30
+ end
31
+
32
+ def typing(seconds=0, &blk)
33
+ client.typing(recipient_id: recipient_id, seconds: seconds, &blk)
34
+ end
35
+
36
+ def qr(text, postback=nil)
37
+ MessengerClient::QuickReply.new(text, postback)
38
+ end
39
+
40
+ def payload_button(text, payload)
41
+ MessengerClient::PayloadButton.new(text, payload)
42
+ end
43
+
44
+ def url_button(text, url)
45
+ MessengerClient::URLButton.new(text, url)
46
+ end
47
+
48
+ def generic_template(title, subtitle, image, link, buttons)
49
+ end
50
+
51
+ def text_with_buttons(text, buttons)
52
+ client.button_template(
53
+ recipient_id: recipient_id,
54
+ text: text,
55
+ buttons: buttons
56
+ )
57
+ end
58
+
59
+ def video(url)
60
+ client.video(recipient_id: recipient_id, url: url)
61
+ end
62
+
63
+ def image(url)
64
+ client.image(recipient_id: recipient_id, url: url)
65
+ end
66
+
67
+ def location
68
+ client.location(recipient_id: recipient_id)
69
+ end
70
+ end
71
+ end
72
+
@@ -0,0 +1,46 @@
1
+ module Sinbotra::Messenger
2
+ class UserPresenter
3
+ attr_reader :current_user
4
+
5
+ def initialize(sender)
6
+ @current_user = Sinbotra::Bot::UserRepo.find_or_create(sender.id)
7
+ end
8
+
9
+ def id; current_user.id; end
10
+
11
+ def conversation
12
+ @current_user.conversation
13
+ end
14
+
15
+ def in_conversation?
16
+ !current_conversation.nil?
17
+ end
18
+
19
+ def start_conversation(convo_id)
20
+ update_conversation(convo_id, 0)
21
+ end
22
+
23
+ def next_step!
24
+ convo = current_user.conversation
25
+ id, step = convo.id, convo.step + 1
26
+ update_conversation(id, step)
27
+ end
28
+
29
+ def update_conversation(convo_id, step)
30
+ Sinbotra::Bot::UserRepo.update_conversation(
31
+ user: current_user,
32
+ convo_id: convo_id,
33
+ step: step
34
+ )
35
+ end
36
+
37
+ def end_conversation!
38
+ current_user.end_conversation
39
+ Sinbotra::Bot::UserRepo.update(current_user)
40
+ end
41
+
42
+ def current_conversation
43
+ current_user.conversation
44
+ end
45
+ end
46
+ end
@@ -2,25 +2,22 @@ require "sinbotra"
2
2
  require "sinatra"
3
3
  require "json"
4
4
 
5
+ require "sinbotra/messenger/middleware/facebook_signature"
6
+ require "sinbotra/messenger/middleware/parse_message"
7
+
5
8
  module Sinbotra
9
+
6
10
  class Server < Sinatra::Base
11
+ use Sinbotra::Messenger::Middleware::FacebookSignature
12
+ use Sinbotra::Messenger::Middleware::ParseMessage
13
+
7
14
  configure do
8
- raise ArgumentError, "You need to set a FACEBOOK_PAGE_TOKEN environmental variable to run the server!" unless ENV["FACEBOOK_PAGE_TOKEN"]
9
- raise ArgumentError, "You need to set a FACEBOOK_APP_SECRET environmental variable to run the server!" unless ENV["FACEBOOK_APP_SECRET"]
10
15
  raise ArgumentError, "You need to set a FACEBOOK_CHALLENGE environmental variable to run the server!" unless ENV["FACEBOOK_CHALLENGE"]
16
+ set :facebook_handler, Sinbotra::MessageHandler.new(:facebook)
17
+ Sinbotra::Bot::UserRepo.connect
11
18
  end
12
19
 
13
20
  helpers do
14
- def verify_facebook_signature!(payload_body)
15
- digest = OpenSSL::HMAC.hexdigest(
16
- OpenSSL::Digest.new("sha1"),
17
- ENV["FACEBOOK_APP_SECRET"],
18
- payload_body
19
- )
20
- signature = "sha1=" + digest
21
- return halt 403, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env["HTTP_X_HUB_SIGNATURE"])
22
- end
23
-
24
21
  def match_facebook_challenge(params)
25
22
  matching = params["hub.mode"] == "subscribe" &&
26
23
  params["hub.verify_token"] == ENV.fetch("FACEBOOK_CHALLENGE")
@@ -37,16 +34,15 @@ module Sinbotra
37
34
  end
38
35
 
39
36
  post "/facebook/webhook" do
40
- payload = request.body.read
41
- verify_facebook_signature!(payload)
42
37
  begin
43
- msg = JSON.load(payload)
44
- Sinbotra::Bot.handle(:facebook, msg)
38
+ msgs = request.env["facebook.messages"]
39
+ settings.facebook_handler.receive_messages(msgs)
40
+ status 200
45
41
  "OK"
46
42
  rescue Exception => e
47
43
  STDERR.puts e.message
48
44
  STDERR.puts e.backtrace
49
- status 500
45
+ status 200
50
46
  "ERROR"
51
47
  end
52
48
  end
@@ -1,3 +1,3 @@
1
1
  module Sinbotra
2
- VERSION = "0.1.6"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -34,6 +34,7 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "messenger_client"
35
35
  spec.add_dependency "sucker_punch"
36
36
  spec.add_dependency "typhoeus"
37
+ spec.add_dependency "redis"
37
38
 
38
39
  spec.add_development_dependency "puma"
39
40
  spec.add_development_dependency "bundler", "~> 1.13"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinbotra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Cruz
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-24 00:00:00.000000000 Z
11
+ date: 2017-03-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sinatra
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: redis
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'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: puma
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -153,21 +167,25 @@ files:
153
167
  - bin/console
154
168
  - bin/setup
155
169
  - config.example.ru
156
- - examples/pushup_bot.rb
157
- - examples/simple_conversations.rb
170
+ - examples/.keep
158
171
  - lib/sinbotra.rb
159
172
  - lib/sinbotra/bot.rb
160
173
  - lib/sinbotra/bot/conversation.rb
161
- - lib/sinbotra/bot/conversation_repo.rb
162
- - lib/sinbotra/bot/default_message_history.rb
174
+ - lib/sinbotra/bot/redis_store.rb
163
175
  - lib/sinbotra/bot/user.rb
164
176
  - lib/sinbotra/bot/user_repo.rb
177
+ - lib/sinbotra/bot/user_store.rb
165
178
  - lib/sinbotra/config.rb
179
+ - lib/sinbotra/message_handler.rb
180
+ - lib/sinbotra/message_store.rb
166
181
  - lib/sinbotra/messenger.rb
167
182
  - lib/sinbotra/messenger/bot.rb
168
- - lib/sinbotra/messenger/handler.rb
169
183
  - lib/sinbotra/messenger/message.rb
170
- - lib/sinbotra/messenger/user.rb
184
+ - lib/sinbotra/messenger/message_presenter.rb
185
+ - lib/sinbotra/messenger/middleware/facebook_signature.rb
186
+ - lib/sinbotra/messenger/middleware/parse_message.rb
187
+ - lib/sinbotra/messenger/platform.rb
188
+ - lib/sinbotra/messenger/user_presenter.rb
171
189
  - lib/sinbotra/server.rb
172
190
  - lib/sinbotra/version.rb
173
191
  - sinbotra.gemspec
@@ -191,7 +209,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
209
  version: '0'
192
210
  requirements: []
193
211
  rubyforge_project:
194
- rubygems_version: 2.6.8
212
+ rubygems_version: 2.5.2
195
213
  signing_key:
196
214
  specification_version: 4
197
215
  summary: A bot framework.
@@ -1,47 +0,0 @@
1
- class PushupBot
2
- include Sinbotra::Messenger::Handler
3
-
4
- enable_get_started!
5
-
6
- get_started do
7
- start_conversation(:onboarding)
8
- end
9
-
10
- hear(/hi|hello|yo/i) do |bot, _msg|
11
- bot.say "Hello there!"
12
- bot.start_conversation(:onboarding, msg)
13
- end
14
-
15
- hear do |bot, msg|
16
- intent = get_intent(msg.text)
17
- if conversations.include?(intent)
18
- bot.start_conversation(intent, msg)
19
- else
20
- bot.say next_dont_understand_phrase
21
- end
22
- end
23
-
24
- conversation :onboarding do |convo|
25
- convo.step :ask_name do |_msg|
26
- convo.say "Hi there!"
27
- convo.say "What's your name?"
28
- convo.next_step!
29
- end
30
-
31
- convo.step :ask_height do |msg|
32
- convo.store!(:name, msg.text)
33
- convo.say "Great #{convo.recall[:name]}. How tall are you?"
34
- convo.next_step!
35
- end
36
-
37
- convo.step :done do |msg|
38
- convo.store!(:height, msg.text)
39
- convo.say "Thank you for the info"
40
- typing 1
41
- convo.say "See you around!"
42
- convo.done!
43
- end
44
- end
45
- end
46
-
47
- Sinbotra::Bot.add_handler(:facebook, PushupBot)
@@ -1,38 +0,0 @@
1
- class SimpleConversation
2
- include Sinbotra::Conversation
3
-
4
- hear /hi|hello/ do |_|
5
- start_conversation(:onboarding)
6
- end
7
-
8
- hear do |msg|
9
- intent = get_intent(msg.text)
10
- if conversations.include?(intent)
11
- start_conversation(intent)
12
- else
13
- say next_dont_understand_phrase
14
- end
15
- end
16
-
17
- conversation :onboarding do
18
- step :ask_name do |convo|
19
- say "Hi there!"
20
- say "What's your name?"
21
- convo.next_step!
22
- end
23
-
24
- step :ask_height do |convo|
25
- convo.store(:name, convo.last_message.text)
26
- say "Great. How tall are you?"
27
- convo.next_step!
28
- end
29
-
30
- step :done do |convo|
31
- convo.store(:height, convo.last_message.text)
32
- say "Thank you for the info"
33
- typing 1
34
- say "See you around!"
35
- convo.done!
36
- end
37
- end
38
- end
@@ -1,17 +0,0 @@
1
- class Sinbotra::Bot
2
- class ConversationRepo
3
- class << self
4
- def connect
5
- @conversations ||= {}
6
- end
7
-
8
- def find(id)
9
- @conversations[id]
10
- end
11
-
12
- def create(id, convo)
13
- @conversations[id] = convo
14
- end
15
- end
16
- end
17
- end
@@ -1,15 +0,0 @@
1
- class Sinbotra::Bot
2
- class DefaultMessageHistory
3
- attr_reader :store
4
-
5
- def initialize(store=[])
6
- @store = store
7
- end
8
-
9
- def add_message(msg)
10
- @store << msg
11
- end
12
-
13
- def all() @store end
14
- end
15
- end
@@ -1,126 +0,0 @@
1
- module Sinbotra::Messenger
2
- module Handler
3
- def self.included(base)
4
- ensure_repos_connected
5
- base.extend ClassMethods
6
- end
7
-
8
- def self.ensure_repos_connected
9
- Sinbotra::Bot::ConversationRepo.connect
10
- Sinbotra::Bot::UserRepo.connect
11
- end
12
-
13
- module ClassMethods
14
- GET_STARTED_ID = "GET_STARTED"
15
-
16
- def handle_message(msg)
17
- user = Sinbotra::Messenger::User.load(msg)
18
- bot = Sinbotra::Messenger::Bot.new(user)
19
- # Next step in conversation
20
- # or match the `#hear`
21
- # or get default fallback
22
- if user.in_conversation?
23
- convo = user.current_conversation
24
- convo.perform_current_step(bot)
25
- elsif intent = match_intent(bot)
26
- intent.callback.(bot) unless intent.nil?
27
- else
28
- intent = default_intent
29
- intent.callback.(bot) unless intent.nil?
30
- end
31
- end
32
-
33
- def handle_action(action_name, opts={})
34
- a = @actions[action_name]
35
- if a.nil?
36
- raise ArgumentError, "No action found with the key #{action_name}"
37
- end
38
- a.(opts)
39
- end
40
-
41
- def enable_get_started!
42
- @client = MessengerClient::Client.new(ENV.fetch("FACEBOOK_PAGE_TOKEN"))
43
- @client.get_started(GET_STARTED_ID)
44
- end
45
-
46
- def get_started(&blk)
47
- postback(GET_STARTED_ID, &blk)
48
- end
49
-
50
- def conversation(id, &blk)
51
- c = Sinbotra::Bot::Conversation.new(id)
52
- Sinbotra::Bot::ConversationRepo.create(id, c)
53
- yield c
54
- end
55
-
56
- Listener = Struct.new(:matcher, :callback) do
57
- def default?; false end
58
- end
59
- DefaultListener = Struct.new(:callback) do
60
- def default?; true end
61
- end
62
- Postback = Struct.new(:matcher, :callback)
63
-
64
- def postback(matcher, &blk)
65
- # TODO: THis feels kinda wrong
66
- @postbacks ||= []
67
- @postbacks << Postback.new(matcher, blk)
68
- end
69
-
70
- def hear(matcher=nil, &blk)
71
- # TODO: THis feels kinda wrong
72
- @listeners ||= []
73
- if matcher.nil?
74
- return @listeners << DefaultListener.new(blk)
75
- end
76
- @listeners << Listener.new(matcher, blk)
77
- end
78
-
79
- def action(name, &blk)
80
- @actions ||= {}
81
- @actions[name] = blk
82
- end
83
-
84
- def get_intent(text)
85
- puts "NOOP get_intent"
86
- nil
87
- end
88
-
89
- def yes?(str)
90
- str =~ yes_matcher
91
- end
92
-
93
- def typing(delay=1)
94
- puts "NOOP show typing for #{delay} seconds"
95
- end
96
-
97
- def next_dont_understand_phrase
98
- puts "NOOP next_dont_understand_phrase"
99
- end
100
-
101
- private
102
-
103
- def yes_matcher
104
- /^(yes|yeah|ya|ok)/i
105
- end
106
-
107
- def default_intent
108
- @listeners.find(&:default?)
109
- end
110
-
111
- def match_intent(bot)
112
- msg = bot.current_message
113
- # look for postback
114
- postback = msg.postback
115
- if !postback.nil?
116
- found_pb = @postbacks.find { |pb| postback.match(pb.matcher) }
117
- return found_pb unless found_pb.nil?
118
- end
119
- # if you couldn't find a postback, look for text
120
- txt = msg.text
121
- @listeners.find { |l| !l.default? && txt.match(l.matcher) }
122
- end
123
- end
124
- end
125
- end
126
-