sinbotra 0.1.6 → 0.2.0
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 +4 -4
- data/.gitignore +1 -0
- data/README.md +126 -0
- data/examples/.keep +0 -0
- data/lib/sinbotra.rb +7 -1
- data/lib/sinbotra/bot.rb +9 -7
- data/lib/sinbotra/bot/conversation.rb +49 -50
- data/lib/sinbotra/bot/redis_store.rb +20 -0
- data/lib/sinbotra/bot/user.rb +9 -46
- data/lib/sinbotra/bot/user_repo.rb +33 -7
- data/lib/sinbotra/bot/user_store.rb +7 -0
- data/lib/sinbotra/message_handler.rb +26 -0
- data/lib/sinbotra/message_store.rb +21 -0
- data/lib/sinbotra/messenger.rb +3 -2
- data/lib/sinbotra/messenger/bot.rb +103 -52
- data/lib/sinbotra/messenger/message.rb +12 -51
- data/lib/sinbotra/messenger/message_presenter.rb +6 -0
- data/lib/sinbotra/messenger/middleware/facebook_signature.rb +32 -0
- data/lib/sinbotra/messenger/middleware/parse_message.rb +81 -0
- data/lib/sinbotra/messenger/platform.rb +72 -0
- data/lib/sinbotra/messenger/user_presenter.rb +46 -0
- data/lib/sinbotra/server.rb +13 -17
- data/lib/sinbotra/version.rb +1 -1
- data/sinbotra.gemspec +1 -0
- metadata +27 -9
- data/examples/pushup_bot.rb +0 -47
- data/examples/simple_conversations.rb +0 -38
- data/lib/sinbotra/bot/conversation_repo.rb +0 -17
- data/lib/sinbotra/bot/default_message_history.rb +0 -15
- data/lib/sinbotra/messenger/handler.rb +0 -126
- data/lib/sinbotra/messenger/user.rb +0 -10
@@ -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
|
data/lib/sinbotra/server.rb
CHANGED
@@ -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
|
-
|
44
|
-
|
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
|
45
|
+
status 200
|
50
46
|
"ERROR"
|
51
47
|
end
|
52
48
|
end
|
data/lib/sinbotra/version.rb
CHANGED
data/sinbotra.gemspec
CHANGED
@@ -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.
|
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-
|
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
|
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/
|
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/
|
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.
|
212
|
+
rubygems_version: 2.5.2
|
195
213
|
signing_key:
|
196
214
|
specification_version: 4
|
197
215
|
summary: A bot framework.
|
data/examples/pushup_bot.rb
DELETED
@@ -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,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
|
-
|